output: Make groups contain their subitems, and get rid of spv_item.
authorBen Pfaff <blp@cs.stanford.edu>
Thu, 14 Jan 2021 06:05:05 +0000 (22:05 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Mon, 25 Jan 2021 04:51:49 +0000 (20:51 -0800)
Until now, the output subsystem has had two different ideas for output
items: struct output_item, which is the primary representation, and
struct spv_item, which represents an output item read from an .spv file.
The biggest difference, until now, has been that spv_item contains its
children, whereas output_item bracketed children inside open/close pairs.
This commit unifies them under output_item, making output_item adopt the
container abstraction.

36 files changed:
src/language/command.c
src/output/ascii.c
src/output/automake.mk
src/output/cairo-fsm.c
src/output/cairo-pager.c
src/output/cairo.c
src/output/csv.c
src/output/driver-provider.h
src/output/driver.c
src/output/driver.h
src/output/html.c
src/output/journal.c
src/output/msglog.c
src/output/odt.c
src/output/output-item.c
src/output/output-item.h
src/output/select.c [new file with mode: 0644]
src/output/select.h [new file with mode: 0644]
src/output/spv-driver.c
src/output/spv/automake.mk
src/output/spv/spv-dump.c [deleted file]
src/output/spv/spv-legacy-data.c
src/output/spv/spv-legacy-data.h
src/output/spv/spv-output.c [deleted file]
src/output/spv/spv-output.h [deleted file]
src/output/spv/spv-select.c [deleted file]
src/output/spv/spv-select.h [deleted file]
src/output/spv/spv-writer.c
src/output/spv/spv.c
src/output/spv/spv.h
src/output/tex.c
src/ui/gui/psppire-output-view.c
src/ui/gui/psppire-output-window.c
src/ui/gui/psppire-window.c
tests/utilities/pspp-output.at
utilities/pspp-output.c

index 3c98c3feda1e2c45a860da84072348e78184a8f5..7c0c9bc413d58c4ddd34bfc32524e5e1eaa3ce4a 100644 (file)
@@ -36,6 +36,7 @@
 #include "libpspp/i18n.h"
 #include "libpspp/message.h"
 #include "libpspp/str.h"
 #include "libpspp/i18n.h"
 #include "libpspp/message.h"
 #include "libpspp/str.h"
+#include "output/driver.h"
 #include "output/output-item.h"
 
 #include "xmalloca.h"
 #include "output/output-item.h"
 
 #include "xmalloca.h"
@@ -173,8 +174,8 @@ do_parse_command (struct lexer *lexer,
                  struct dataset *ds, enum cmd_state state)
 {
   const struct command *command = NULL;
                  struct dataset *ds, enum cmd_state state)
 {
   const struct command *command = NULL;
+  size_t nesting_level = SIZE_MAX;
   enum cmd_result result;
   enum cmd_result result;
-  bool opened = false;
   int n_tokens;
 
   /* Read the command's first token. */
   int n_tokens;
 
   /* Read the command's first token. */
@@ -198,10 +199,10 @@ do_parse_command (struct lexer *lexer,
       result = CMD_FAILURE;
       goto finish;
     }
       result = CMD_FAILURE;
       goto finish;
     }
-  output_item_submit (group_open_item_create_nocopy (
-                        utf8_to_title (command->name),
-                        utf8_to_title (command->name)));
-  opened = true;
+
+  nesting_level = output_open_group (group_item_create_nocopy (
+                                       utf8_to_title (command->name),
+                                       utf8_to_title (command->name)));
 
   if (command->function == NULL)
     {
 
   if (command->function == NULL)
     {
@@ -247,8 +248,8 @@ finish:
     while (lex_token (lexer) == T_ENDCMD)
       lex_get (lexer);
 
     while (lex_token (lexer) == T_ENDCMD)
       lex_get (lexer);
 
-  if (opened)
-    output_item_submit (group_close_item_create ());
+  if (nesting_level != SIZE_MAX)
+    output_close_groups (nesting_level);
 
   return result;
 }
 
   return result;
 }
index 7b888d06ec5e02886359742e9c039b976a30dce1..cf649b0aecdffc2e7c07d0f2da24ccfa944c0c72 100644 (file)
@@ -584,11 +584,9 @@ ascii_output_table_item_unref (struct ascii_driver *a,
 }
 
 static void
 }
 
 static void
-ascii_submit (struct output_driver *driver,
-              const struct output_item *item)
+ascii_submit (struct output_driver *driver, const struct output_item *item)
 {
   struct ascii_driver *a = ascii_driver_cast (driver);
 {
   struct ascii_driver *a = ascii_driver_cast (driver);
-
   if (a->error)
     return;
 
   if (a->error)
     return;
 
@@ -620,9 +618,8 @@ ascii_submit (struct output_driver *driver,
     case OUTPUT_ITEM_CHART:
       if (a->chart_file_name != NULL)
         {
     case OUTPUT_ITEM_CHART:
       if (a->chart_file_name != NULL)
         {
-          char *file_name = xr_draw_png_chart (item->chart, a->chart_file_name,
-                                               ++a->chart_cnt, &a->fg,
-                                               &a->bg);
+          char *file_name = xr_draw_png_chart (
+            item->chart, a->chart_file_name, ++a->chart_cnt, &a->fg, &a->bg);
           if (file_name != NULL)
             {
               struct output_item *text_item = text_item_create_nocopy (
           if (file_name != NULL)
             {
               struct output_item *text_item = text_item_create_nocopy (
@@ -647,11 +644,12 @@ ascii_submit (struct output_driver *driver,
       ascii_output_table_item_unref (
         a, text_item_to_table_item (
           message_item_to_text_item (
       ascii_output_table_item_unref (
         a, text_item_to_table_item (
           message_item_to_text_item (
-              output_item_ref (item))));
+            output_item_ref (item))));
       break;
 
       break;
 
-    case OUTPUT_ITEM_GROUP_OPEN:
-    case OUTPUT_ITEM_GROUP_CLOSE:
+    case OUTPUT_ITEM_GROUP:
+      NOT_REACHED ();
+
     case OUTPUT_ITEM_PAGE_BREAK:
     case OUTPUT_ITEM_PAGE_SETUP:
       break;
     case OUTPUT_ITEM_PAGE_BREAK:
     case OUTPUT_ITEM_PAGE_SETUP:
       break;
@@ -665,10 +663,10 @@ const struct output_driver_factory list_driver_factory =
 
 static const struct output_driver_class ascii_driver_class =
   {
 
 static const struct output_driver_class ascii_driver_class =
   {
-    "text",
-    ascii_destroy,
-    ascii_submit,
-    ascii_flush,
+    .name = "text",
+    .destroy = ascii_destroy,
+    .submit = ascii_submit,
+    .flush = ascii_flush,
   };
 \f
 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
   };
 \f
 static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1,
index 61c812767d821e6878c7c04a86cef17ea1eef619..822a00200b04ce8ae75ab65a4c902506f970668d 100644 (file)
@@ -84,6 +84,8 @@ src_output_liboutput_la_SOURCES = \
        src/output/pivot-table.h \
        src/output/render.c \
        src/output/render.h \
        src/output/pivot-table.h \
        src/output/render.c \
        src/output/render.h \
+       src/output/select.c \
+       src/output/select.h \
        src/output/spv-driver.c \
        src/output/table-provider.h \
        src/output/table.c \
        src/output/spv-driver.c \
        src/output/table-provider.h \
        src/output/table.c \
index 183cdb0b7ff925a3aff80a2dccaca337957bf643..5377c28b1c1bcd847b665bfa706aa6bc5b287e85 100644 (file)
@@ -990,8 +990,7 @@ xr_fsm_create (const struct output_item *item_,
       item = output_item_ref (item_);
       break;
 
       item = output_item_ref (item_);
       break;
 
-    case OUTPUT_ITEM_GROUP_OPEN:
-    case OUTPUT_ITEM_GROUP_CLOSE:
+    case OUTPUT_ITEM_GROUP:
     case OUTPUT_ITEM_PAGE_SETUP:
       return NULL;
 
     case OUTPUT_ITEM_PAGE_SETUP:
       return NULL;
 
@@ -1148,8 +1147,7 @@ xr_fsm_measure (struct xr_fsm *fsm, cairo_t *cr, int *wp, int *hp)
       fsm->cairo = NULL;
       break;
 
       fsm->cairo = NULL;
       break;
 
-    case OUTPUT_ITEM_GROUP_OPEN:
-    case OUTPUT_ITEM_GROUP_CLOSE:
+    case OUTPUT_ITEM_GROUP:
     case OUTPUT_ITEM_MESSAGE:
     case OUTPUT_ITEM_PAGE_BREAK:
     case OUTPUT_ITEM_PAGE_SETUP:
     case OUTPUT_ITEM_MESSAGE:
     case OUTPUT_ITEM_PAGE_BREAK:
     case OUTPUT_ITEM_PAGE_SETUP:
@@ -1213,8 +1211,7 @@ xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
       fsm->cairo = NULL;
       break;
 
       fsm->cairo = NULL;
       break;
 
-    case OUTPUT_ITEM_GROUP_OPEN:
-    case OUTPUT_ITEM_GROUP_CLOSE:
+    case OUTPUT_ITEM_GROUP:
     case OUTPUT_ITEM_MESSAGE:
     case OUTPUT_ITEM_PAGE_BREAK:
     case OUTPUT_ITEM_PAGE_SETUP:
     case OUTPUT_ITEM_MESSAGE:
     case OUTPUT_ITEM_PAGE_BREAK:
     case OUTPUT_ITEM_PAGE_SETUP:
@@ -1346,8 +1343,7 @@ xr_fsm_draw_slice (struct xr_fsm *fsm, cairo_t *cr, int space)
       used = xr_fsm_draw_table (fsm, space);
       break;
 
       used = xr_fsm_draw_table (fsm, space);
       break;
 
-    case OUTPUT_ITEM_GROUP_OPEN:
-    case OUTPUT_ITEM_GROUP_CLOSE:
+    case OUTPUT_ITEM_GROUP:
     case OUTPUT_ITEM_MESSAGE:
     case OUTPUT_ITEM_PAGE_SETUP:
     case OUTPUT_ITEM_TEXT:
     case OUTPUT_ITEM_MESSAGE:
     case OUTPUT_ITEM_PAGE_SETUP:
     case OUTPUT_ITEM_TEXT:
index 06de07b82eb0ed22188e8979f792b0c18c9d63a8..1dbffd8270aa37d3bee3deffadae794fedb14ffb 100644 (file)
@@ -101,20 +101,19 @@ struct xr_pager
 
     /* Current output item. */
     struct xr_fsm *fsm;
 
     /* Current output item. */
     struct xr_fsm *fsm;
-    struct output_item *item;
+    struct output_iterator iter;
+    struct output_item *root_item;
     int slice_idx;
     int slice_idx;
+    const char *label;
 
 
-    /* Grouping, for constructing the outline for PDFs.
-
-       The 'group_ids' were returned by cairo_pdf_surface_add_outline() and
-       represent the groups within which upcoming output is nested.  The
-       'group_opens' will be passed to cairo_pdf_surface_add_outline() when the
-       next item is rendered (we defer it so that the location associated with
-       the outline item can be the first object actually output in it). */
-    int *group_ids;
-    size_t n_group_ids, allocated_group_ids;
-    struct output_item **group_opens;
-    size_t n_opens, allocated_opens;
+    /* Grouping, for constructing the outline for PDFs. */
+    struct outline_node
+      {
+        const struct output_item *item;
+        int group_id;
+      }
+    *nodes;
+    size_t n_nodes, allocated_nodes;
 
     /* Current output page. */
     cairo_t *cr;
 
     /* Current output page. */
     cairo_t *cr;
@@ -248,16 +247,14 @@ xr_pager_destroy (struct xr_pager *p)
 {
   if (p)
     {
 {
   if (p)
     {
-      free (p->group_ids);
-      for (size_t i = 0; i < p->n_opens; i++)
-        output_item_unref (p->group_opens[i]);
-      free (p->group_opens);
+      free (p->nodes);
 
       xr_page_style_unref (p->page_style);
       xr_fsm_style_unref (p->fsm_style);
 
       xr_fsm_destroy (p->fsm);
 
       xr_page_style_unref (p->page_style);
       xr_fsm_style_unref (p->fsm_style);
 
       xr_fsm_destroy (p->fsm);
-      output_item_unref (p->item);
+      output_iterator_destroy (&p->iter);
+      output_item_unref (p->root_item);
 
       if (p->cr)
         {
 
       if (p->cr)
         {
@@ -271,15 +268,15 @@ xr_pager_destroy (struct xr_pager *p)
 bool
 xr_pager_has_item (const struct xr_pager *p)
 {
 bool
 xr_pager_has_item (const struct xr_pager *p)
 {
-  return p->item != NULL;
+  return p->root_item != NULL;
 }
 
 void
 xr_pager_add_item (struct xr_pager *p, const struct output_item *item)
 {
 }
 
 void
 xr_pager_add_item (struct xr_pager *p, const struct output_item *item)
 {
-  assert (!p->item);
-  p->item = output_item_ref (item);
-  p->slice_idx = 0;
+  assert (!p->root_item);
+  p->root_item = output_item_ref (item);
+  output_iterator_init (&p->iter, item);
   xr_pager_run (p);
 }
 
   xr_pager_run (p);
 }
 
@@ -339,7 +336,7 @@ xr_pager_finish_page (struct xr_pager *p)
 bool
 xr_pager_needs_new_page (struct xr_pager *p)
 {
 bool
 xr_pager_needs_new_page (struct xr_pager *p)
 {
-  if (p->item && (!p->cr || p->y >= p->fsm_style->size[V]))
+  if (p->root_item && (!p->cr || p->y >= p->fsm_style->size[V]))
     {
       xr_pager_finish_page (p);
       return true;
     {
       xr_pager_finish_page (p);
       return true;
@@ -363,106 +360,104 @@ add_outline (cairo_t *cr, int parent_id,
 static void
 xr_pager_run (struct xr_pager *p)
 {
 static void
 xr_pager_run (struct xr_pager *p)
 {
-  if (p->item && p->cr && p->y < p->fsm_style->size[V])
+  if (!p->root_item || !p->cr || p->y >= p->fsm_style->size[V])
+    return;
+
+  for (;;)
     {
     {
-      if (!p->fsm)
+      /* Make sure we've got an object to render. */
+      while (!p->fsm)
         {
         {
-          if (p->item->type == OUTPUT_ITEM_GROUP_OPEN)
-            {
-              if (p->n_opens >= p->allocated_opens)
-                p->group_opens = x2nrealloc (p->group_opens,
-                                             &p->allocated_opens,
-                                             sizeof p->group_opens);
-              p->group_opens[p->n_opens++] = output_item_ref (p->item);
-            }
-          else if (p->item->type == OUTPUT_ITEM_GROUP_CLOSE)
+          /* If there are no remaining objects to render, then we're done. */
+          if (!p->iter.cur)
             {
             {
-              if (p->n_opens)
-                output_item_unref (p->group_opens[--p->n_opens]);
-              else if (p->n_group_ids)
-                p->n_group_ids--;
-              else
-                {
-                  /* Something wrong! */
-                }
+              output_item_unref (p->root_item);
+              p->root_item = NULL;
+              return;
             }
 
             }
 
-          p->fsm = xr_fsm_create_for_printing (p->item, p->fsm_style, p->cr);
-          if (!p->fsm)
+          /* Prepare to render the current object. */
+          p->fsm = xr_fsm_create_for_printing (p->iter.cur, p->fsm_style,
+                                               p->cr);
+          p->label = output_item_get_label (p->iter.cur);
+          p->slice_idx = 0;
+          while (p->n_nodes > p->iter.n)
+            p->n_nodes--;
+          while (p->n_nodes)
             {
             {
-              output_item_unref (p->item);
-              p->item = NULL;
+              size_t i = p->n_nodes - 1;
+              if (p->nodes[i].item == p->iter.nodes[i].group)
+                break;
 
 
-              return;
+              p->nodes--;
             }
             }
+          while (p->n_nodes < p->iter.n)
+            {
+              if (p->n_nodes >= p->allocated_nodes)
+                p->nodes = x2nrealloc (p->nodes, &p->allocated_nodes,
+                                       sizeof *p->nodes);
+              size_t i = p->n_nodes++;
+              p->nodes[i] = (struct outline_node) {
+                .item = p->iter.nodes[i].group,
+              };
+            }
+          output_iterator_next (&p->iter);
         }
 
         }
 
-      for (;;)
+      char *dest_name = NULL;
+      if (p->page_style->include_outline)
         {
         {
-          char *dest_name = NULL;
-          if (p->page_style->include_outline)
-            {
-              static int counter = 0;
-              dest_name = xasprintf ("dest%d", counter++);
-              char *attrs = xasprintf ("name='%s'", dest_name);
-              cairo_tag_begin (p->cr, CAIRO_TAG_DEST, attrs);
-              free (attrs);
-            }
+          static int counter = 0;
+          dest_name = xasprintf ("dest%d", counter++);
+          char *attrs = xasprintf ("name='%s'", dest_name);
+          cairo_tag_begin (p->cr, CAIRO_TAG_DEST, attrs);
+          free (attrs);
+        }
+
+      int spacing = p->fsm_style->object_spacing;
+      int chunk = xr_fsm_draw_slice (p->fsm, p->cr,
+                                     p->fsm_style->size[V] - p->y);
+      p->y += chunk + spacing;
+      cairo_translate (p->cr, 0, xr_to_pt (chunk + spacing));
 
 
-          int spacing = p->fsm_style->object_spacing;
-          int chunk = xr_fsm_draw_slice (p->fsm, p->cr,
-                                         p->fsm_style->size[V] - p->y);
-          p->y += chunk + spacing;
-          cairo_translate (p->cr, 0, xr_to_pt (chunk + spacing));
+      if (p->page_style->include_outline)
+        {
+          cairo_tag_end (p->cr, CAIRO_TAG_DEST);
 
 
-          if (p->page_style->include_outline)
+          if (chunk && p->slice_idx++ == 0)
             {
             {
-              cairo_tag_end (p->cr, CAIRO_TAG_DEST);
+              char *attrs = xasprintf ("dest='%s'", dest_name);
 
 
-              if (chunk && p->slice_idx++ == 0)
+              int parent_group_id = CAIRO_PDF_OUTLINE_ROOT;
+              for (size_t i = 0; i < p->n_nodes; i++)
                 {
                 {
-                  char *attrs = xasprintf ("dest='%s'", dest_name);
-
-                  int parent_group_id = (p->n_group_ids
-                                         ? p->group_ids[p->n_group_ids - 1]
-                                         : CAIRO_PDF_OUTLINE_ROOT);
-                  for (size_t i = 0; i < p->n_opens; i++)
+                  struct outline_node *node = &p->nodes[i];
+                  if (!node->group_id)
                     {
                     {
-                      parent_group_id = add_outline (
-                        p->cr, parent_group_id,
-                        output_item_get_label (p->group_opens[i]),
-                        attrs, CAIRO_PDF_OUTLINE_FLAG_OPEN);
-                      output_item_unref (p->group_opens[i]);
-
-                      if (p->n_group_ids >= p->allocated_group_ids)
-                        p->group_ids = x2nrealloc (p->group_ids,
-                                                   &p->allocated_group_ids,
-                                                   sizeof *p->group_ids);
-                      p->group_ids[p->n_group_ids++] = parent_group_id;
+                      const char *label = output_item_get_label (node->item);
+                      node->group_id = add_outline (
+                        p->cr, parent_group_id, label, attrs,
+                        CAIRO_PDF_OUTLINE_FLAG_OPEN);
                     }
                     }
-                  p->n_opens = 0;
-
-                  add_outline (p->cr, parent_group_id,
-                               output_item_get_label (p->item), attrs, 0);
-                  free (attrs);
+                  parent_group_id = node->group_id;
                 }
                 }
-              free (dest_name);
-            }
 
 
-          if (xr_fsm_is_empty (p->fsm))
-            {
-              xr_fsm_destroy (p->fsm);
-              p->fsm = NULL;
-              output_item_unref (p->item);
-              p->item = NULL;
-              return;
-            }
-          else if (!chunk)
-            {
-              assert (p->y > 0);
-              p->y = INT_MAX;
-              return;
+              add_outline (p->cr, parent_group_id, p->label, attrs, 0);
+              free (attrs);
             }
             }
+          free (dest_name);
+        }
+
+      if (xr_fsm_is_empty (p->fsm))
+        {
+          xr_fsm_destroy (p->fsm);
+          p->fsm = NULL;
+        }
+      else if (!chunk)
+        {
+          assert (p->y > 0);
+          p->y = INT_MAX;
+          return;
         }
     }
 }
         }
     }
 }
index e5ac93bcf0635a63d5c0d094fd39dbc62f647cc1..81aeddc15f4094b4ed88511605323b3007f1bd54 100644 (file)
@@ -639,8 +639,8 @@ struct output_driver_factory png_driver_factory =
 
 static const struct output_driver_class cairo_driver_class =
 {
 
 static const struct output_driver_class cairo_driver_class =
 {
-  "cairo",
-  xr_destroy,
-  xr_submit,
-  NULL,
+  .name = "cairo",
+  .destroy = xr_destroy,
+  .submit = xr_submit,
+  .handles_groups = true,
 };
 };
index a34282e012ce57da598c962ba59cf11bdd78b33b..130fe69dfe59293814bf445e50b70bb31302823a 100644 (file)
@@ -245,11 +245,8 @@ csv_submit (struct output_driver *driver,
     case OUTPUT_ITEM_CHART:
       break;
 
     case OUTPUT_ITEM_CHART:
       break;
 
-    case OUTPUT_ITEM_GROUP_OPEN:
-      break;
-
-    case OUTPUT_ITEM_GROUP_CLOSE:
-      break;
+    case OUTPUT_ITEM_GROUP:
+      NOT_REACHED ();
 
     case OUTPUT_ITEM_IMAGE:
       break;
 
     case OUTPUT_ITEM_IMAGE:
       break;
@@ -296,8 +293,8 @@ struct output_driver_factory csv_driver_factory = { "csv", "-", csv_create };
 
 static const struct output_driver_class csv_driver_class =
   {
 
 static const struct output_driver_class csv_driver_class =
   {
-    "csv",
-    csv_destroy,
-    csv_submit,
-    csv_flush,
+    .name = "csv",
+    .destroy = csv_destroy,
+    .submit = csv_submit,
+    .flush = csv_flush,
   };
   };
index 0f01fa9b30438f083ebf5865d3c5c8509b4df970..3712663bc5dcc8f95ad7ba2b7598e0767bb82e62 100644 (file)
@@ -24,6 +24,7 @@
 #include "output/driver.h"
 
 struct output_item;
 #include "output/driver.h"
 
 struct output_item;
+struct output_iterator;
 struct string_map;
 struct file_handle;
 
 struct string_map;
 struct file_handle;
 
@@ -73,8 +74,17 @@ struct output_driver_class
        it doesn't make sense for DRIVER to be used this way, then this function
        need not do anything. */
     void (*flush) (struct output_driver *driver);
        it doesn't make sense for DRIVER to be used this way, then this function
        need not do anything. */
     void (*flush) (struct output_driver *driver);
-  };
 
 
+    /* Ordinarily, the core driver code will skip passing hidden output items
+       to 'submit'.  If this member is true, the core driver hands them to the
+       driver to let it handle them itself. */
+    bool handles_show;
+
+    /* Ordinarily, the core driver code will flatten groups of output items
+       before passing them to 'submit'.  If this member is true, the core
+       driver code leaves them in place for the driver to handle. */
+    bool handles_groups;
+  };
 
 /* An abstract way for the output subsystem to create an output driver. */
 struct output_driver_factory
 
 /* An abstract way for the output subsystem to create an output driver. */
 struct output_driver_factory
index 6dfebab854cf6a3164e4220cf780053b82a1bcca..61ce6f51aa4e30272c6f6b09917a65c340a5f88c 100644 (file)
@@ -55,11 +55,8 @@ struct output_engine
     char *command_name;            /* Name of command being processed. */
     char *title, *subtitle;        /* Components of page title. */
 
     char *command_name;            /* Name of command being processed. */
     char *title, *subtitle;        /* Components of page title. */
 
-    /* Output grouping stack.
-
-       TEXT_ITEM_GROUP_OPEN pushes a group on the stack and
-       TEXT_ITEM_GROUP_CLOSE pops one off. */
-    char **groups;               /* Command names of nested sections. */
+    /* Output grouping stack. */
+    struct output_item **groups;
     size_t n_groups;
     size_t allocated_groups;
 
     size_t n_groups;
     size_t allocated_groups;
 
@@ -121,8 +118,8 @@ output_engine_pop (void)
   free (e->command_name);
   free (e->title);
   free (e->subtitle);
   free (e->command_name);
   free (e->title);
   free (e->subtitle);
-  for (size_t i = 0; i < e->n_groups; i++)
-    free (e->groups[i]);
+  if (e->n_groups)
+    output_item_unref (e->groups[0]);
   free (e->groups);
   string_map_destroy (&e->heading_vars);
   free (e);
   free (e->groups);
   string_map_destroy (&e->heading_vars);
   free (e);
@@ -137,40 +134,85 @@ output_get_supported_formats (struct string_set *formats)
     string_set_insert (formats, (*fp)->extension);
 }
 
     string_set_insert (formats, (*fp)->extension);
 }
 
+static bool
+output_driver_should_show (const struct output_driver *d,
+                           const struct output_item *item)
+{
+  enum settings_output_type type = SETTINGS_OUTPUT_RESULT;
+  switch (item->type)
+    {
+    case OUTPUT_ITEM_MESSAGE:
+      type = (item->message->severity == MSG_S_NOTE
+              ? SETTINGS_OUTPUT_NOTE
+              : SETTINGS_OUTPUT_ERROR);
+      break;
+
+    case OUTPUT_ITEM_TEXT:
+      if (item->text.subtype == TEXT_ITEM_SYNTAX)
+        type = SETTINGS_OUTPUT_SYNTAX;
+      break;
+
+    case OUTPUT_ITEM_CHART:
+    case OUTPUT_ITEM_GROUP:
+    case OUTPUT_ITEM_IMAGE:
+    case OUTPUT_ITEM_PAGE_BREAK:
+    case OUTPUT_ITEM_PAGE_SETUP:
+    case OUTPUT_ITEM_TABLE:
+      break;
+    }
+
+  return (settings_get_output_routing (type) & d->device_type) != 0;
+}
+
+/* Adds to OUT the subset of IN that driver D should show, considering routing
+   and visibility of each item, and flattening groups for drivers that don't
+   handle them internally. */
+static void
+make_driver_output_subset (const struct output_item *in,
+                           const struct output_driver *d,
+                           struct output_item *out)
+{
+  if (in->type == OUTPUT_ITEM_GROUP)
+    {
+      /* If we should include the group itself, then clone IN inside OUT, and
+         add any children to the clone instead to OUT directly. */
+      if (output_driver_should_show (d, in) && d->class->handles_groups)
+        {
+          struct output_item *group = group_item_clone_empty (in);
+          group_item_add_child (out, group);
+          out = group;
+        }
+
+      for (size_t i = 0; i < in->group.n_children; i++)
+        make_driver_output_subset (in->group.children[i], d, out);
+    }
+  else
+    {
+      if (output_driver_should_show (d, in)
+          && (in->show || d->class->handles_show))
+        group_item_add_child (out, output_item_ref (in));
+    }
+}
+
 static void
 output_submit__ (struct output_engine *e, struct output_item *item)
 {
 static void
 output_submit__ (struct output_engine *e, struct output_item *item)
 {
+  if (e->n_groups > 0)
+    {
+      group_item_add_child (e->groups[e->n_groups - 1], item);
+      return;
+    }
+
   struct llx *llx, *next;
   llx_for_each_safe (llx, next, &e->drivers)
     {
       struct output_driver *d = llx_data (llx);
 
   struct llx *llx, *next;
   llx_for_each_safe (llx, next, &e->drivers)
     {
       struct output_driver *d = llx_data (llx);
 
-      enum settings_output_type type = SETTINGS_OUTPUT_RESULT;
-      switch (item->type)
-        {
-        case OUTPUT_ITEM_MESSAGE:
-          type = (item->message->severity == MSG_S_NOTE
-                  ? SETTINGS_OUTPUT_NOTE
-                  : SETTINGS_OUTPUT_ERROR);
-          break;
-
-        case OUTPUT_ITEM_TEXT:
-          if (item->text.subtype == TEXT_ITEM_SYNTAX)
-            type = SETTINGS_OUTPUT_SYNTAX;
-          break;
-
-        case OUTPUT_ITEM_CHART:
-        case OUTPUT_ITEM_GROUP_OPEN:
-        case OUTPUT_ITEM_GROUP_CLOSE:
-        case OUTPUT_ITEM_IMAGE:
-        case OUTPUT_ITEM_PAGE_BREAK:
-        case OUTPUT_ITEM_PAGE_SETUP:
-        case OUTPUT_ITEM_TABLE:
-          break;
-        }
-
-      if (settings_get_output_routing (type) & d->device_type)
-        d->class->submit (d, item);
+      struct output_item *root = root_item_create ();
+      make_driver_output_subset (item, d, root);
+      for (size_t i = 0; i < root->group.n_children; i++)
+        d->class->submit (d, root->group.children[i]);
+      output_item_unref (root);
     }
 
   output_item_unref (item);
     }
 
   output_item_unref (item);
@@ -222,47 +264,7 @@ output_submit (struct output_item *item)
     return;
   flush_deferred_text (e);
 
     return;
   flush_deferred_text (e);
 
-  switch (item->type)
-    {
-    case OUTPUT_ITEM_GROUP_OPEN:
-      if (e->n_groups >= e->allocated_groups)
-        e->groups = x2nrealloc (e->groups, &e->allocated_groups,
-                                sizeof *e->groups);
-      e->groups[e->n_groups] = xstrdup_if_nonnull (item->command_name);
-      e->n_groups++;
-      break;
-
-    case OUTPUT_ITEM_GROUP_CLOSE:
-      assert (e->n_groups > 0);
-
-      size_t idx = --e->n_groups;
-      free (e->groups[idx]);
-
-      char *key = xasprintf ("Head%zu", idx);
-      free (string_map_find_and_delete (&e->heading_vars, key));
-      free (key);
-      break;
-
-    case OUTPUT_ITEM_TEXT:
-      {
-        enum text_item_subtype st = item->text.subtype;
-        char *key = (st == TEXT_ITEM_TITLE ? xasprintf ("Head%zu", e->n_groups)
-                     : st == TEXT_ITEM_PAGE_TITLE ? xstrdup ("PageTitle")
-                     : NULL);
-        if (key)
-          string_map_replace_nocopy (&e->heading_vars, key,
-                                     text_item_get_plain_text (item));
-      }
-      break;
-
-    case OUTPUT_ITEM_CHART:
-    case OUTPUT_ITEM_IMAGE:
-    case OUTPUT_ITEM_MESSAGE:
-    case OUTPUT_ITEM_PAGE_BREAK:
-    case OUTPUT_ITEM_PAGE_SETUP:
-    case OUTPUT_ITEM_TABLE:
-      break;
-    }
+  /* XXX heading_vars */
 
   output_submit__ (e, item);
 }
 
   output_submit__ (e, item);
 }
@@ -277,8 +279,8 @@ output_get_command_name (void)
     return NULL;
 
   for (size_t i = e->n_groups; i-- > 0;)
     return NULL;
 
   for (size_t i = e->n_groups; i-- > 0;)
-    if (e->groups[i])
-      return e->groups[i];
+    if (e->groups[i]->command_name)
+      return e->groups[i]->command_name;
 
   return NULL;
 }
 
   return NULL;
 }
@@ -290,6 +292,40 @@ output_get_uppercase_command_name (void)
   return command_name ? utf8_to_upper (command_name) : NULL;
 }
 
   return command_name ? utf8_to_upper (command_name) : NULL;
 }
 
+size_t
+output_open_group (struct output_item *item)
+{
+  struct output_engine *e = engine_stack_top ();
+  if (e == NULL)
+    return 0;
+
+  if (e->n_groups >= e->allocated_groups)
+    e->groups = x2nrealloc (e->groups, &e->allocated_groups,
+                            sizeof *e->groups);
+  e->groups[e->n_groups++] = item;
+  if (e->n_groups > 1)
+    group_item_add_child (e->groups[e->n_groups - 2], item);
+
+  return e->n_groups - 1;
+}
+
+void
+output_close_groups (size_t nesting_level)
+{
+  struct output_engine *e = engine_stack_top ();
+  if (e == NULL)
+    return;
+
+  while (e->n_groups > nesting_level)
+    {
+      flush_deferred_text (e);
+
+      struct output_item *group = e->groups[--e->n_groups];
+      if (e->n_groups == 0)
+        output_submit__ (e, group);
+    }
+}
+
 /* Flushes output to screen devices, so that the user can see
    output that doesn't fill up an entire page. */
 void
 /* Flushes output to screen devices, so that the user can see
    output that doesn't fill up an entire page. */
 void
@@ -357,14 +393,6 @@ output_set_filename (const char *filename)
 
   string_map_replace (&e->heading_vars, "Filename", filename);
 }
 
   string_map_replace (&e->heading_vars, "Filename", filename);
 }
-
-size_t
-output_get_group_level (void)
-{
-  struct output_engine *e = engine_stack_top ();
-
-  return e->n_groups;
-}
 \f
 void
 output_driver_init (struct output_driver *driver,
 \f
 void
 output_driver_init (struct output_driver *driver,
index 8c386cb0650614d596b49ed3344322f38cae2d5e..b3e3a520b25af7053a72500735d981453f135060 100644 (file)
@@ -42,7 +42,8 @@ void output_set_filename (const char *);
 const char *output_get_command_name (void);
 char *output_get_uppercase_command_name (void);
 
 const char *output_get_command_name (void);
 char *output_get_uppercase_command_name (void);
 
-size_t output_get_group_level (void);
+size_t output_open_group (struct output_item *);
+void output_close_groups (size_t nesting_level);
 
 void output_driver_parse_option (const char *option,
                                  struct string_map *options);
 
 void output_driver_parse_option (const char *option,
                                  struct string_map *options);
index dcff0af085bcfe4e5feed0e843923c16b68c922a..e3483b4ebc9318a042cd7baaf9a207b1b6285c62 100644 (file)
@@ -247,7 +247,8 @@ html_destroy (struct output_driver *driver)
 }
 
 static void
 }
 
 static void
-html_submit (struct output_driver *driver, const struct output_item *item)
+html_submit__ (struct output_driver *driver, const struct output_item *item,
+               int level)
 {
   struct html_driver *html = html_driver_cast (driver);
 
 {
   struct html_driver *html = html_driver_cast (driver);
 
@@ -270,10 +271,9 @@ html_submit (struct output_driver *driver, const struct output_item *item)
         }
       break;
 
         }
       break;
 
-    case OUTPUT_ITEM_GROUP_OPEN:
-      break;
-
-    case OUTPUT_ITEM_GROUP_CLOSE:
+    case OUTPUT_ITEM_GROUP:
+      for (size_t i = 0; i < item->group.n_children; i++)
+        html_submit__ (driver, item->group.children[i], level + 1);
       break;
 
     case OUTPUT_ITEM_IMAGE:
       break;
 
     case OUTPUT_ITEM_IMAGE:
@@ -320,8 +320,7 @@ html_submit (struct output_driver *driver, const struct output_item *item)
 
           case TEXT_ITEM_TITLE:
             {
 
           case TEXT_ITEM_TITLE:
             {
-              int level = MIN (5, output_get_group_level ()) + 1;
-              char tag[3] = { 'H', level + '1', '\0' };
+              char tag[3] = { 'H', MIN (5, level) + '0', '\0' };
               print_title_tag (html->file, tag, s);
             }
             break;
               print_title_tag (html->file, tag, s);
             }
             break;
@@ -345,6 +344,12 @@ html_submit (struct output_driver *driver, const struct output_item *item)
     }
 }
 
     }
 }
 
+static void
+html_submit (struct output_driver *driver, const struct output_item *item)
+{
+  html_submit__ (driver, item, 1);
+}
+
 /* Write TEXT to file F, escaping characters as necessary for HTML.  Spaces are
    replaced by SPACE, which should be " " or "&nbsp;" New-lines are replaced by
    NEWLINE, which might be "<BR>" or "\n" or something else appropriate. */
 /* Write TEXT to file F, escaping characters as necessary for HTML.  Spaces are
    replaced by SPACE, which should be " " or "&nbsp;" New-lines are replaced by
    NEWLINE, which might be "<BR>" or "\n" or something else appropriate. */
@@ -740,8 +745,8 @@ struct output_driver_factory html_driver_factory =
 
 static const struct output_driver_class html_driver_class =
   {
 
 static const struct output_driver_class html_driver_class =
   {
-    "html",
-    html_destroy,
-    html_submit,
-    NULL,
+    .name = "html",
+    .destroy = html_destroy,
+    .submit = html_submit,
+    .handles_groups = true,
   };
   };
index 2dd1925843c84a595a6c247292014a9b2229fcbe..4945db2ee9d831aa1fcbdb78790836a6617f3e5b 100644 (file)
@@ -115,9 +115,12 @@ journal_submit (struct output_driver *driver, const struct output_item *item)
         journal_output (j, text_item_get_plain_text (item));
       break;
 
         journal_output (j, text_item_get_plain_text (item));
       break;
 
+    case OUTPUT_ITEM_GROUP:
+      for (size_t i = 0; i < item->group.n_children; i++)
+        journal_submit (driver, item->group.children[i]);
+      break;
+
     case OUTPUT_ITEM_CHART:
     case OUTPUT_ITEM_CHART:
-    case OUTPUT_ITEM_GROUP_OPEN:
-    case OUTPUT_ITEM_GROUP_CLOSE:
     case OUTPUT_ITEM_IMAGE:
     case OUTPUT_ITEM_PAGE_BREAK:
     case OUTPUT_ITEM_PAGE_SETUP:
     case OUTPUT_ITEM_IMAGE:
     case OUTPUT_ITEM_PAGE_BREAK:
     case OUTPUT_ITEM_PAGE_SETUP:
@@ -128,12 +131,10 @@ journal_submit (struct output_driver *driver, const struct output_item *item)
 
 static const struct output_driver_class journal_class =
   {
 
 static const struct output_driver_class journal_class =
   {
-    "journal",
-    journal_destroy,
-    journal_submit,
-    NULL                        /* flush */
+    .name = "journal",
+    .destroy = journal_destroy,
+    .submit = journal_submit,
   };
   };
-
 \f
 
 /* Enables journaling. */
 \f
 
 /* Enables journaling. */
index f91c7e61176bece6167d5c0ab9a983df916388f2..7fe6555f19dc4acc8f0d16ff3812392f5d9fbbd9 100644 (file)
@@ -108,8 +108,7 @@ msglog_submit (struct output_driver *driver, const struct output_item *item)
 
 static const struct output_driver_class msglog_class =
   {
 
 static const struct output_driver_class msglog_class =
   {
-    "msglog",
-    msglog_destroy,
-    msglog_submit,
-    NULL
+    .name = "msglog",
+    .destroy = msglog_destroy,
+    .submit = msglog_submit,
   };
   };
index 683d19bcf59b31cb48206ead9a1eeb9705e8fe97..a7eeaee9ef279f62c66ad66fd6fc36b766d0ac52 100644 (file)
@@ -597,11 +597,8 @@ odt_submit (struct output_driver *driver, const struct output_item *item)
     case OUTPUT_ITEM_CHART:
       break;
 
     case OUTPUT_ITEM_CHART:
       break;
 
-    case OUTPUT_ITEM_GROUP_OPEN:
-      break;
-
-    case OUTPUT_ITEM_GROUP_CLOSE:
-      break;
+    case OUTPUT_ITEM_GROUP:
+      NOT_REACHED ();
 
     case OUTPUT_ITEM_IMAGE:
       break;
 
     case OUTPUT_ITEM_IMAGE:
       break;
@@ -639,8 +636,7 @@ struct output_driver_factory odt_driver_factory =
 
 static const struct output_driver_class odt_driver_class =
 {
 
 static const struct output_driver_class odt_driver_class =
 {
-  "odf",
-  odt_destroy,
-  odt_submit,
-  NULL,
+  .name = "odf",
+  .destroy = odt_destroy,
+  .submit = odt_submit,
 };
 };
index 0168b10b4a5e6a07caa206d6dd6f8248d6a49793..5bfd1254134629be3d92bc437cbcffa6623f77c8 100644 (file)
@@ -25,6 +25,7 @@
 #include "libpspp/cast.h"
 #include "libpspp/message.h"
 #include "libpspp/str.h"
 #include "libpspp/cast.h"
 #include "libpspp/message.h"
 #include "libpspp/str.h"
+#include "libpspp/zip-reader.h"
 #include "output/chart.h"
 #include "output/driver.h"
 #include "output/page-setup.h"
 #include "output/chart.h"
 #include "output/driver.h"
 #include "output/page-setup.h"
 #define _(msgid) gettext (msgid)
 #define N_(msgid) msgid
 \f
 #define _(msgid) gettext (msgid)
 #define N_(msgid) msgid
 \f
+const char *
+output_item_type_to_string (enum output_item_type type)
+{
+  switch (type)
+    {
+    case OUTPUT_ITEM_CHART: return "chart";
+    case OUTPUT_ITEM_GROUP: return "group";
+    case OUTPUT_ITEM_IMAGE: return "image";
+    case OUTPUT_ITEM_MESSAGE: return "message";
+    case OUTPUT_ITEM_PAGE_BREAK: return "page break";
+    case OUTPUT_ITEM_PAGE_SETUP: return "page setup";
+    case OUTPUT_ITEM_TABLE: return "table";
+    case OUTPUT_ITEM_TEXT: return "text";
+    }
+
+  NOT_REACHED ();
+}
+\f
 #define OUTPUT_ITEM_INITIALIZER(TYPE) .type = TYPE, .ref_cnt = 1, .show = true
 
 /* Increases ITEM's reference count, indicating that it has an additional
 #define OUTPUT_ITEM_INITIALIZER(TYPE) .type = TYPE, .ref_cnt = 1, .show = true
 
 /* Increases ITEM's reference count, indicating that it has an additional
@@ -66,10 +85,9 @@ output_item_unref (struct output_item *item)
               chart_unref (item->chart);
               break;
 
               chart_unref (item->chart);
               break;
 
-            case OUTPUT_ITEM_GROUP_OPEN:
-              break;
-
-            case OUTPUT_ITEM_GROUP_CLOSE:
+            case OUTPUT_ITEM_GROUP:
+              for (size_t i = 0; i < item->group.n_children; i++)
+                output_item_unref (item->group.children[i]);
               break;
 
             case OUTPUT_ITEM_IMAGE:
               break;
 
             case OUTPUT_ITEM_IMAGE:
@@ -99,6 +117,7 @@ output_item_unref (struct output_item *item)
           free (item->label);
           free (item->command_name);
           free (item->cached_label);
           free (item->label);
           free (item->command_name);
           free (item->cached_label);
+          spv_info_destroy (item->spv_info);
           free (item);
         }
     }
           free (item);
         }
     }
@@ -112,14 +131,10 @@ output_item_is_shared (const struct output_item *item)
   return item->ref_cnt > 1;
 }
 
   return item->ref_cnt > 1;
 }
 
-struct output_item *
-output_item_unshare (struct output_item *old)
+/* Returns a clone of OLD, without initializing type-specific fields.  */
+static struct output_item *
+output_item_clone_common (const struct output_item *old)
 {
 {
-  assert (old->ref_cnt > 0);
-  if (!output_item_is_shared (old))
-    return old;
-  output_item_unref (old);
-
   struct output_item *new = xmalloc (sizeof *new);
   *new = (struct output_item) {
     .ref_cnt = 1,
   struct output_item *new = xmalloc (sizeof *new);
   *new = (struct output_item) {
     .ref_cnt = 1,
@@ -127,17 +142,35 @@ output_item_unshare (struct output_item *old)
     .command_name = xstrdup_if_nonnull (old->command_name),
     .type = old->type,
     .show = old->show,
     .command_name = xstrdup_if_nonnull (old->command_name),
     .type = old->type,
     .show = old->show,
+    .spv_info = spv_info_clone (old->spv_info),
   };
   };
+  return new;
+}
+
+struct output_item *
+output_item_unshare (struct output_item *old)
+{
+  assert (old->ref_cnt > 0);
+  if (!output_item_is_shared (old))
+    return old;
+  output_item_unref (old);
+
+  struct output_item *new = output_item_clone_common (old);
   switch (old->type)
     {
     case OUTPUT_ITEM_CHART:
       new->chart = chart_ref (old->chart);
       break;
 
   switch (old->type)
     {
     case OUTPUT_ITEM_CHART:
       new->chart = chart_ref (old->chart);
       break;
 
-    case OUTPUT_ITEM_GROUP_OPEN:
-      break;
+    case OUTPUT_ITEM_GROUP:
+      new->group.children = xmemdup (
+        old->group.children,
+        old->group.n_children * sizeof *old->group.children);
+      new->group.n_children = new->group.allocated_children
+        = old->group.n_children;
 
 
-    case OUTPUT_ITEM_GROUP_CLOSE:
+      for (size_t i = 0; i < new->group.n_children; i++)
+        output_item_ref (new->group.children[i]);
       break;
 
     case OUTPUT_ITEM_IMAGE:
       break;
 
     case OUTPUT_ITEM_IMAGE:
@@ -173,6 +206,28 @@ output_item_submit (struct output_item *item)
   output_submit (item);
 }
 
   output_submit (item);
 }
 
+/* If ROOT is a group item, submits each of its children, but not ROOT itself.
+   This is useful if ROOT is being used as a container for output items but it
+   has no significance itself.
+
+   If ROOT is not a group, submits it the normal way.
+
+   Takes ownership of ROOT. */
+void
+output_item_submit_children (struct output_item *root)
+{
+  assert (!output_item_is_shared (root));
+  if (root->type == OUTPUT_ITEM_GROUP)
+    {
+      for (size_t i = 0; i < root->group.n_children; i++)
+        output_submit (root->group.children[i]);
+      root->group.n_children = 0;
+      output_item_unref (root);
+    }
+  else
+    output_submit (root);
+}
+
 /* Returns the label for ITEM, which the caller must not modify or free. */
 const char *
 output_item_get_label (const struct output_item *item)
 /* Returns the label for ITEM, which the caller must not modify or free. */
 const char *
 output_item_get_label (const struct output_item *item)
@@ -185,13 +240,9 @@ output_item_get_label (const struct output_item *item)
     case OUTPUT_ITEM_CHART:
       return item->chart->title ? item->chart->title : _("Chart");
 
     case OUTPUT_ITEM_CHART:
       return item->chart->title ? item->chart->title : _("Chart");
 
-    case OUTPUT_ITEM_GROUP_OPEN:
+    case OUTPUT_ITEM_GROUP:
       return item->command_name ? item->command_name : _("Group");
 
       return item->command_name ? item->command_name : _("Group");
 
-    case OUTPUT_ITEM_GROUP_CLOSE:
-      /* Not marked for translation: user should never see it. */
-      return "Group Close";
-
     case OUTPUT_ITEM_IMAGE:
       return "Image";
 
     case OUTPUT_ITEM_IMAGE:
       return "Image";
 
@@ -249,6 +300,145 @@ output_item_set_label_nocopy (struct output_item *item, char *label)
   free (item->label);
   item->label = label;
 }
   free (item->label);
   item->label = label;
 }
+
+void
+output_item_set_command_name (struct output_item *item, const char *name)
+{
+  output_item_set_command_name_nocopy (item, xstrdup_if_nonnull (name));
+}
+
+void
+output_item_set_command_name_nocopy (struct output_item *item, char *name)
+{
+  free (item->command_name);
+  item->command_name = name;
+}
+
+char *
+output_item_get_subtype (const struct output_item *item)
+{
+  return (item->type == OUTPUT_ITEM_TABLE
+          ? pivot_value_to_string (item->table->subtype, item->table)
+          : NULL);
+}
+
+void
+output_item_add_spv_info (struct output_item *item)
+{
+  assert (!output_item_is_shared (item));
+  if (!item->spv_info)
+    item->spv_info = xzalloc (sizeof *item->spv_info);
+}
+\f
+static void
+indent (int indentation)
+{
+  for (int i = 0; i < indentation * 2; i++)
+    putchar (' ');
+}
+
+void
+output_item_dump (const struct output_item *item, int indentation)
+{
+  indent (indentation);
+  if (item->label)
+    printf ("label=\"%s\" ", item->label);
+  if (item->command_name)
+    printf ("command=\"%s\" ", item->command_name);
+  if (!item->show)
+    printf ("(%s) ", item->type == OUTPUT_ITEM_GROUP ? "collapsed" : "hidden");
+
+  switch (item->type)
+    {
+    case OUTPUT_ITEM_CHART:
+      printf ("chart \"%s\"\n", item->chart->title ? item->chart->title : "");
+      break;
+
+    case OUTPUT_ITEM_GROUP:
+      printf ("group\n");
+      for (size_t i = 0; i < item->group.n_children; i++)
+        output_item_dump (item->group.children[i], indentation + 1);
+      break;
+
+    case OUTPUT_ITEM_IMAGE:
+      printf ("image\n");
+      break;
+
+    case OUTPUT_ITEM_MESSAGE:
+      printf ("message\n");
+      break;
+
+    case OUTPUT_ITEM_PAGE_BREAK:
+      printf ("page break\n");
+      break;
+
+    case OUTPUT_ITEM_PAGE_SETUP:
+      printf ("page setup\n");
+      break;
+
+    case OUTPUT_ITEM_TABLE:
+      pivot_table_dump (item->table, indentation + 1);
+      break;
+
+    case OUTPUT_ITEM_TEXT:
+      printf ("text %s \"%s\"\n",
+              text_item_subtype_to_string (item->text.subtype),
+              pivot_value_to_string_defaults (item->text.content));
+      break;
+    }
+}
+\f
+void
+output_iterator_init (struct output_iterator *iter,
+                      const struct output_item *item)
+{
+  *iter = (struct output_iterator) OUTPUT_ITERATOR_INIT (item);
+}
+
+void
+output_iterator_destroy (struct output_iterator *iter)
+{
+  if (iter)
+    {
+      free (iter->nodes);
+      iter->nodes = NULL;
+      iter->n = iter->allocated = 0;
+    }
+}
+
+void
+output_iterator_next (struct output_iterator *iter)
+{
+  const struct output_item *cur = iter->cur;
+  if (cur)
+    {
+      if (cur->type == OUTPUT_ITEM_GROUP && cur->group.n_children > 0)
+        {
+          if (iter->n >= iter->allocated)
+            iter->nodes = x2nrealloc (iter->nodes, &iter->allocated,
+                                      sizeof *iter->nodes);
+          iter->nodes[iter->n++] = (struct output_iterator_node) {
+            .group = cur,
+            .idx = 0,
+          };
+          iter->cur = cur->group.children[0];
+          return;
+        }
+
+      for (; iter->n > 0; iter->n--)
+        {
+          struct output_iterator_node *node = &iter->nodes[iter->n - 1];
+          if (++node->idx < node->group->group.n_children)
+            {
+              iter->cur = node->group->group.children[node->idx];
+              return;
+            }
+        }
+
+      iter->cur = NULL;
+      output_iterator_destroy (iter);
+    }
+}
 \f
 struct output_item *
 chart_item_create (struct chart *chart)
 \f
 struct output_item *
 chart_item_create (struct chart *chart)
@@ -262,33 +452,55 @@ chart_item_create (struct chart *chart)
 }
 \f
 struct output_item *
 }
 \f
 struct output_item *
-group_open_item_create (const char *command_name, const char *label)
+group_item_create (const char *command_name, const char *label)
 {
 {
-  return group_open_item_create_nocopy (
+  return group_item_create_nocopy (
     xstrdup_if_nonnull (command_name),
     xstrdup_if_nonnull (label));
 }
 
 struct output_item *
     xstrdup_if_nonnull (command_name),
     xstrdup_if_nonnull (label));
 }
 
 struct output_item *
-group_open_item_create_nocopy (char *command_name, char *label)
+group_item_create_nocopy (char *command_name, char *label)
 {
   struct output_item *item = xmalloc (sizeof *item);
   *item = (struct output_item) {
 {
   struct output_item *item = xmalloc (sizeof *item);
   *item = (struct output_item) {
-    OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_GROUP_OPEN),
+    OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_GROUP),
     .label = label,
     .command_name = command_name,
   };
   return item;
 }
 
     .label = label,
     .command_name = command_name,
   };
   return item;
 }
 
+/* Returns a new group item suitable as the root node of an output document.  A
+   root node is a group whose own properties are mostly disregarded.  Instead
+   of having root nodes, it would make just as much sense to just keep around
+   arrays of nodes that would serve as the top level of an output document, but
+   we'd need more special cases instead of just using the existing support for
+   group items. */
 struct output_item *
 struct output_item *
-group_close_item_create (void)
+root_item_create (void)
 {
 {
-  struct output_item *item = xmalloc (sizeof *item);
-  *item = (struct output_item) {
-    OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_GROUP_CLOSE),
-  };
-  return item;
+  return group_item_create ("", _("Output"));
+}
+
+/* Returns a clone of OLD but without any of its children. */
+struct output_item *
+group_item_clone_empty (const struct output_item *old)
+{
+  return output_item_clone_common (old);
+}
+
+/* Adds CHILD as a child of group item PARENT. */
+void
+group_item_add_child (struct output_item *parent, struct output_item *child)
+{
+  assert (parent->type == OUTPUT_ITEM_GROUP);
+  assert (!output_item_is_shared (parent));
+  if (parent->group.n_children >= parent->group.allocated_children)
+    parent->group.children = x2nrealloc (parent->group.children,
+                                         &parent->group.allocated_children,
+                                         sizeof *parent->group.children);
+  parent->group.children[parent->group.n_children++] = child;
 }
 \f
 /* Creates and returns a new output item containing IMAGE.  Takes ownership of
 }
 \f
 /* Creates and returns a new output item containing IMAGE.  Takes ownership of
@@ -554,4 +766,55 @@ text_item_subtype_to_string (enum text_item_subtype subtype)
       return _("Text");
     }
 }
       return _("Text");
     }
 }
+\f
+void
+spv_info_destroy (struct spv_info *spv_info)
+{
+  if (spv_info)
+    {
+      zip_reader_unref (spv_info->zip_reader);
+      free (spv_info->structure_member);
+      free (spv_info->xml_member);
+      free (spv_info->bin_member);
+      free (spv_info->png_member);
+      free (spv_info);
+    }
+}
 
 
+struct spv_info *
+spv_info_clone (const struct spv_info *old)
+{
+  if (!old)
+    return NULL;
+
+  struct spv_info *new = xmalloc (sizeof *new);
+  *new = (struct spv_info) {
+    .zip_reader = old->zip_reader ? zip_reader_ref (old->zip_reader) : NULL,
+    .error = old->error,
+    .structure_member = xstrdup_if_nonnull (old->structure_member),
+    .xml_member = xstrdup_if_nonnull (old->xml_member),
+    .bin_member = xstrdup_if_nonnull (old->bin_member),
+    .png_member = xstrdup_if_nonnull (old->png_member),
+  };
+  return new;
+}
+
+size_t
+spv_info_get_members (const struct spv_info *spv_info, const char **members,
+                      size_t allocated_members)
+{
+  if (!spv_info)
+    return 0;
+
+  const char *s[] = {
+    spv_info->structure_member,
+    spv_info->xml_member,
+    spv_info->bin_member,
+    spv_info->png_member,
+  };
+  size_t n = 0;
+  for (size_t i = 0; i < sizeof s / sizeof *s; i++)
+    if (s[i] && n < allocated_members)
+      members[n++] = s[i];
+  return n;
+}
index a408807f0fe7c9114125633b996f2820d0fe0211..72d4661510d3993e8cf5ffc95ce5c9d78e1b172d 100644 (file)
 #include <cairo.h>
 #include <stdbool.h>
 #include "libpspp/cast.h"
 #include <cairo.h>
 #include <stdbool.h>
 #include "libpspp/cast.h"
+#include "libpspp/string-array.h"
 
 enum output_item_type
   {
     OUTPUT_ITEM_CHART,
 
 enum output_item_type
   {
     OUTPUT_ITEM_CHART,
-    OUTPUT_ITEM_GROUP_OPEN,
-    OUTPUT_ITEM_GROUP_CLOSE,
+    OUTPUT_ITEM_GROUP,
     OUTPUT_ITEM_IMAGE,
     OUTPUT_ITEM_MESSAGE,
     OUTPUT_ITEM_PAGE_BREAK,
     OUTPUT_ITEM_IMAGE,
     OUTPUT_ITEM_MESSAGE,
     OUTPUT_ITEM_PAGE_BREAK,
@@ -39,6 +39,8 @@ enum output_item_type
     OUTPUT_ITEM_TEXT,
   };
 
     OUTPUT_ITEM_TEXT,
   };
 
+const char *output_item_type_to_string (enum output_item_type);
+
 /* A single output item. */
 struct output_item
   {
 /* A single output item. */
 struct output_item
   {
@@ -59,7 +61,7 @@ struct output_item
        output. */
     char *command_name;
 
        output. */
     char *command_name;
 
-    /* For OUTPUT_ITEM_GROUP_OPEN, this is true if the group's subtree should
+    /* For OUTPUT_ITEM_GROUP, this is true if the group's subtree should
        be expanded in an outline view, false otherwise.
 
        For other kinds of output items, this is true to show the item's
        be expanded in an outline view, false otherwise.
 
        For other kinds of output items, this is true to show the item's
@@ -67,6 +69,10 @@ struct output_item
        outline view. */
     bool show;
 
        outline view. */
     bool show;
 
+    /* Information about the SPV file this output_item was read from.
+       May be NULL. */
+    struct spv_info *spv_info;
+
     enum output_item_type type;
     union
       {
     enum output_item_type type;
     union
       {
@@ -74,6 +80,14 @@ struct output_item
 
         cairo_surface_t *image;
 
 
         cairo_surface_t *image;
 
+        struct
+          {
+            struct output_item **children;
+            size_t n_children;
+            size_t allocated_children;
+          }
+        group;
+
         struct msg *message;
 
         struct page_setup *page_setup;
         struct msg *message;
 
         struct page_setup *page_setup;
@@ -104,23 +118,65 @@ bool output_item_is_shared (const struct output_item *);
 struct output_item *output_item_unshare (struct output_item *);
 
 void output_item_submit (struct output_item *);
 struct output_item *output_item_unshare (struct output_item *);
 
 void output_item_submit (struct output_item *);
+void output_item_submit_children (struct output_item *);
 
 const char *output_item_get_label (const struct output_item *);
 void output_item_set_label (struct output_item *, const char *);
 void output_item_set_label_nocopy (struct output_item *, char *);
 
 const char *output_item_get_label (const struct output_item *);
 void output_item_set_label (struct output_item *, const char *);
 void output_item_set_label_nocopy (struct output_item *, char *);
+
+void output_item_set_command_name (struct output_item *, const char *);
+void output_item_set_command_name_nocopy (struct output_item *, char *);
+
+char *output_item_get_subtype (const struct output_item *);
+
+void output_item_add_spv_info (struct output_item *);
+
+void output_item_dump (const struct output_item *, int indentation);
+\f
+/* In-order traversal of a tree of output items. */
+
+struct output_iterator_node
+  {
+    const struct output_item *group;
+    size_t idx;
+  };
+
+struct output_iterator
+  {
+    const struct output_item *cur;
+    struct output_iterator_node *nodes;
+    size_t n, allocated;
+  };
+#define OUTPUT_ITERATOR_INIT(ITEM) { .cur = ITEM }
+
+/* Iteration functions. */
+void output_iterator_init (struct output_iterator *,
+                           const struct output_item *);
+void output_iterator_destroy (struct output_iterator *);
+void output_iterator_next (struct output_iterator *);
+
+/* Iteration helper macros. */
+#define OUTPUT_ITEM_FOR_EACH(ITER, ROOT) \
+  for (output_iterator_init (ITER, ROOT); (ITER)->cur; \
+       output_iterator_next (ITER))
+#define OUTPUT_ITEM_FOR_EACH_SKIP_ROOT(ITER, ROOT) \
+  for (output_iterator_init (ITER, ROOT), output_iterator_next (ITER); \
+       (ITER)->cur; output_iterator_next (ITER))
 \f
 /* OUTPUT_ITEM_CHART. */
 struct output_item *chart_item_create (struct chart *);
 \f
 \f
 /* OUTPUT_ITEM_CHART. */
 struct output_item *chart_item_create (struct chart *);
 \f
-/* OUTPUT_ITEM_GROUP_OPEN. */
-struct output_item *group_open_item_create (const char *command_name,
-                                            const char *label);
-struct output_item *group_open_item_create_nocopy (char *command_name,
-                                                   char *label);
-\f
-/* OUTPUT_ITEM_GROUP_CLOSE. */
+/* OUTPUT_ITEM_GROUP. */
+struct output_item *group_item_create (const char *command_name,
+                                       const char *label);
+struct output_item *group_item_create_nocopy (char *command_name, char *label);
 
 
-struct output_item *group_close_item_create (void);
+void group_item_add_child (struct output_item *parent,
+                           struct output_item *child);
+
+struct output_item *root_item_create (void);
+
+struct output_item *group_item_clone_empty (const struct output_item *);
 \f
 /* OUTPUT_ITEM_IMAGE. */
 
 \f
 /* OUTPUT_ITEM_IMAGE. */
 
@@ -164,5 +220,29 @@ bool text_item_append (struct output_item *dst, const struct output_item *src);
 struct output_item *text_item_to_table_item (struct output_item *);
 
 const char *text_item_subtype_to_string (enum text_item_subtype);
 struct output_item *text_item_to_table_item (struct output_item *);
 
 const char *text_item_subtype_to_string (enum text_item_subtype);
+\f
+/* An informational node for output items that were read from an .spv file.
+   This is mostly for debugging and troubleshooting purposes with the
+   pspp-output program. */
+struct spv_info
+  {
+    /* The .spv file. */
+    struct zip_reader *zip_reader;
+
+    /* True if there was an error reading the output item (e.g. because of
+       corruption or because PSPP doesn't understand the format.) */
+    bool error;
+
+    /* Zip member names.  All may be NULL. */
+    char *structure_member;
+    char *xml_member;
+    char *bin_member;
+    char *png_member;
+  };
+
+void spv_info_destroy (struct spv_info *);
+struct spv_info *spv_info_clone (const struct spv_info *);
+size_t spv_info_get_members (const struct spv_info *, const char **members,
+                             size_t allocated_members);
 
 #endif /* output/output-item.h */
 
 #endif /* output/output-item.h */
diff --git a/src/output/select.c b/src/output/select.c
new file mode 100644 (file)
index 0000000..52ab7f2
--- /dev/null
@@ -0,0 +1,359 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "output/select.h"
+
+#include <string.h>
+
+#include "libpspp/assertion.h"
+#include "libpspp/bit-vector.h"
+#include "libpspp/message.h"
+
+#include "gl/c-ctype.h"
+#include "gl/xalloc.h"
+
+const char *
+output_item_class_to_string (enum output_item_class class)
+{
+  switch (class)
+    {
+#define OUTPUT_CLASS(ENUM, NAME) case OUTPUT_CLASS_##ENUM: return NAME;
+      OUTPUT_CLASSES
+#undef OUTPUT_CLASS
+    default: return NULL;
+    }
+}
+
+enum output_item_class
+output_item_class_from_string (const char *name)
+{
+#define OUTPUT_CLASS(ENUM, NAME) \
+  if (!strcmp (name, NAME)) return OUTPUT_CLASS_##ENUM;
+  OUTPUT_CLASSES
+#undef OUTPUT_CLASS
+
+  return (enum output_item_class) OUTPUT_N_CLASSES;
+}
+
+enum output_item_class
+output_item_classify (const struct output_item *item)
+{
+  const char *label = output_item_get_label (item);
+  if (!label)
+    label = "";
+
+  switch (item->type)
+    {
+    case OUTPUT_ITEM_CHART:
+      return OUTPUT_CLASS_CHARTS;
+
+    case OUTPUT_ITEM_GROUP:
+      return OUTPUT_CLASS_OUTLINEHEADERS;
+
+    case OUTPUT_ITEM_IMAGE:
+      return OUTPUT_CLASS_OTHER;
+
+    case OUTPUT_ITEM_MESSAGE:
+      return (item->message->severity == MSG_S_NOTE
+              ? OUTPUT_CLASS_NOTES
+              : OUTPUT_CLASS_WARNINGS);
+
+    case OUTPUT_ITEM_PAGE_BREAK:
+      return OUTPUT_CLASS_OTHER;
+
+    case OUTPUT_ITEM_PAGE_SETUP:
+      return OUTPUT_CLASS_OTHER;
+
+    case OUTPUT_ITEM_TABLE:
+      return (!strcmp (label, "Warnings") ? OUTPUT_CLASS_WARNINGS
+              : !strcmp (label, "Notes") ? OUTPUT_CLASS_NOTES
+              : OUTPUT_CLASS_TABLES);
+
+    case OUTPUT_ITEM_TEXT:
+      return (!strcmp (label, "Title") ? OUTPUT_CLASS_HEADINGS
+              : !strcmp (label, "Log") ? OUTPUT_CLASS_LOGS
+              : !strcmp (label, "Page Title") ? OUTPUT_CLASS_PAGETITLE
+              : OUTPUT_CLASS_TEXTS);
+
+    default:
+      return OUTPUT_CLASS_UNKNOWN;
+    }
+}
+\f
+static bool
+string_matches (const char *pattern, const char *s)
+{
+  /* XXX This should be a Unicode case insensitive comparison. */
+  while (c_tolower (*pattern) == c_tolower (*s))
+    {
+      if (*pattern == '\0')
+        return true;
+
+      pattern++;
+      s++;
+    }
+
+  return pattern[0] == '*' && pattern[1] == '\0';
+}
+
+static int
+string_array_matches (const char *name, const struct string_array *array)
+{
+  if (!array->n)
+    return -1;
+  else if (!name)
+    return false;
+
+  for (size_t i = 0; i < array->n; i++)
+    if (string_matches (array->strings[i], name))
+      return true;
+
+  return false;
+}
+
+static bool
+match (const char *name,
+       const struct string_array *white,
+       const struct string_array *black)
+{
+  return (string_array_matches (name, white) != false
+          && string_array_matches (name, black) != true);
+}
+
+static int
+match_instance (const int *instances, size_t n_instances,
+                int instance_within_command)
+{
+  int retval = false;
+  for (size_t i = 0; i < n_instances; i++)
+    {
+      if (instances[i] == instance_within_command)
+        return true;
+      else if (instances[i] == -1)
+        retval = -1;
+    }
+  return retval;
+}
+
+static bool
+match_command (size_t nth_command, size_t *commands, size_t n_commands)
+{
+  for (size_t i = 0; i < n_commands; i++)
+    if (nth_command == commands[i])
+      return true;
+  return false;
+}
+
+static void
+select_matches (const struct output_item **items,
+                unsigned int *depths, size_t n_items,
+                const struct output_criteria *c, unsigned long int *include)
+{
+  /* Counting instances within a command. */
+  int instance_within_command = 0;
+  int last_instance = -1;
+
+  /* Counting commands. */
+  const struct output_item *command_item = NULL;
+  const struct output_item *command_command_item = NULL;
+  size_t nth_command = 0;
+
+  for (size_t i = 0; i < n_items; i++)
+    {
+      const struct output_item *item = items[i];
+
+      if (depths[i] == 0)
+        {
+          command_item = item;
+          if (last_instance >= 0)
+            {
+              bitvector_set1 (include, last_instance);
+              last_instance = -1;
+            }
+          instance_within_command = 0;
+        }
+
+      if (!((1u << output_item_classify (item)) & c->classes))
+        continue;
+
+      if (!c->include_hidden && item->type != OUTPUT_ITEM_GROUP && !item->show)
+        continue;
+
+      if (c->error && (!item->spv_info || !item->spv_info->error))
+        continue;
+
+      if (!match (item->command_name,
+                  &c->include.commands, &c->exclude.commands))
+        continue;
+
+      if (c->n_commands)
+        {
+          if (command_item != command_command_item)
+            {
+              command_command_item = command_item;
+              nth_command++;
+            }
+
+          if (!match_command (nth_command, c->commands, c->n_commands))
+            continue;
+        }
+
+      char *subtype = output_item_get_subtype (item);
+      bool match_subtype = match (subtype,
+                                  &c->include.subtypes, &c->exclude.subtypes);
+      if (!match_subtype)
+        continue;
+
+      if (!match (output_item_get_label (item),
+                  &c->include.labels, &c->exclude.labels))
+        continue;
+
+      if (c->members.n)
+        {
+          const char *members[4];
+          size_t n = spv_info_get_members (item->spv_info, members,
+                                           sizeof members / sizeof *members);
+
+          bool found = false;
+          for (size_t j = 0; j < n; j++)
+            if (string_array_matches (members[j], &c->members) == true)
+              {
+                found = true;
+                break;
+              }
+          if (!found)
+            continue;
+        }
+
+      if (c->n_instances)
+        {
+          if (!depths[i])
+            continue;
+          instance_within_command++;
+
+          int include_instance = match_instance (c->instances, c->n_instances,
+                                                 instance_within_command);
+          if (!include_instance)
+            continue;
+          else if (include_instance < 0)
+            {
+              last_instance = i;
+              continue;
+            }
+        }
+
+      bitvector_set1 (include, i);
+    }
+
+  if (last_instance >= 0)
+    bitvector_set1 (include, last_instance);
+}
+
+static size_t
+count_items (const struct output_item *item)
+{
+  size_t n = 1;
+  if (item->type == OUTPUT_ITEM_GROUP)
+    for (size_t i = 0; i < item->group.n_children; i++)
+      n += count_items (item->group.children[i]);
+  return n;
+}
+
+static size_t
+flatten_items (const struct output_item *item, size_t index, size_t depth,
+               const struct output_item **items, unsigned int *depths)
+{
+  items[index] = item;
+  depths[index] = depth;
+  index++;
+
+  if (item->type == OUTPUT_ITEM_GROUP)
+    for (size_t i = 0; i < item->group.n_children; i++)
+      index = flatten_items (item->group.children[i], index, depth + 1,
+                             items, depths);
+
+  return index;
+}
+
+static size_t
+unflatten_items (const struct output_item *in, size_t index,
+                 unsigned long *include, struct output_item *out)
+{
+  bool include_item = bitvector_is_set (include, index++);
+  if (in->type == OUTPUT_ITEM_GROUP)
+    {
+      /* If we should include the group itself, then clone IN inside OUT, and
+         add any children to the clone instead to OUT directly. */
+      if (include_item)
+        {
+          struct output_item *group = group_item_clone_empty (in);
+          group_item_add_child (out, group);
+          out = group;
+        }
+
+      for (size_t i = 0; i < in->group.n_children; i++)
+        index = unflatten_items (in->group.children[i], index, include, out);
+    }
+  else
+    {
+      if (include_item)
+        group_item_add_child (out, output_item_ref (in));
+    }
+  return index;
+}
+
+/* Consumes IN, which must be a group, and returns a new output item whose
+   children are all the (direct and indirect) children of IN that meet the NC
+   criteria in C[]. */
+struct output_item *
+output_select (struct output_item *in,
+               const struct output_criteria c[], size_t nc)
+{
+  assert (in->type == OUTPUT_ITEM_GROUP);
+  if (!nc)
+    return in;
+
+  /* Number of items (not counting the root). */
+  size_t n_items = count_items (in) - 1;
+
+  const struct output_item **items = xnmalloc (n_items, sizeof *items);
+  unsigned int *depths = xnmalloc (n_items, sizeof *depths);
+  size_t n_flattened = 0;
+  for (size_t i = 0; i < in->group.n_children; i++)
+    n_flattened = flatten_items (in->group.children[i], n_flattened,
+                                 0, items, depths);
+  assert (n_flattened == n_items);
+
+  unsigned long int *include = bitvector_allocate (n_items);
+  for (size_t i = 0; i < nc; i++)
+    select_matches (items, depths, n_items, &c[i], include);
+
+  struct output_item *out = root_item_create ();
+  size_t n_unflattened = 0;
+  for (size_t i = 0; i < in->group.n_children; i++)
+    n_unflattened = unflatten_items (in->group.children[i], n_unflattened,
+                                     include, out);
+  assert (n_unflattened == n_items);
+
+  free (items);
+  free (depths);
+  free (include);
+
+  output_item_unref (in);
+  return out;
+}
diff --git a/src/output/select.h b/src/output/select.h
new file mode 100644 (file)
index 0000000..7759173
--- /dev/null
@@ -0,0 +1,114 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2009, 2011 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef OUTPUT_SELECT_H
+#define OUTPUT_SELECT_H 1
+
+#include "output/output-item.h"
+
+/* Selecting subsets of a tree of output items based on user-specified
+   criteria.  pspp-output uses this; a future OMS or OUTPUT MODIFY command
+   would use it too. */
+
+/* Classifications for output items.  These only roughly correspond to the
+   output item types; for example, "warnings" are a subset of text items.
+   These classifications really  */
+#define OUTPUT_CLASSES                                \
+    OUTPUT_CLASS(CHARTS, "charts")                    \
+    OUTPUT_CLASS(HEADINGS, "headings")                \
+    OUTPUT_CLASS(LOGS, "logs")                        \
+    OUTPUT_CLASS(MODELS, "models")                    \
+    OUTPUT_CLASS(TABLES, "tables")                    \
+    OUTPUT_CLASS(TEXTS, "texts")                      \
+    OUTPUT_CLASS(TREES, "trees")                      \
+    OUTPUT_CLASS(WARNINGS, "warnings")                \
+    OUTPUT_CLASS(OUTLINEHEADERS, "outlineheaders")    \
+    OUTPUT_CLASS(PAGETITLE, "pagetitle")              \
+    OUTPUT_CLASS(NOTES, "notes")                      \
+    OUTPUT_CLASS(UNKNOWN, "unknown")                  \
+    OUTPUT_CLASS(OTHER, "other")
+enum output_item_class
+  {
+#define OUTPUT_CLASS(ENUM, NAME) OUTPUT_CLASS_##ENUM,
+    OUTPUT_CLASSES
+#undef OUTPUT_CLASS
+  };
+enum
+  {
+#define OUTPUT_CLASS(ENUM, NAME) +1
+    OUTPUT_N_CLASSES = OUTPUT_CLASSES
+#undef OUTPUT_CLASS
+};
+#define OUTPUT_ALL_CLASSES ((1u << OUTPUT_N_CLASSES) - 1)
+
+const char *output_item_class_to_string (enum output_item_class);
+enum output_item_class output_item_class_from_string (const char *);
+
+enum output_item_class output_item_classify (const struct output_item *);
+
+/* Matching criteria for commands, subtypes, and labels.
+
+   Each of the members is an array of strings.  A string that ends in '*'
+   matches anything that begins with the rest of the string, otherwise a string
+   requires an exact (case-insensitive) match. */
+struct output_criteria_match
+  {
+    struct string_array commands;
+    struct string_array subtypes;
+    struct string_array labels;
+  };
+
+struct output_criteria
+  {
+    /* Include objects that are not visible? */
+    bool include_hidden;
+
+    /* If false, include all objects.
+       If true, include only objects that have an error on loading. */
+    bool error;
+
+    /* Bit-mask of OUTPUT_CLASS_* for the classes to include. */
+    unsigned int classes;
+
+    /* Include all of the objects that match 'include' and do not match
+       'exclude', except that objects are included by default if 'include' is
+       empty. */
+    struct output_criteria_match include;
+    struct output_criteria_match exclude;
+
+    /* Include objects under commands with indexes listed in COMMANDS.  Indexes
+       are 1-based.  Everything is included if N_COMMANDS is 0. */
+    size_t *commands;
+    size_t n_commands;
+
+    /* Include XML and binary member names that match (except that everything
+       is included by default if empty). */
+    struct string_array members;
+
+    /* Include the objects with indexes listed in INSTANCES within each of the
+       commands that are included.  Indexes are 1-based.  Index -1 means the
+       last object within a command. */
+    int *instances;
+    size_t n_instances;
+  };
+
+#define OUTPUT_CRITERIA_INITIALIZER { .classes = OUTPUT_ALL_CLASSES }
+
+struct output_item *output_select (struct output_item *,
+                                   const struct output_criteria[],
+                                   size_t n_criteria);
+
+#endif /* output/select.h */
index f0c00e5fa84ad615bcd3828cc20817d528b56684..36db324ee79db9e1e4c6baa6a0cdf981179094ea 100644 (file)
@@ -100,8 +100,9 @@ struct output_driver_factory spv_driver_factory =
 
 static const struct output_driver_class spv_driver_class =
   {
 
 static const struct output_driver_class spv_driver_class =
   {
-    "spv",
-    spv_destroy,
-    spv_submit,
-    NULL,
+    .name = "spv",
+    .destroy = spv_destroy,
+    .submit = spv_submit,
+    .handles_show = true,
+    .handles_groups = true,
   };
   };
index 01f8a334a9d58e72f10301c5ace53463a377324b..c42b7920114b8827f827eab61445d89016aec6ca 100644 (file)
 src_output_liboutput_la_SOURCES += \
        src/output/spv/spv-css-parser.c \
        src/output/spv/spv-css-parser.h \
 src_output_liboutput_la_SOURCES += \
        src/output/spv/spv-css-parser.c \
        src/output/spv/spv-css-parser.h \
-       src/output/spv/spv-dump.c \
        src/output/spv/spv-legacy-data.c \
        src/output/spv/spv-legacy-data.h \
        src/output/spv/spv-legacy-decoder.c \
        src/output/spv/spv-legacy-decoder.h \
        src/output/spv/spv-light-decoder.c \
        src/output/spv/spv-light-decoder.h \
        src/output/spv/spv-legacy-data.c \
        src/output/spv/spv-legacy-data.h \
        src/output/spv/spv-legacy-decoder.c \
        src/output/spv/spv-legacy-decoder.h \
        src/output/spv/spv-light-decoder.c \
        src/output/spv/spv-light-decoder.h \
-       src/output/spv/spv-output.c \
-       src/output/spv/spv-output.h \
-       src/output/spv/spv-select.c \
-       src/output/spv/spv-select.h \
        src/output/spv/spv-table-look.c \
        src/output/spv/spv-table-look.h \
        src/output/spv/spv-writer.c \
        src/output/spv/spv-table-look.c \
        src/output/spv/spv-table-look.h \
        src/output/spv/spv-writer.c \
diff --git a/src/output/spv/spv-dump.c b/src/output/spv/spv-dump.c
deleted file mode 100644 (file)
index db8a60d..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/* 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
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation, either version 3 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
-
-#include <config.h>
-
-#include "output/spv/spv.h"
-
-#include <inttypes.h>
-#include <stdlib.h>
-
-#include "data/settings.h"
-#include "output/pivot-table.h"
-
-#include "gl/xalloc.h"
-
-static void
-indent (int indentation)
-{
-  for (int i = 0; i < indentation * 2; i++)
-    putchar (' ');
-}
-
-void
-spv_item_dump (const struct spv_item *item, int indentation)
-{
-  indent (indentation);
-  if (item->label)
-    printf ("\"%s\" ", item->label);
-  if (!item->visible)
-    printf ("(hidden) ");
-
-  switch (item->type)
-    {
-    case SPV_ITEM_HEADING:
-      printf ("heading\n");
-      for (size_t i = 0; i < item->n_children; i++)
-        spv_item_dump (item->children[i], indentation + 1);
-      break;
-
-    case SPV_ITEM_TEXT:
-      printf ("text \"%s\"\n", pivot_value_to_string_defaults (item->text));
-      break;
-
-    case SPV_ITEM_TABLE:
-      if (item->table)
-        pivot_table_dump (item->table, indentation + 1);
-      else
-        {
-          printf ("unloaded table in %s", item->bin_member);
-          if (item->xml_member)
-            printf (" and %s", item->xml_member);
-          putchar ('\n');
-        }
-      break;
-
-    case SPV_ITEM_GRAPH:
-      printf ("graph\n");
-      break;
-
-    case SPV_ITEM_MODEL:
-      printf ("model\n");
-      break;
-
-    case SPV_ITEM_IMAGE:
-      printf ("image in %s\n", item->png_member);
-      break;
-
-    case SPV_ITEM_TREE:
-      printf ("tree\n");
-      break;
-    }
-}
index 4e3d72c4a4c1832af4a66d6de64497407d6db1d2..59c9331e9f07161041f147ed4119a60db6c7c089 100644 (file)
@@ -334,7 +334,7 @@ char * WARN_UNUSED_RESULT
 spv_legacy_data_decode (const uint8_t *in, size_t size, struct spv_data *out)
 {
   char *error = NULL;
 spv_legacy_data_decode (const uint8_t *in, size_t size, struct spv_data *out)
 {
   char *error = NULL;
-  memset (out, 0, sizeof *out);
+  *out = (struct spv_data) SPV_DATA_INITIALIZER;
 
   struct spvbin_input input;
   spvbin_input_init (&input, in, size);
 
   struct spvbin_input input;
   spvbin_input_init (&input, in, size);
@@ -394,7 +394,7 @@ spv_legacy_data_decode (const uint8_t *in, size_t size, struct spv_data *out)
 
 error:
   spv_data_uninit (out);
 
 error:
   spv_data_uninit (out);
-  memset (out, 0, sizeof *out);
+  *out = (struct spv_data) SPV_DATA_INITIALIZER;
   spvob_free_legacy_binary (lb);
   return error;
 }
   spvob_free_legacy_binary (lb);
   return error;
 }
index 1e8fa2d151a510e7492db36a6fa200318af47258..3323123e7e4cea5de5cdb43eb1bc1580b28aa70b 100644 (file)
@@ -33,6 +33,8 @@ struct spv_data
     size_t n_sources;
   };
 
     size_t n_sources;
   };
 
+#define SPV_DATA_INITIALIZER { NULL, 0 }
+
 void spv_data_uninit (struct spv_data *);
 void spv_data_dump (const struct spv_data *, FILE *);
 
 void spv_data_uninit (struct spv_data *);
 void spv_data_dump (const struct spv_data *, FILE *);
 
diff --git a/src/output/spv/spv-output.c b/src/output/spv/spv-output.c
deleted file mode 100644 (file)
index 8006f85..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/* PSPP - a program for statistical analysis.
-   Copyright (C) 2018 Free Software Foundation, Inc.
-
-   This program is free software: you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation, either version 3 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
-
-#include <config.h>
-
-#include "output/spv/spv-output.h"
-
-#include "output/pivot-table.h"
-#include "output/spv/spv.h"
-#include "output/output-item.h"
-
-#include "gl/xalloc.h"
-
-void
-spv_text_submit (const struct spv_item *in)
-{
-  enum spv_item_class class = spv_item_get_class (in);
-  struct output_item *item = text_item_create_value (
-    (class == SPV_CLASS_HEADINGS ? TEXT_ITEM_TITLE
-     : class == SPV_CLASS_PAGETITLE ? TEXT_ITEM_PAGE_TITLE
-     : TEXT_ITEM_LOG),
-    pivot_value_clone (spv_item_get_text (in)),
-    xstrdup_if_nonnull (in->label));
-  output_item_submit (item);
-}
diff --git a/src/output/spv/spv-output.h b/src/output/spv/spv-output.h
deleted file mode 100644 (file)
index b100c0c..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-/* PSPP - a program for statistical analysis.
-   Copyright (C) 2018 Free Software Foundation, Inc.
-
-   This program is free software: you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation, either version 3 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef OUTPUT_SPV_OUTPUT_H
-#define OUTPUT_SPV_OUTPUT_H 1
-
-/* Interface between SPVs and the PSPP output engine. */
-
-struct spv_item;
-
-void spv_text_submit (const struct spv_item *);
-
-#endif /* output/spv/spv-output.h */
diff --git a/src/output/spv/spv-select.c b/src/output/spv/spv-select.c
deleted file mode 100644 (file)
index 10f35a1..0000000
+++ /dev/null
@@ -1,262 +0,0 @@
-/* PSPP - a program for statistical analysis.
-   Copyright (C) 2018 Free Software Foundation, Inc.
-
-   This program is free software: you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation, either version 3 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
-
-#include <config.h>
-
-#include "spv-select.h"
-
-#include <string.h>
-
-#include "libpspp/assertion.h"
-#include "libpspp/bit-vector.h"
-#include "output/spv/spv.h"
-
-#include "gl/c-ctype.h"
-#include "gl/xalloc.h"
-
-/* Returns true if ITEM represents a command, false otherwise.
-
-   The root item and each of its immediate children are considered to be
-   command items. */
-static bool
-is_command_item (const struct spv_item *item)
-{
-  return !item->parent || !item->parent->parent;
-}
-
-static struct spv_item *
-find_command_item (struct spv_item *item)
-{
-  while (!is_command_item (item))
-    item = item->parent;
-  return item;
-}
-
-static bool
-string_matches (const char *pattern, const char *s)
-{
-  /* XXX This should be a Unicode case insensitive comparison. */
-  while (c_tolower (*pattern) == c_tolower (*s))
-    {
-      if (*pattern == '\0')
-        return true;
-
-      pattern++;
-      s++;
-    }
-
-  return pattern[0] == '*' && pattern[1] == '\0';
-}
-
-static int
-string_array_matches (const char *name, const struct string_array *array)
-{
-  if (!array->n)
-    return -1;
-  else if (!name)
-    return false;
-
-  for (size_t i = 0; i < array->n; i++)
-    if (string_matches (array->strings[i], name))
-      return true;
-
-  return false;
-}
-
-static bool
-match (const char *name,
-       const struct string_array *white,
-       const struct string_array *black)
-{
-  return (string_array_matches (name, white) != false
-          && string_array_matches (name, black) != true);
-}
-
-static int
-match_instance (const int *instances, size_t n_instances,
-                int instance_within_command)
-{
-  int retval = false;
-  for (size_t i = 0; i < n_instances; i++)
-    {
-      if (instances[i] == instance_within_command)
-        return true;
-      else if (instances[i] == -1)
-        retval = -1;
-    }
-  return retval;
-}
-
-static bool
-match_command (size_t nth_command, size_t *commands, size_t n_commands)
-{
-  for (size_t i = 0; i < n_commands; i++)
-    if (nth_command == commands[i])
-      return true;
-  return false;
-}
-
-static void
-select_matches (const struct spv_reader *spv, const struct spv_criteria *c,
-                unsigned long int *include)
-{
-  /* Counting instances within a command. */
-  struct spv_item *instance_command_item = NULL;
-  int instance_within_command = 0;
-  int last_instance = -1;
-
-  /* Counting commands. */
-  struct spv_item *command_command_item = NULL;
-  size_t nth_command = 0;
-
-  struct spv_item *item;
-  ssize_t index = -1;
-  SPV_ITEM_FOR_EACH_SKIP_ROOT (item, spv_get_root (spv))
-    {
-      index++;
-
-      struct spv_item *new_command_item = find_command_item (item);
-      if (new_command_item != instance_command_item)
-        {
-          if (last_instance >= 0)
-            {
-              bitvector_set1 (include, last_instance);
-              last_instance = -1;
-            }
-
-          instance_command_item = new_command_item;
-          instance_within_command = 0;
-        }
-
-      if (!((1u << spv_item_get_class (item)) & c->classes))
-        continue;
-
-      if (!c->include_hidden && !spv_item_is_visible (item))
-        continue;
-
-      if (c->error)
-        {
-          spv_item_load (item);
-          if (!item->error)
-            continue;
-        }
-
-      if (!match (spv_item_get_command_id (item),
-                  &c->include.commands, &c->exclude.commands))
-        continue;
-
-      if (c->n_commands)
-        {
-          if (new_command_item != command_command_item)
-            {
-              command_command_item = new_command_item;
-              nth_command++;
-            }
-
-          if (!match_command (nth_command, c->commands, c->n_commands))
-            continue;
-        }
-
-      if (!match (spv_item_get_subtype (item),
-                  &c->include.subtypes, &c->exclude.subtypes))
-        continue;
-
-      if (!match (spv_item_get_label (item),
-                  &c->include.labels, &c->exclude.labels))
-        continue;
-
-      if (c->members.n)
-        {
-          char *members[] = {
-            item->structure_member,
-            item->xml_member,
-            item->bin_member,
-            item->png_member
-          };
-
-          bool found = false;
-          for (size_t i = 0; i < sizeof members / sizeof *members; i++)
-            if (string_array_matches (members[i], &c->members) == true)
-              {
-                found = true;
-                break;
-              }
-          if (!found)
-            continue;
-        }
-
-      if (c->n_instances)
-        {
-          if (is_command_item (item))
-            continue;
-          instance_within_command++;
-
-          int include_instance = match_instance (c->instances, c->n_instances,
-                                                 instance_within_command);
-          if (!include_instance)
-            continue;
-          else if (include_instance < 0)
-            {
-              last_instance = index;
-              continue;
-            }
-        }
-
-      bitvector_set1 (include, index);
-    }
-
-  if (last_instance >= 0)
-    bitvector_set1 (include, last_instance);
-}
-
-void
-spv_select (const struct spv_reader *spv,
-            const struct spv_criteria c[], size_t nc,
-            struct spv_item ***itemsp, size_t *n_itemsp)
-{
-  struct spv_item *item;
-
-  struct spv_criteria default_criteria = SPV_CRITERIA_INITIALIZER;
-  if (!nc)
-    {
-      nc = 1;
-      c = &default_criteria;
-    }
-
-  /* Count items. */
-  size_t max_items = 0;
-  SPV_ITEM_FOR_EACH_SKIP_ROOT (item, spv_get_root (spv))
-    max_items++;
-
-  /* Allocate bitmap for items then fill it in with selected items. */
-  unsigned long int *include = bitvector_allocate (max_items);
-  for (size_t i = 0; i < nc; i++)
-    select_matches (spv, &c[i], include);
-
-  /* Copy selected items into output array. */
-  size_t n_items = 0;
-  struct spv_item **items = xnmalloc (bitvector_count (include, max_items),
-                                      sizeof *items);
-  size_t i = 0;
-  SPV_ITEM_FOR_EACH_SKIP_ROOT (item, spv_get_root (spv))
-    if (bitvector_is_set (include, i++))
-      items[n_items++] = item;
-  *itemsp = items;
-  *n_itemsp = n_items;
-
-  /* Free memory. */
-  free (include);
-}
diff --git a/src/output/spv/spv-select.h b/src/output/spv/spv-select.h
deleted file mode 100644 (file)
index b2a1e0e..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/* PSPP - a program for statistical analysis.
-   Copyright (C) 2019 Free Software Foundation, Inc.
-
-   This program is free software: you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation, either version 3 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef OUTPUT_SPV_SELECT_H
-#define OUTPUT_SPV_SELECT_H 1
-
-#include "libpspp/string-array.h"
-
-struct spv_item;
-struct spv_reader;
-
-/* Matching criteria for commands, subtypes, and labels.
-
-   Each of the members is an array of strings.  A string that ends in '*'
-   matches anything that begins with the rest of the string, otherwise a string
-   requires an exact (case-insensitive) match. */
-struct spv_criteria_match
-  {
-    struct string_array commands;
-    struct string_array subtypes;
-    struct string_array labels;
-  };
-
-struct spv_criteria
-  {
-    /* Include objects that are not visible? */
-    bool include_hidden;
-
-    /* If false, include all objects.
-       If true, include only objects that have an error on loading. */
-    bool error;
-
-    /* Bit-mask of SPV_CLASS_* for the classes to include. */
-    unsigned int classes;
-
-    /* Include all of the objects that match 'include' and do not match
-       'exclude', except that objects are included by default if 'include' is
-       empty. */
-    struct spv_criteria_match include;
-    struct spv_criteria_match exclude;
-
-    /* Include objects under commands with indexes listed in COMMANDS.  Indexes
-       are 1-based.  Everything is included if N_COMMANDS is 0. */
-    size_t *commands;
-    size_t n_commands;
-
-    /* Include XML and binary member names that match (except that everything
-       is included by default if empty). */
-    struct string_array members;
-
-    /* Include the objects with indexes listed in INSTANCES within each of the
-       commands that are included.  Indexes are 1-based.  Index -1 means the
-       last object within a command. */
-    int *instances;
-    size_t n_instances;
-  };
-
-#define SPV_CRITERIA_INITIALIZER { .classes = SPV_ALL_CLASSES }
-
-void spv_select (const struct spv_reader *,
-                 const struct spv_criteria[], size_t n_criteria,
-                 struct spv_item ***items, size_t *n_items);
-
-#endif /* output/spv/spv-select.h */
index 70b455d4567a67e0de3ddf17f12eff0e34d8e9ea..64bb365c614f48d1f13f4cffaa949bd7198468f5 100644 (file)
@@ -1106,11 +1106,10 @@ spv_writer_write (struct spv_writer *w, const struct output_item *item)
       }
       break;
 
       }
       break;
 
-    case OUTPUT_ITEM_GROUP_OPEN:
+    case OUTPUT_ITEM_GROUP:
       spv_writer_open_heading (w, item);
       spv_writer_open_heading (w, item);
-      break;
-
-    case OUTPUT_ITEM_GROUP_CLOSE:
+      for (size_t i = 0; i < item->group.n_children; i++)
+        spv_writer_write (w, item->group.children[i]);
       spv_writer_close_heading (w);
       break;
 
       spv_writer_close_heading (w);
       break;
 
index fc058281eccd633f1e1824cd78c12a728de8fb51..f37dff17f2173a455427afc6d3557ce715ba6e5b 100644 (file)
@@ -19,6 +19,7 @@
 #include "output/spv/spv.h"
 
 #include <assert.h>
 #include "output/spv/spv.h"
 
 #include <assert.h>
+#include <cairo.h>
 #include <inttypes.h>
 #include <libxml/HTMLparser.h>
 #include <libxml/xmlreader.h>
 #include <inttypes.h>
 #include <libxml/HTMLparser.h>
 #include <libxml/xmlreader.h>
@@ -31,6 +32,7 @@
 #include "libpspp/message.h"
 #include "libpspp/str.h"
 #include "libpspp/zip-reader.h"
 #include "libpspp/message.h"
 #include "libpspp/str.h"
 #include "libpspp/zip-reader.h"
+#include "output/output-item.h"
 #include "output/page-setup.h"
 #include "output/pivot-table.h"
 #include "output/spv/detail-xml-parser.h"
 #include "output/page-setup.h"
 #include "output/pivot-table.h"
 #include "output/spv/detail-xml-parser.h"
@@ -60,281 +62,6 @@ struct spv_reader
     struct page_setup *page_setup;
   };
 
     struct page_setup *page_setup;
   };
 
-const struct page_setup *
-spv_get_page_setup (const struct spv_reader *spv)
-{
-  return spv->page_setup;
-}
-
-const char *
-spv_item_type_to_string (enum spv_item_type type)
-{
-  switch (type)
-    {
-    case SPV_ITEM_HEADING: return "heading";
-    case SPV_ITEM_TEXT: return "text";
-    case SPV_ITEM_TABLE: return "table";
-    case SPV_ITEM_GRAPH: return "graph";
-    case SPV_ITEM_MODEL: return "model";
-    case SPV_ITEM_IMAGE: return "image";
-    default: return "**error**";
-    }
-}
-
-const char *
-spv_item_class_to_string (enum spv_item_class class)
-{
-  switch (class)
-    {
-#define SPV_CLASS(ENUM, NAME) case SPV_CLASS_##ENUM: return NAME;
-      SPV_CLASSES
-#undef SPV_CLASS
-    default: return NULL;
-    }
-}
-
-enum spv_item_class
-spv_item_class_from_string (const char *name)
-{
-#define SPV_CLASS(ENUM, NAME) \
-  if (!strcmp (name, NAME)) return SPV_CLASS_##ENUM;
-  SPV_CLASSES
-#undef SPV_CLASS
-
-  return (enum spv_item_class) SPV_N_CLASSES;
-}
-
-enum spv_item_type
-spv_item_get_type (const struct spv_item *item)
-{
-  return item->type;
-}
-
-enum spv_item_class
-spv_item_get_class (const struct spv_item *item)
-{
-  const char *label = spv_item_get_label (item);
-  if (!label)
-    label = "";
-
-  switch (item->type)
-    {
-    case SPV_ITEM_HEADING:
-      return SPV_CLASS_HEADINGS;
-
-    case SPV_ITEM_TEXT:
-      return (!strcmp (label, "Title") ? SPV_CLASS_OUTLINEHEADERS
-              : !strcmp (label, "Log") ? SPV_CLASS_LOGS
-              : !strcmp (label, "Page Title") ? SPV_CLASS_PAGETITLE
-              : SPV_CLASS_TEXTS);
-
-    case SPV_ITEM_TABLE:
-      return (!strcmp (label, "Warnings") ? SPV_CLASS_WARNINGS
-              : !strcmp (label, "Notes") ? SPV_CLASS_NOTES
-              : SPV_CLASS_TABLES);
-
-    case SPV_ITEM_GRAPH:
-      return SPV_CLASS_CHARTS;
-
-    case SPV_ITEM_MODEL:
-      return SPV_CLASS_MODELS;
-
-    case SPV_ITEM_IMAGE:
-      return SPV_CLASS_OTHER;
-
-    case SPV_ITEM_TREE:
-      return SPV_CLASS_TREES;
-
-    default:
-      return SPV_CLASS_UNKNOWN;
-    }
-}
-
-const char *
-spv_item_get_label (const struct spv_item *item)
-{
-  return item->label;
-}
-
-bool
-spv_item_is_heading (const struct spv_item *item)
-{
-  return item->type == SPV_ITEM_HEADING;
-}
-
-size_t
-spv_item_get_n_children (const struct spv_item *item)
-{
-  return item->n_children;
-}
-
-struct spv_item *
-spv_item_get_child (const struct spv_item *item, size_t idx)
-{
-  assert (idx < item->n_children);
-  return item->children[idx];
-}
-
-bool
-spv_item_is_table (const struct spv_item *item)
-{
-  return item->type == SPV_ITEM_TABLE;
-}
-
-bool
-spv_item_is_text (const struct spv_item *item)
-{
-  return item->type == SPV_ITEM_TEXT;
-}
-
-const struct pivot_value *
-spv_item_get_text (const struct spv_item *item)
-{
-  assert (spv_item_is_text (item));
-  return item->text;
-}
-
-bool
-spv_item_is_image (const struct spv_item *item)
-{
-  return item->type == SPV_ITEM_IMAGE;
-}
-
-static cairo_status_t
-read_from_zip_member (void *zm_, unsigned char *data, unsigned int length)
-{
-  struct zip_member *zm = zm_;
-  if (!zm)
-    return CAIRO_STATUS_READ_ERROR;
-
-  while (length > 0)
-    {
-      int n = zip_member_read (zm, data, length);
-      if (n <= 0)
-        return CAIRO_STATUS_READ_ERROR;
-
-      data += n;
-      length -= n;
-    }
-
-  return CAIRO_STATUS_SUCCESS;
-}
-
-cairo_surface_t *
-spv_item_get_image (const struct spv_item *item_)
-{
-  struct spv_item *item = CONST_CAST (struct spv_item *, item_);
-  assert (spv_item_is_image (item));
-
-  if (!item->image)
-    {
-      struct zip_member *zm;
-      char *error = zip_member_open (item->spv->zip, item->png_member, &zm);
-      item->image = cairo_image_surface_create_from_png_stream (
-        read_from_zip_member, zm);
-      zip_member_finish (zm);
-      free (error);
-    }
-
-  return item->image;
-}
-
-struct spv_item *
-spv_item_next (const struct spv_item *item)
-{
-  if (item->n_children)
-    return item->children[0];
-
-  while (item->parent)
-    {
-      size_t idx = item->parent_idx + 1;
-      item = item->parent;
-      if (idx < item->n_children)
-        return item->children[idx];
-    }
-
-  return NULL;
-}
-
-const struct spv_item *
-spv_item_get_parent (const struct spv_item *item)
-{
-  return item->parent;
-}
-
-size_t
-spv_item_get_level (const struct spv_item *item)
-{
-  int level = 0;
-  for (; item->parent; item = item->parent)
-    level++;
-  return level;
-}
-
-const char *
-spv_item_get_command_id (const struct spv_item *item)
-{
-  return item->command_id;
-}
-
-const char *
-spv_item_get_subtype (const struct spv_item *item)
-{
-  return item->subtype;
-}
-
-bool
-spv_item_is_visible (const struct spv_item *item)
-{
-  return item->visible;
-}
-
-static void
-spv_item_destroy (struct spv_item *item)
-{
-  if (item)
-    {
-      free (item->structure_member);
-
-      free (item->label);
-      free (item->command_id);
-
-      for (size_t i = 0; i < item->n_children; i++)
-        spv_item_destroy (item->children[i]);
-      free (item->children);
-
-      pivot_table_unref (item->table);
-      pivot_table_look_unref (item->table_look);
-      free (item->bin_member);
-      free (item->xml_member);
-      free (item->subtype);
-
-      pivot_value_destroy (item->text);
-
-      free (item->png_member);
-      if (item->image)
-        cairo_surface_destroy (item->image);
-
-      free (item);
-    }
-}
-
-static void
-spv_heading_add_child (struct spv_item *parent, struct spv_item *child)
-{
-  assert (parent->type == SPV_ITEM_HEADING);
-  assert (!child->parent);
-
-  child->parent = parent;
-  child->parent_idx = parent->n_children;
-
-  if (parent->n_children >= parent->allocated_children)
-    parent->children = x2nrealloc (parent->children,
-                                   &parent->allocated_children,
-                                   sizeof *parent->children);
-  parent->children[parent->n_children++] = child;
-}
-
 static xmlNode *
 find_xml_child_element (xmlNode *parent, const char *child_name)
 {
 static xmlNode *
 find_xml_child_element (xmlNode *parent, const char *child_name)
 {
@@ -550,24 +277,27 @@ decode_embedded_html (const xmlNode *node, struct font_style *font_style)
   return ds_steal_cstr (&markup);
 }
 
   return ds_steal_cstr (&markup);
 }
 
-static char *
-xstrdup_if_nonempty (const char *s)
-{
-  return s && s[0] ? xstrdup (s) : NULL;
-}
+static struct output_item *
+decode_container_text (const struct spvsx_container_text *ct)
+{
+  struct font_style *font_style = xmalloc (sizeof *font_style);
+  char *text = decode_embedded_html (ct->html->node_.raw, font_style);
+  struct pivot_value *value = xmalloc (sizeof *value);
+  *value = (struct pivot_value) {
+    .font_style = font_style,
+    .type = PIVOT_VALUE_TEXT,
+    .text = {
+      .local = text,
+      .c = text,
+      .id = text,
+      .user_provided = true,
+    },
+  };
 
 
-static void
-decode_container_text (const struct spvsx_container_text *ct,
-                       struct spv_item *item)
-{
-  item->type = SPV_ITEM_TEXT;
-  item->command_id = xstrdup_if_nonempty (ct->command_name);
-
-  item->text = xzalloc (sizeof *item->text);
-  item->text->type = PIVOT_VALUE_TEXT;
-  item->text->font_style = xmalloc (sizeof *item->text->font_style);
-  item->text->text.local = decode_embedded_html (ct->html->node_.raw,
-                                                 item->text->font_style);
+  struct output_item *item = text_item_create_value (TEXT_ITEM_LOG,
+                                                     value, NULL);
+  output_item_set_command_name (item, ct->command_name);
+  return item;
 }
 
 static void
 }
 
 static void
@@ -613,40 +343,15 @@ decode_page_paragraph (const struct spvsx_page_paragraph *page_paragraph,
   xmlFreeDoc (html_doc);
 }
 
   xmlFreeDoc (html_doc);
 }
 
-void
-spv_item_load (const struct spv_item *item)
-{
-  if (spv_item_is_table (item))
-    spv_item_get_table (item);
-  else if (spv_item_is_image (item))
-    spv_item_get_image (item);
-}
-
-bool
-spv_item_is_light_table (const struct spv_item *item)
-{
-  return item->type == SPV_ITEM_TABLE && !item->xml_member;
-}
-
 char * WARN_UNUSED_RESULT
 char * WARN_UNUSED_RESULT
-spv_item_get_raw_light_table (const struct spv_item *item,
-                              void **data, size_t *size)
-{
-  return zip_member_read_all (item->spv->zip, item->bin_member, data, size);
-}
-
-char * WARN_UNUSED_RESULT
-spv_item_get_light_table (const struct spv_item *item,
-                          struct spvlb_table **tablep)
+spv_read_light_table (struct zip_reader *zip, const char *bin_member,
+                      struct spvlb_table **tablep)
 {
   *tablep = NULL;
 
 {
   *tablep = NULL;
 
-  if (!spv_item_is_light_table (item))
-    return xstrdup ("not a light binary table object");
-
   void *data;
   size_t size;
   void *data;
   size_t size;
-  char *error = spv_item_get_raw_light_table (item, &data, &size);
+  char *error = zip_member_read_all (zip, bin_member, &data, &size);
   if (error)
     return error;
 
   if (error)
     return error;
 
@@ -661,57 +366,34 @@ spv_item_get_light_table (const struct spv_item *item,
            : input.ofs != input.size
            ? xasprintf ("expected end of file at offset %#zx", input.ofs)
            : NULL);
            : input.ofs != input.size
            ? xasprintf ("expected end of file at offset %#zx", input.ofs)
            : NULL);
-  if (error)
-    {
-      struct string s = DS_EMPTY_INITIALIZER;
-      spv_item_format_path (item, &s);
-      ds_put_format (&s, " (%s): %s", item->bin_member, error);
-
-      free (error);
-      error = ds_steal_cstr (&s);
-    }
   free (data);
   if (!error)
     *tablep = table;
   return error;
 }
 
   free (data);
   if (!error)
     *tablep = table;
   return error;
 }
 
-static char *
-pivot_table_open_light (struct spv_item *item)
+static char * WARN_UNUSED_RESULT
+pivot_table_open_light (struct zip_reader *zip, const char *bin_member,
+                        struct pivot_table **tablep)
 {
 {
-  assert (spv_item_is_light_table (item));
+  *tablep = NULL;
 
   struct spvlb_table *raw_table;
 
   struct spvlb_table *raw_table;
-  char *error = spv_item_get_light_table (item, &raw_table);
+  char *error = spv_read_light_table (zip, bin_member, &raw_table);
   if (!error)
   if (!error)
-    error = decode_spvlb_table (raw_table, &item->table);
+    error = decode_spvlb_table (raw_table, tablep);
   spvlb_free_table (raw_table);
 
   return error;
 }
 
   spvlb_free_table (raw_table);
 
   return error;
 }
 
-bool
-spv_item_is_legacy_table (const struct spv_item *item)
-{
-  return item->type == SPV_ITEM_TABLE && item->xml_member;
-}
-
 char * WARN_UNUSED_RESULT
 char * WARN_UNUSED_RESULT
-spv_item_get_raw_legacy_data (const struct spv_item *item,
-                              void **data, size_t *size)
-{
-  if (!spv_item_is_legacy_table (item))
-    return xstrdup ("not a legacy table object");
-
-  return zip_member_read_all (item->spv->zip, item->bin_member, data, size);
-}
-
-char * WARN_UNUSED_RESULT
-spv_item_get_legacy_data (const struct spv_item *item, struct spv_data *data)
+spv_read_legacy_data (struct zip_reader *zip, const char *bin_member,
+                      struct spv_data *data)
 {
   void *raw;
   size_t size;
 {
   void *raw;
   size_t size;
-  char *error = spv_item_get_raw_legacy_data (item, &raw, &size);
+  char *error = zip_member_read_all (zip, bin_member, &raw, &size);
   if (!error)
     {
       error = spv_legacy_data_decode (raw, size, data);
   if (!error)
     {
       error = spv_legacy_data_decode (raw, size, data);
@@ -721,15 +403,15 @@ spv_item_get_legacy_data (const struct spv_item *item, struct spv_data *data)
   return error;
 }
 
   return error;
 }
 
-static char * WARN_UNUSED_RESULT
-spv_read_xml_member (struct spv_reader *spv, const char *member_name,
+char * WARN_UNUSED_RESULT
+spv_read_xml_member (struct zip_reader *zip, const char *xml_member,
                      bool keep_blanks, const char *root_element_name,
                      xmlDoc **docp)
 {
   *docp = NULL;
 
   struct zip_member *zm;
                      bool keep_blanks, const char *root_element_name,
                      xmlDoc **docp)
 {
   *docp = NULL;
 
   struct zip_member *zm;
-  char *error = zip_member_open (spv->zip, member_name, &zm);
+  char *error = zip_member_open (zip, xml_member, &zm);
   if (error)
     return error;
 
   if (error)
     return error;
 
@@ -739,7 +421,7 @@ spv_read_xml_member (struct spv_reader *spv, const char *member_name,
   if (!parser)
     {
       zip_member_finish (zm);
   if (!parser)
     {
       zip_member_finish (zm);
-      return xasprintf (_("%s: Failed to create XML parser"), member_name);
+      return xasprintf (_("%s: Failed to create XML parser"), xml_member);
     }
 
   int retval;
     }
 
   int retval;
@@ -764,7 +446,7 @@ spv_read_xml_member (struct spv_reader *spv, const char *member_name,
   if (!well_formed)
     {
       xmlFreeDoc (doc);
   if (!well_formed)
     {
       xmlFreeDoc (doc);
-      return xasprintf(_("%s: document is not well-formed"), member_name);
+      return xasprintf(_("%s: document is not well-formed"), xml_member);
     }
 
   const xmlNode *root_node = xmlDocGetRootElement (doc);
     }
 
   const xmlNode *root_node = xmlDocGetRootElement (doc);
@@ -773,7 +455,7 @@ spv_read_xml_member (struct spv_reader *spv, const char *member_name,
     {
       xmlFreeDoc (doc);
       return xasprintf(_("%s: root node is \"%s\" but \"%s\" was expected"),
     {
       xmlFreeDoc (doc);
       return xasprintf(_("%s: root node is \"%s\" but \"%s\" was expected"),
-                       member_name,
+                       xml_member,
                        CHAR_CAST (char *, root_node->name), root_element_name);
     }
 
                        CHAR_CAST (char *, root_node->name), root_element_name);
     }
 
@@ -781,24 +463,9 @@ spv_read_xml_member (struct spv_reader *spv, const char *member_name,
   return NULL;
 }
 
   return NULL;
 }
 
-char * WARN_UNUSED_RESULT
-spv_item_get_legacy_table (const struct spv_item *item, xmlDoc **docp)
-{
-  assert (spv_item_is_legacy_table (item));
-
-  return spv_read_xml_member (item->spv, item->xml_member, false,
-                              "visualization", docp);
-}
-
-char * WARN_UNUSED_RESULT
-spv_item_get_structure (const struct spv_item *item, struct _xmlDoc **docp)
-{
-  return spv_read_xml_member (item->spv, item->structure_member, false,
-                              "heading", docp);
-}
-
+#if 0
 static const char *
 static const char *
-identify_item (const struct spv_item *item)
+identify_item (const struct output_item *item)
 {
   return (item->label ? item->label
           : item->command_id ? item->command_id
 {
   return (item->label ? item->label
           : item->command_id ? item->command_id
@@ -843,204 +510,240 @@ spv_item_format_path (const struct spv_item *item, struct string *s)
         }
     }
 }
         }
     }
 }
+#endif
 
 static char * WARN_UNUSED_RESULT
 
 static char * WARN_UNUSED_RESULT
-pivot_table_open_legacy (struct spv_item *item)
+pivot_table_open_legacy (struct zip_reader *zip, const char *bin_member,
+                         const char *xml_member, const char *subtype,
+                         const struct pivot_table_look *look,
+                         struct pivot_table **tablep)
 {
 {
-  assert (spv_item_is_legacy_table (item));
+  *tablep = NULL;
 
 
-  struct spv_data data;
-  char *error = spv_item_get_legacy_data (item, &data);
+  struct spv_data data = SPV_DATA_INITIALIZER;
+  char *error = spv_read_legacy_data (zip, bin_member, &data);
   if (error)
   if (error)
-    {
-      struct string s = DS_EMPTY_INITIALIZER;
-      spv_item_format_path (item, &s);
-      ds_put_format (&s, " (%s): %s", item->bin_member, error);
-
-      free (error);
-      return ds_steal_cstr (&s);
-    }
+    goto exit;
 
   xmlDoc *doc;
 
   xmlDoc *doc;
-  error = spv_read_xml_member (item->spv, item->xml_member, false,
+  error = spv_read_xml_member (zip, xml_member, false,
                                "visualization", &doc);
   if (error)
                                "visualization", &doc);
   if (error)
-    {
-      spv_data_uninit (&data);
-      return error;
-    }
+    goto exit_free_data;
 
   struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
   struct spvdx_visualization *v;
   spvdx_parse_visualization (&ctx, xmlDocGetRootElement (doc), &v);
   error = spvxml_context_finish (&ctx, &v->node_);
 
   struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
   struct spvdx_visualization *v;
   spvdx_parse_visualization (&ctx, xmlDocGetRootElement (doc), &v);
   error = spvxml_context_finish (&ctx, &v->node_);
-
-  if (!error)
-    error = decode_spvdx_table (v, item->subtype, item->table_look,
-                                &data, &item->table);
-
   if (error)
   if (error)
-    {
-      struct string s = DS_EMPTY_INITIALIZER;
-      spv_item_format_path (item, &s);
-      ds_put_format (&s, " (%s): %s", item->xml_member, error);
+    goto exit_free_doc;
 
 
-      free (error);
-      error = ds_steal_cstr (&s);
-    }
+  error = decode_spvdx_table (v, subtype, look, &data, tablep);
 
 
-  spv_data_uninit (&data);
   spvdx_free_visualization (v);
   spvdx_free_visualization (v);
+exit_free_doc:
   if (doc)
     xmlFreeDoc (doc);
   if (doc)
     xmlFreeDoc (doc);
-
+exit_free_data:
+  spv_data_uninit (&data);
+exit:
   return error;
 }
 
   return error;
 }
 
-const struct pivot_table *
-spv_item_get_table (const struct spv_item *item_)
+static struct output_item *
+spv_read_table_item (struct zip_reader *zip,
+                     const struct spvsx_table *table)
 {
 {
-  struct spv_item *item = CONST_CAST (struct spv_item *, item_);
+  const struct spvsx_table_structure *ts = table->table_structure;
+  const char *bin_member = ts->data_path->text;
+  const char *xml_member = ts->path ? ts->path->text : NULL;
 
 
-  assert (spv_item_is_table (item));
-  if (!item->table)
+  struct pivot_table *pt = NULL;
+  char *error;
+  if (xml_member)
     {
     {
-      char *error = (item->xml_member
-                     ? pivot_table_open_legacy (item)
-                     : pivot_table_open_light (item));
-      if (error)
+      struct pivot_table_look *look;
+      error = (table->table_properties
+               ? spv_table_look_decode (table->table_properties, &look)
+               : xstrdup ("Legacy table lacks tableProperties"));
+      if (!error)
         {
         {
-          item->error = true;
-          msg (ME, "%s", error);
-          item->table = pivot_table_create_for_text (
-            pivot_value_new_text (N_("Error")),
-            pivot_value_new_user_text (error, -1));
-          free (error);
+          error = pivot_table_open_legacy (zip, bin_member, xml_member,
+                                           table->sub_type, look, &pt);
+          pivot_table_look_unref (look);
         }
     }
         }
     }
+  else
+    error = pivot_table_open_light (zip, bin_member, &pt);
+  if (error)
+    pt = pivot_table_create_for_text (
+      pivot_value_new_text (N_("Error")),
+      pivot_value_new_user_text_nocopy (error));
 
 
-  return item->table;
+  struct output_item *item = table_item_create (pt);
+  output_item_set_command_name (item, table->command_name);
+  output_item_add_spv_info (item);
+  item->spv_info->error = error != NULL;
+  item->spv_info->zip_reader = zip_reader_ref (zip);
+  item->spv_info->bin_member = xstrdup (bin_member);
+  item->spv_info->xml_member = xstrdup_if_nonnull (xml_member);
+  return item;
 }
 
 }
 
-/* Constructs a new spv_item from XML and stores it in *ITEMP.  Returns NULL if
-   successful, otherwise an error message for the caller to use and free (with
-   free()).
+static cairo_status_t
+read_from_zip_member (void *zm_, unsigned char *data, unsigned int length)
+{
+  struct zip_member *zm = zm_;
+  if (!zm)
+    return CAIRO_STATUS_READ_ERROR;
+
+  while (length > 0)
+    {
+      int n = zip_member_read (zm, data, length);
+      if (n <= 0)
+        return CAIRO_STATUS_READ_ERROR;
+
+      data += n;
+      length -= n;
+    }
+
+  return CAIRO_STATUS_SUCCESS;
+}
 
 
-   XML should be a 'heading' or 'container' element. */
 static char * WARN_UNUSED_RESULT
 static char * WARN_UNUSED_RESULT
-spv_decode_container (const struct spvsx_container *c,
-                      const char *structure_member,
-                      struct spv_item *parent)
+spv_read_image (struct zip_reader *zip, const char *png_member,
+                const char *command_name, struct output_item **itemp)
+{
+  struct zip_member *zm;
+  char *error = zip_member_open (zip, png_member, &zm);
+  if (error)
+    return error;
+
+  cairo_surface_t *surface = cairo_image_surface_create_from_png_stream (
+    read_from_zip_member, zm);
+  if (zm)
+    zip_member_finish (zm);
+
+  if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)
+    return xstrdup ("reading image failed");
+
+  struct output_item *item = image_item_create (surface);
+  output_item_set_command_name (item, command_name);
+  output_item_add_spv_info (item);
+  item->spv_info->zip_reader = zip_reader_ref (zip);
+  item->spv_info->png_member = xstrdup (png_member);
+  *itemp = item;
+  return NULL;
+}
+
+static struct output_item *
+error_item_create (char *s)
 {
 {
-  struct spv_item *item = xzalloc (sizeof *item);
-  item->spv = parent->spv;
-  item->label = xstrdup (c->label->text);
-  item->visible = c->visibility == SPVSX_VISIBILITY_VISIBLE;
-  item->structure_member = xstrdup (structure_member);
+  struct output_item *item = text_item_create_nocopy (TEXT_ITEM_LOG, s,
+                                                      xstrdup ("Error"));
+  output_item_add_spv_info (item);
+  item->spv_info->error = true;
+  return item;
+}
 
 
+static struct output_item *
+spv_decode_container (struct zip_reader *zip,
+                      const struct spvsx_container *c)
+{
   assert (c->n_seq == 1);
   struct spvxml_node *content = c->seq[0];
   assert (c->n_seq == 1);
   struct spvxml_node *content = c->seq[0];
+
+  struct output_item *item = NULL;
+  char *error;
   if (spvsx_is_container_text (content))
   if (spvsx_is_container_text (content))
-    decode_container_text (spvsx_cast_container_text (content), item);
-  else if (spvsx_is_table (content))
-    {
-      item->type = SPV_ITEM_TABLE;
-
-      struct spvsx_table *table = spvsx_cast_table (content);
-      const struct spvsx_table_structure *ts = table->table_structure;
-      item->bin_member = xstrdup (ts->data_path->text);
-      item->command_id = xstrdup_if_nonempty (table->command_name);
-      item->subtype = xstrdup_if_nonempty (table->sub_type);
-      if (ts->path)
-        {
-          item->xml_member = ts->path ? xstrdup (ts->path->text) : NULL;
-          char *error = (table->table_properties
-                         ? spv_table_look_decode (table->table_properties,
-                                                  &item->table_look)
-                         : xstrdup ("Legacy table lacks tableProperties"));
-          if (error)
-            {
-              spv_item_destroy (item);
-              return error;
-            }
-        }
-    }
-  else if (spvsx_is_graph (content))
     {
     {
-      struct spvsx_graph *graph = spvsx_cast_graph (content);
-      item->type = SPV_ITEM_GRAPH;
-      item->command_id = xstrdup_if_nonempty (graph->command_name);
-      /* XXX */
+      item = decode_container_text (spvsx_cast_container_text (content));
+      error = NULL;
     }
     }
-  else if (spvsx_is_model (content))
+  else if (spvsx_is_table (content))
     {
     {
-      struct spvsx_model *model = spvsx_cast_model (content);
-      item->type = SPV_ITEM_MODEL;
-      item->command_id = xstrdup_if_nonempty (model->command_name);
-      /* XXX */
+      item = spv_read_table_item (zip, spvsx_cast_table (content));
+      error = NULL;
     }
   else if (spvsx_is_object (content))
     {
       struct spvsx_object *object = spvsx_cast_object (content);
     }
   else if (spvsx_is_object (content))
     {
       struct spvsx_object *object = spvsx_cast_object (content);
-      item->type = SPV_ITEM_IMAGE;
-      item->png_member = xstrdup (object->uri);
+      error = spv_read_image (zip, object->uri, object->command_name, &item);
     }
   else if (spvsx_is_image (content))
     {
       struct spvsx_image *image = spvsx_cast_image (content);
     }
   else if (spvsx_is_image (content))
     {
       struct spvsx_image *image = spvsx_cast_image (content);
-      item->type = SPV_ITEM_IMAGE;
-      item->png_member = xstrdup (image->data_path->text);
+      error = spv_read_image (zip, image->data_path->text, image->command_name,
+                              &item);
     }
     }
+  else if (spvsx_is_graph (content))
+    error = xstrdup ("graphs not yet implemented");
+  else if (spvsx_is_model (content))
+    error = xstrdup ("models not yet implemented");
   else if (spvsx_is_tree (content))
   else if (spvsx_is_tree (content))
-    item->type = SPV_ITEM_TREE;
+    error = xstrdup ("trees not yet implemented");
   else
     NOT_REACHED ();
 
   else
     NOT_REACHED ();
 
-  spv_heading_add_child (parent, item);
-  return NULL;
+  if (error)
+    item = error_item_create (error);
+  else
+    output_item_set_label (item, c->label->text);
+  item->show = c->visibility == SPVSX_VISIBILITY_VISIBLE;
+
+  return item;
 }
 
 }
 
-static char * WARN_UNUSED_RESULT
-spv_decode_children (struct spv_reader *spv, const char *structure_member,
+static void
+set_structure_member (struct output_item *item, struct zip_reader *zip,
+                      const char *structure_member)
+{
+  if (structure_member)
+    {
+      output_item_add_spv_info (item);
+      if (!item->spv_info->zip_reader)
+        item->spv_info->zip_reader = zip_reader_ref (zip);
+      if (!item->spv_info->structure_member)
+        item->spv_info->structure_member = xstrdup (structure_member);
+    }
+}
+
+static void
+spv_decode_children (struct zip_reader *zip, const char *structure_member,
                      struct spvxml_node **seq, size_t n_seq,
                      struct spvxml_node **seq, size_t n_seq,
-                     struct spv_item *parent)
+                     struct output_item *parent)
 {
   for (size_t i = 0; i < n_seq; i++)
     {
       const struct spvxml_node *node = seq[i];
 
 {
   for (size_t i = 0; i < n_seq; i++)
     {
       const struct spvxml_node *node = seq[i];
 
-      char *error = NULL;
+      struct output_item *child;
       if (spvsx_is_container (node))
         {
           const struct spvsx_container *container
             = spvsx_cast_container (node);
       if (spvsx_is_container (node))
         {
           const struct spvsx_container *container
             = spvsx_cast_container (node);
-          error = spv_decode_container (container, structure_member, parent);
+          child = spv_decode_container (zip, container);
         }
       else if (spvsx_is_heading (node))
         {
           const struct spvsx_heading *subheading = spvsx_cast_heading (node);
         }
       else if (spvsx_is_heading (node))
         {
           const struct spvsx_heading *subheading = spvsx_cast_heading (node);
-          struct spv_item *subitem = xzalloc (sizeof *subitem);
-          subitem->structure_member = xstrdup (structure_member);
-          subitem->spv = parent->spv;
-          subitem->type = SPV_ITEM_HEADING;
-          subitem->label = xstrdup (subheading->label->text);
-          if (subheading->command_name)
-            subitem->command_id = xstrdup (subheading->command_name);
-          subitem->visible = !subheading->heading_visibility_present;
-          spv_heading_add_child (parent, subitem);
-
-          error = spv_decode_children (spv, structure_member,
-                                       subheading->seq, subheading->n_seq,
-                                       subitem);
+
+          child = group_item_create (subheading->command_name,
+                                     subheading->label->text);
+          child->show = !subheading->heading_visibility_present;
+
+          /* Pass NULL for 'structure_member' so that only top-level items get
+             tagged that way.  Lower-level items are always in the same
+             structure member as their parent anyway. */
+           spv_decode_children (zip, NULL, subheading->seq,
+                                subheading->n_seq, child);
         }
       else
         NOT_REACHED ();
 
         }
       else
         NOT_REACHED ();
 
-      if (error)
-        return error;
+      set_structure_member (child, zip, structure_member);
+      group_item_add_child (parent, child);
     }
     }
-
-  return NULL;
 }
 
 static struct page_setup *
 }
 
 static struct page_setup *
@@ -1085,44 +788,52 @@ decode_page_setup (const struct spvsx_page_setup *in, const char *file_name)
   return out;
 }
 
   return out;
 }
 
-static char * WARN_UNUSED_RESULT
-spv_heading_read (struct spv_reader *spv,
-                  const char *file_name, const char *member_name)
+static void
+spv_add_error_heading (struct output_item *root_item,
+                       struct zip_reader *zip, const char *structure_member,
+                       char *error)
+{
+  struct output_item *item = error_item_create (
+    xasprintf ("%s: %s", structure_member, error));
+  free (error);
+  set_structure_member (item, zip, structure_member);
+  group_item_add_child (root_item, item);
+}
+
+static void
+spv_heading_read (struct zip_reader *zip, struct output_item *root_item,
+                  struct page_setup **psp, const char *file_name,
+                  const char *structure_member)
 {
   xmlDoc *doc;
 {
   xmlDoc *doc;
-  char *error = spv_read_xml_member (spv, member_name, true, "heading", &doc);
+  char *error = spv_read_xml_member (zip, structure_member, true,
+                                     "heading", &doc);
   if (error)
   if (error)
-    return error;
+    {
+      spv_add_error_heading (root_item, zip, structure_member, error);
+      return;
+    }
 
   struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
   struct spvsx_root_heading *root;
   spvsx_parse_root_heading (&ctx, xmlDocGetRootElement (doc), &root);
   error = spvxml_context_finish (&ctx, &root->node_);
 
   struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
   struct spvsx_root_heading *root;
   spvsx_parse_root_heading (&ctx, xmlDocGetRootElement (doc), &root);
   error = spvxml_context_finish (&ctx, &root->node_);
-
-  if (!error && root->page_setup)
-    spv->page_setup = decode_page_setup (root->page_setup, file_name);
-
-  for (size_t i = 0; !error && i < root->n_seq; i++)
-    error = spv_decode_children (spv, member_name, root->seq, root->n_seq,
-                                 spv->root);
-
   if (error)
     {
   if (error)
     {
-      char *s = xasprintf ("%s: %s", member_name, error);
-      free (error);
-      error = s;
+      xmlFreeDoc (doc);
+      spv_add_error_heading (root_item, zip, structure_member, error);
+      return;
     }
 
     }
 
-  spvsx_free_root_heading (root);
-  xmlFreeDoc (doc);
+  if (root->page_setup && psp && !*psp)
+    *psp = decode_page_setup (root->page_setup, file_name);
 
 
-  return error;
-}
+  for (size_t i = 0; i < root->n_seq; i++)
+    spv_decode_children (zip, structure_member, root->seq, root->n_seq,
+                         root_item);
 
 
-struct spv_item *
-spv_get_root (const struct spv_reader *spv)
-{
-  return spv->root;
+  spvsx_free_root_heading (root);
+  xmlFreeDoc (doc);
 }
 
 static int
 }
 
 static int
@@ -1165,81 +876,43 @@ spv_detect (const char *filename)
 }
 
 char * WARN_UNUSED_RESULT
 }
 
 char * WARN_UNUSED_RESULT
-spv_open (const char *filename, struct spv_reader **spvp)
+spv_read (const char *filename, struct output_item **outp,
+          struct page_setup **psp)
 {
 {
-  *spvp = NULL;
+  *outp = NULL;
+  if (psp)
+    *psp = NULL;
 
   struct spv_reader *spv = xzalloc (sizeof *spv);
 
   struct spv_reader *spv = xzalloc (sizeof *spv);
-  char *error = zip_reader_create (filename, &spv->zip);
+  struct zip_reader *zip;
+  char *error = zip_reader_create (filename, &zip);
   if (error)
   if (error)
-    {
-      spv_close (spv);
-      return error;
-    }
+    return error;
 
 
-  int detect = spv_detect__ (spv->zip, &error);
+  int detect = spv_detect__ (zip, &error);
   if (detect <= 0)
     {
   if (detect <= 0)
     {
-      spv_close (spv);
-      return error ? error : xasprintf("%s: not an SPV file", filename);
+      zip_reader_unref (zip);
+      return error ? error : xasprintf ("%s: not an SPV file", filename);
     }
 
     }
 
-  spv->root = xzalloc (sizeof *spv->root);
-  spv->root->spv = spv;
-  spv->root->type = SPV_ITEM_HEADING;
+  *outp = root_item_create ();
   for (size_t i = 0; ; i++)
     {
   for (size_t i = 0; ; i++)
     {
-      const char *member_name = zip_reader_get_member_name (spv->zip, i);
-      if (!member_name)
+      const char *structure_member = zip_reader_get_member_name (zip, i);
+      if (!structure_member)
         break;
 
         break;
 
-      struct substring member_name_ss = ss_cstr (member_name);
-      if (ss_starts_with (member_name_ss, ss_cstr ("outputViewer"))
-          && ss_ends_with (member_name_ss, ss_cstr (".xml")))
-        {
-          char *error = spv_heading_read (spv, filename, member_name);
-          if (error)
-            {
-              spv_close (spv);
-              return error;
-            }
-        }
+      struct substring structure_member_ss = ss_cstr (structure_member);
+      if (ss_starts_with (structure_member_ss, ss_cstr ("outputViewer"))
+          && ss_ends_with (structure_member_ss, ss_cstr (".xml")))
+        spv_heading_read (zip, *outp, psp, filename, structure_member);
     }
 
     }
 
-  *spvp = spv;
+  zip_reader_unref (zip);
   return NULL;
 }
 
   return NULL;
 }
 
-void
-spv_close (struct spv_reader *spv)
-{
-  if (spv)
-    {
-      zip_reader_unref (spv->zip);
-      spv_item_destroy (spv->root);
-      page_setup_destroy (spv->page_setup);
-      free (spv);
-    }
-}
-
-void
-spv_item_set_table_look (struct spv_item *item,
-                         const struct pivot_table_look *look)
-{
-  /* If this is a table, install the table look in it.
-
-     (We can't just set item->table_look because light tables ignore it and
-     legacy tables sometimes override it.) */
-  if (spv_item_is_table (item))
-    {
-      spv_item_load (item);
-      pivot_table_set_look (item->table, look);
-    }
-
-  for (size_t i = 0; i < item->n_children; i++)
-    spv_item_set_table_look (item->children[i], look);
-}
-
 char * WARN_UNUSED_RESULT
 spv_decode_fmt_spec (uint32_t u32, struct fmt_spec *out)
 {
 char * WARN_UNUSED_RESULT
 spv_decode_fmt_spec (uint32_t u32, struct fmt_spec *out)
 {
index eda7704dd70e746b257175df905dcdd67cafe59f..5bf4f5692f8ca859cba2f1f75f41b6a1224497c2 100644 (file)
 #ifndef OUTPUT_SPV_H
 #define OUTPUT_SPV_H 1
 
 #ifndef OUTPUT_SPV_H
 #define OUTPUT_SPV_H 1
 
-/* SPSS Viewer (SPV) file reader.
+/* SPSS Viewer (SPV) file reader. */
 
 
-   An SPV file, represented as struct spv_reader, contains a number of
-   top-level headings, each of which recursively contains other headings and
-   tables.  Here, we model a heading, text, table, or other element as an
-   "item", and a an SPV file as a single root item that contains each of the
-   top-level headings as a child item.
- */
-
-#include <cairo.h>
 #include <stdbool.h>
 #include <stdbool.h>
-#include <stddef.h>
 #include <stdint.h>
 #include <stdint.h>
+#include <libxml/tree.h>
 
 #include "libpspp/compiler.h"
 
 struct fmt_spec;
 
 #include "libpspp/compiler.h"
 
 struct fmt_spec;
-struct pivot_table;
+struct output_item;
+struct page_setup;
 struct spv_data;
 struct spv_data;
-struct spv_reader;
 struct spvlb_table;
 struct spvlb_table;
-struct string;
-struct _xmlDoc;
-
-/* SPV files. */
-
-char *spv_open (const char *filename, struct spv_reader **) WARN_UNUSED_RESULT;
-void spv_close (struct spv_reader *);
+struct zip_reader;
 
 
+/* Main functions. */
+char *spv_read (const char *filename, struct output_item **,
+                struct page_setup **) WARN_UNUSED_RESULT;
 char *spv_detect (const char *filename) WARN_UNUSED_RESULT;
 
 char *spv_detect (const char *filename) WARN_UNUSED_RESULT;
 
-const char *spv_get_errors (const struct spv_reader *);
-void spv_clear_errors (struct spv_reader *);
-
-struct spv_item *spv_get_root (const struct spv_reader *);
-void spv_item_dump (const struct spv_item *, int indentation);
-
-const struct page_setup *spv_get_page_setup (const struct spv_reader *);
-
-/* Items.
-
-   An spv_item represents of the elements that can occur in an SPV file.  Items
-   form a tree because "heading" items can have an arbitrary number of child
-   items, which in turn may also be headings.  The root item, that is, the item
-   returned by spv_get_root(), is always a heading. */
-
-enum spv_item_type
-  {
-    SPV_ITEM_HEADING,
-    SPV_ITEM_TEXT,
-    SPV_ITEM_TABLE,
-    SPV_ITEM_GRAPH,
-    SPV_ITEM_MODEL,
-    SPV_ITEM_IMAGE,
-    SPV_ITEM_TREE,
-  };
-
-const char *spv_item_type_to_string (enum spv_item_type);
-
-#define SPV_CLASSES                                \
-    SPV_CLASS(CHARTS, "charts")                    \
-    SPV_CLASS(HEADINGS, "headings")                \
-    SPV_CLASS(LOGS, "logs")                        \
-    SPV_CLASS(MODELS, "models")                    \
-    SPV_CLASS(TABLES, "tables")                    \
-    SPV_CLASS(TEXTS, "texts")                      \
-    SPV_CLASS(TREES, "trees")                      \
-    SPV_CLASS(WARNINGS, "warnings")                \
-    SPV_CLASS(OUTLINEHEADERS, "outlineheaders")    \
-    SPV_CLASS(PAGETITLE, "pagetitle")              \
-    SPV_CLASS(NOTES, "notes")                      \
-    SPV_CLASS(UNKNOWN, "unknown")                  \
-    SPV_CLASS(OTHER, "other")
-enum spv_item_class
-  {
-#define SPV_CLASS(ENUM, NAME) SPV_CLASS_##ENUM,
-    SPV_CLASSES
-#undef SPV_CLASS
-  };
-enum
-  {
-#define SPV_CLASS(ENUM, NAME) +1
-    SPV_N_CLASSES = SPV_CLASSES
-#undef SPV_CLASS
-};
-#define SPV_ALL_CLASSES ((1u << SPV_N_CLASSES) - 1)
-
-const char *spv_item_class_to_string (enum spv_item_class);
-enum spv_item_class spv_item_class_from_string (const char *);
-
-struct spv_item
-  {
-    struct spv_reader *spv;
-    struct spv_item *parent;
-    size_t parent_idx;         /* item->parent->children[parent_idx] == item */
-
-    bool error;
-
-    char *structure_member;
-
-    enum spv_item_type type;
-    char *label;                /* Localized label. */
-    char *command_id;           /* Non-localized unique command identifier. */
-
-    /* Whether the item is visible.
-       For SPV_ITEM_HEADING, false indicates that the item is collapsed.
-       For SPV_ITEM_TABLE, false indicates that the item is not shown. */
-    bool visible;
-
-    /* SPV_ITEM_HEADING only. */
-    struct spv_item **children;
-    size_t n_children, allocated_children;
-
-    /* SPV_ITEM_TABLE only. */
-    struct pivot_table *table;    /* NULL if not yet loaded. */
-    struct pivot_table_look *table_look;
-    char *bin_member;
-    char *xml_member;
-    char *subtype;
-
-    /* SPV_ITEM_TEXT only.  */
-    struct pivot_value *text;
-
-    /* SPV_ITEM_IMAGE only. */
-    char *png_member;
-    cairo_surface_t *image;
-  };
-
-void spv_item_format_path (const struct spv_item *, struct string *);
-
-void spv_item_load (const struct spv_item *);
-
-enum spv_item_type spv_item_get_type (const struct spv_item *);
-enum spv_item_class spv_item_get_class (const struct spv_item *);
-
-const char *spv_item_get_label (const struct spv_item *);
-
-bool spv_item_is_heading (const struct spv_item *);
-size_t spv_item_get_n_children (const struct spv_item *);
-struct spv_item *spv_item_get_child (const struct spv_item *, size_t idx);
-
-bool spv_item_is_table (const struct spv_item *);
-const struct pivot_table *spv_item_get_table (const struct spv_item *);
-
-bool spv_item_is_text (const struct spv_item *);
-const struct pivot_value *spv_item_get_text (const struct spv_item *);
-
-bool spv_item_is_image (const struct spv_item *);
-cairo_surface_t *spv_item_get_image (const struct spv_item *);
-
-bool spv_item_is_visible (const struct spv_item *);
-
-#define SPV_ITEM_FOR_EACH(ITER, ROOT) \
-  for ((ITER) = (ROOT); (ITER) != NULL; (ITER) = spv_item_next(ITER))
-#define SPV_ITEM_FOR_EACH_SKIP_ROOT(ITER, ROOT) \
-  for ((ITER) = (ROOT); ((ITER) = spv_item_next(ITER)) != NULL;)
-struct spv_item *spv_item_next (const struct spv_item *);
-
-const struct spv_item *spv_item_get_parent (const struct spv_item *);
-size_t spv_item_get_level (const struct spv_item *);
-
-const char *spv_item_get_command_id (const struct spv_item *);
-const char *spv_item_get_subtype (const struct spv_item *);
-
-char *spv_item_get_structure (const struct spv_item *, struct _xmlDoc **)
-  WARN_UNUSED_RESULT;
-
-bool spv_item_is_light_table (const struct spv_item *);
-char *spv_item_get_light_table (const struct spv_item *,
-                                    struct spvlb_table **)
-  WARN_UNUSED_RESULT;
-char *spv_item_get_raw_light_table (const struct spv_item *,
-                                    void **data, size_t *size)
-  WARN_UNUSED_RESULT;
-
-bool spv_item_is_legacy_table (const struct spv_item *);
-char *spv_item_get_raw_legacy_data (const struct spv_item *item,
-                                    void **data, size_t *size)
-  WARN_UNUSED_RESULT;
-char *spv_item_get_legacy_data (const struct spv_item *, struct spv_data *)
-  WARN_UNUSED_RESULT;
-char *spv_item_get_legacy_table (const struct spv_item *, struct _xmlDoc **)
-  WARN_UNUSED_RESULT;
-
-void spv_item_set_table_look (struct spv_item *,
-                              const struct pivot_table_look *);
+/* Debugging functions. */
+char *spv_read_light_table (struct zip_reader *, const char *bin_member,
+                            struct spvlb_table **) WARN_UNUSED_RESULT;
+char *spv_read_legacy_data (struct zip_reader *, const char *bin_member,
+                            struct spv_data *) WARN_UNUSED_RESULT;
+char *spv_read_xml_member (struct zip_reader *, const char *member_name,
+                           bool keep_blanks, const char *root_element_name,
+                           xmlDoc **) WARN_UNUSED_RESULT;
 
 
+/* Helpers. */
 char *spv_decode_fmt_spec (uint32_t u32, struct fmt_spec *) WARN_UNUSED_RESULT;
 
 #endif /* output/spv/spv.h */
 char *spv_decode_fmt_spec (uint32_t u32, struct fmt_spec *) WARN_UNUSED_RESULT;
 
 #endif /* output/spv/spv.h */
index b9a90df2d78a3850354495c0177efcca3a792dd0..254d6a686183e64b9ebc60b76e301eb1995bc371 100644 (file)
@@ -328,11 +328,8 @@ tex_submit (struct output_driver *driver, const struct output_item *item)
         }
       break;
 
         }
       break;
 
-    case OUTPUT_ITEM_GROUP_OPEN:
-      break;
-
-    case OUTPUT_ITEM_GROUP_CLOSE:
-      break;
+    case OUTPUT_ITEM_GROUP:
+      NOT_REACHED ();
 
     case OUTPUT_ITEM_IMAGE:
       {
 
     case OUTPUT_ITEM_IMAGE:
       {
@@ -614,8 +611,7 @@ struct output_driver_factory tex_driver_factory =
 
 static const struct output_driver_class tex_driver_class =
   {
 
 static const struct output_driver_class tex_driver_class =
   {
-    "tex",
-    tex_destroy,
-    tex_submit,
-    NULL,
+    .name = "tex",
+    .destroy = tex_destroy,
+    .submit = tex_submit,
   };
   };
index 6d704ae1bccf1bba19eaa468cbddaf6eacbca03f..857c1c5730782ee5a9be7f6f8b70a1f3e65b1d3b 100644 (file)
@@ -325,7 +325,7 @@ rerender (struct psppire_output_view *view)
       if (view->y > 0)
         view->y += view->object_spacing;
 
       if (view->y > 0)
         view->y += view->object_spacing;
 
-      if (item->item->type == OUTPUT_ITEM_GROUP_OPEN)
+      if (item->item->type == OUTPUT_ITEM_GROUP)
         continue;
 
       r = xr_fsm_create_for_scrolling (item->item, view->style, cr);
         continue;
 
       r = xr_fsm_create_for_scrolling (item->item, view->style, cr);
@@ -396,19 +396,10 @@ psppire_output_view_put (struct psppire_output_view *view,
   GtkWidget *drawing_area;
   int tw, th;
 
   GtkWidget *drawing_area;
   int tw, th;
 
-  if (item->type == OUTPUT_ITEM_GROUP_CLOSE)
-    {
-      if (view->cur_group)
-        {
-          if (!gtk_tree_path_up (view->cur_group))
-            {
-              gtk_tree_path_free (view->cur_group);
-              view->cur_group = NULL;
-            }
-        }
-      return;
-    }
-  else if (item->type == OUTPUT_ITEM_TEXT)
+  if (item->type == OUTPUT_ITEM_GROUP)
+    return;
+
+  if (item->type == OUTPUT_ITEM_TEXT)
     {
       char *text = text_item_get_plain_text (item);
       bool text_is_empty = text[0] == '\0';
     {
       char *text = text_item_get_plain_text (item);
       bool text_is_empty = text[0] == '\0';
@@ -425,9 +416,7 @@ psppire_output_view_put (struct psppire_output_view *view,
   view_item->drawing_area = NULL;
 
   GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (view->output));
   view_item->drawing_area = NULL;
 
   GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (view->output));
-  if (item->type == OUTPUT_ITEM_GROUP_OPEN)
-    tw = th = 0;
-  else if (win)
+  if (win)
     {
       view_item->drawing_area = drawing_area = gtk_drawing_area_new ();
 
     {
       view_item->drawing_area = drawing_area = gtk_drawing_area_new ();
 
@@ -473,13 +462,7 @@ psppire_output_view_put (struct psppire_output_view *view,
       else
         gtk_tree_store_append (store, &iter, NULL);
 
       else
         gtk_tree_store_append (store, &iter, NULL);
 
-      if (item->type == OUTPUT_ITEM_GROUP_OPEN)
-        {
-          gtk_tree_path_free (view->cur_group);
-          view->cur_group = gtk_tree_model_get_path (GTK_TREE_MODEL (store),
-                                                     &iter);
-        }
-
+      /* XXX group? */
       gtk_tree_store_set (store, &iter,
                           COL_LABEL, output_item_get_label (item),
                          COL_ADDR, item,
       gtk_tree_store_set (store, &iter,
                           COL_LABEL, output_item_get_label (item),
                          COL_ADDR, item,
@@ -1102,10 +1085,10 @@ psppire_output_view_submit (struct output_driver *this,
 
 static struct output_driver_class psppire_output_view_driver_class =
   {
 
 static struct output_driver_class psppire_output_view_driver_class =
   {
-    "PSPPIRE Output View",      /* name */
-    NULL,                       /* destroy */
-    psppire_output_view_submit, /* submit */
-    NULL,                       /* flush */
+    .name = "PSPPIRE Output View",
+    .submit = psppire_output_view_submit,
+    .handles_groups = true,
+    .handles_show = true,
   };
 
 void
   };
 
 void
index b7ed01a1916c143cc406518bb0fd983b96bf3256..99688e97e23f706bf50a5d030bc7fe7a8235640d 100644 (file)
@@ -156,10 +156,10 @@ psppire_output_submit (struct output_driver *this,
 
 static struct output_driver_class psppire_output_class =
   {
 
 static struct output_driver_class psppire_output_class =
   {
-    "PSPPIRE",                  /* name */
-    NULL,                       /* destroy */
-    psppire_output_submit,      /* submit */
-    NULL,                       /* flush */
+    .name = "PSPPIRE",
+    .submit = psppire_output_submit,
+    .handles_groups = true,
+    .handles_show = true,
   };
 
 void
   };
 
 void
index 4b40f94d1e04e0a055a58703ad21d47b66adfc8a..1475398b03b6725bb3bb162e48610f1410914460 100644 (file)
@@ -35,8 +35,6 @@
 #include "output/output-item.h"
 #include "output/pivot-table.h"
 #include "output/spv/spv.h"
 #include "output/output-item.h"
 #include "output/pivot-table.h"
 #include "output/spv/spv.h"
-#include "output/spv/spv-output.h"
-#include "output/spv/spv-select.h"
 
 #include "helper.h"
 #include "psppire-data-window.h"
 
 #include "helper.h"
 #include "psppire-data-window.h"
@@ -698,86 +696,11 @@ psppire_window_file_chooser_dialog (PsppireWindow *toplevel)
   return dialog;
 }
 
   return dialog;
 }
 
-struct item_path
-  {
-    const struct spv_item **nodes;
-    size_t n;
-
-#define N_STUB 10
-    const struct spv_item *stub[N_STUB];
-  };
-
-static void
-swap_nodes (const struct spv_item **a, const struct spv_item **b)
-{
-  const struct spv_item *tmp = *a;
-  *a = *b;
-  *b = tmp;
-}
-
-static void
-get_path (const struct spv_item *item, struct item_path *path)
-{
-  size_t allocated = 10;
-  path->nodes = path->stub;
-  path->n = 0;
-
-  while (item)
-    {
-      if (path->n >= allocated)
-        {
-          if (path->nodes == path->stub)
-            path->nodes = xmemdup (path->stub, sizeof path->stub);
-          path->nodes = x2nrealloc (path->nodes, &allocated,
-                                    sizeof *path->nodes);
-        }
-      path->nodes[path->n++] = item;
-      item = item->parent;
-    }
-
-  for (size_t i = 0; i < path->n / 2; i++)
-    swap_nodes (&path->nodes[i], &path->nodes[path->n - i - 1]);
-}
-
-static void
-free_path (struct item_path *path)
-{
-  if (path && path->nodes != path->stub)
-    free (path->nodes);
-}
-
-static void
-dump_heading_transition (const struct spv_item *old,
-                         const struct spv_item *new)
-{
-  if (old == new)
-    return;
-
-  struct item_path old_path, new_path;
-  get_path (old, &old_path);
-  get_path (new, &new_path);
-
-  size_t common = 0;
-  for (; common < old_path.n && common < new_path.n; common++)
-    if (old_path.nodes[common] != new_path.nodes[common])
-      break;
-
-  for (size_t i = common; i < old_path.n; i++)
-    output_item_submit (group_close_item_create ());
-  for (size_t i = common; i < new_path.n; i++)
-    output_item_submit (group_open_item_create (
-                              new_path.nodes[i]->command_id,
-                              new_path.nodes[i]->label));
-
-  free_path (&old_path);
-  free_path (&new_path);
-}
-
 void
 read_spv_file (const char *filename)
 {
 void
 read_spv_file (const char *filename)
 {
-  struct spv_reader *spv;
-  char *error = spv_open (filename, &spv);
+  struct output_item *root;
+  char *error = spv_read (filename, &root, NULL);
   if (error)
     {
       /* XXX */
   if (error)
     {
       /* XXX */
@@ -785,30 +708,7 @@ read_spv_file (const char *filename)
       return;
     }
 
       return;
     }
 
-  struct spv_item **items;
-  size_t n_items;
-  spv_select (spv, NULL, 0, &items, &n_items);
-  struct spv_item *prev_heading = spv_get_root (spv);
-  for (size_t i = 0; i < n_items; i++)
-    {
-      struct spv_item *heading
-        = items[i]->type == SPV_ITEM_HEADING ? items[i] : items[i]->parent;
-      dump_heading_transition (prev_heading, heading);
-      if (items[i]->type == SPV_ITEM_TEXT)
-        spv_text_submit (items[i]);
-      else if (items[i]->type == SPV_ITEM_TABLE)
-        pivot_table_submit (pivot_table_ref (spv_item_get_table (items[i])));
-      else if (items[i]->type == SPV_ITEM_IMAGE)
-        {
-          cairo_surface_t *image = spv_item_get_image (items[i]);
-          output_item_submit (image_item_create (cairo_surface_reference (
-                                                   image)));
-        }
-      prev_heading = heading;
-    }
-  dump_heading_transition (prev_heading, spv_get_root (spv));
-  free (items);
-  spv_close (spv);
+  output_item_submit_children (root);
 }
 
 /* Callback for the file_open action.
 }
 
 /* Callback for the file_open action.
index 65a20177d4e9ce4fe4587cfff9f47efd9522729b..3baa904f40b76adc49649a19bf164066c590756e 100644 (file)
@@ -17,20 +17,20 @@ AT_BANNER([pspp-output])
 
 AT_SETUP([pspp-output dir])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv], [0], [dnl
 
 AT_SETUP([pspp-output dir])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv], [0], [dnl
-- heading "Set" command "Set"
-- heading "Title" command "Title"
+- group "Set" command "Set"
+- group "Title" command "Title"
     - text "Page Title" command "Title"
     - text "Page Title" command "Title"
-- heading "Data List" command "Data List"
+- group "Data List" command "Data List"
     - table "Reading 1 record from INLINE." command "Data List" subtype "Fixed Data Records"
     - table "Reading 1 record from INLINE." command "Data List" subtype "Fixed Data Records"
-- heading "Begin Data" command "Begin Data"
-- heading "List" command "List"
+- group "Begin Data" command "Begin Data"
+- group "List" command "List"
     - table "Data List" command "List"
     - table "Data List" command "List"
-- heading "Frequencies" command "Frequencies"
+- group "Frequencies" command "Frequencies"
     - table "Statistics" command "Frequencies"
     - table "v0" command "Frequencies" subtype "Frequencies"
     - table "v1" command "Frequencies" subtype "Frequencies"
     - table "v2" command "Frequencies" subtype "Frequencies"
     - table "Statistics" command "Frequencies"
     - table "v0" command "Frequencies" subtype "Frequencies"
     - table "v1" command "Frequencies" subtype "Frequencies"
     - table "v2" command "Frequencies" subtype "Frequencies"
-- heading "Regression" command "Regression"
+- group "Regression" command "Regression"
     - table "Model Summary (v2)" command "Regression" subtype "Model Summary"
     - table "ANOVA (v2)" command "Regression" subtype "ANOVA"
     - table "Coefficients (v2)" command "Regression" subtype "Coefficients"
     - table "Model Summary (v2)" command "Regression" subtype "Model Summary"
     - table "ANOVA (v2)" command "Regression" subtype "ANOVA"
     - table "Coefficients (v2)" command "Regression" subtype "Coefficients"
@@ -38,38 +38,40 @@ AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv], [0], [dnl
 AT_CLEANUP
 
 AT_SETUP([pspp-output --select equal])
 AT_CLEANUP
 
 AT_SETUP([pspp-output --select equal])
-AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --select=headings],
+AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --select=tables],
   [0], [dnl
   [0], [dnl
-- heading "Set" command "Set"
-- heading "Title" command "Title"
-- heading "Data List" command "Data List"
-- heading "Begin Data" command "Begin Data"
-- heading "List" command "List"
-- heading "Frequencies" command "Frequencies"
-- heading "Regression" command "Regression"
+- table "Reading 1 record from INLINE." command "Data List" subtype "Fixed Data Records"
+- table "Data List" command "List"
+- table "Statistics" command "Frequencies"
+- table "v0" command "Frequencies" subtype "Frequencies"
+- table "v1" command "Frequencies" subtype "Frequencies"
+- table "v2" command "Frequencies" subtype "Frequencies"
+- table "Model Summary (v2)" command "Regression" subtype "Model Summary"
+- table "ANOVA (v2)" command "Regression" subtype "ANOVA"
+- table "Coefficients (v2)" command "Regression" subtype "Coefficients"
 ])
 AT_CLEANUP
 
 AT_SETUP([pspp-output --select unequal])
 ])
 AT_CLEANUP
 
 AT_SETUP([pspp-output --select unequal])
-AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --select=^headings],
+AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --select=^outlineheaders],
   [0], [dnl
   [0], [dnl
-    - text "Page Title" command "Title"
-    - table "Reading 1 record from INLINE." command "Data List" subtype "Fixed Data Records"
-    - table "Data List" command "List"
-    - table "Statistics" command "Frequencies"
-    - table "v0" command "Frequencies" subtype "Frequencies"
-    - table "v1" command "Frequencies" subtype "Frequencies"
-    - table "v2" command "Frequencies" subtype "Frequencies"
-    - table "Model Summary (v2)" command "Regression" subtype "Model Summary"
-    - table "ANOVA (v2)" command "Regression" subtype "ANOVA"
-    - table "Coefficients (v2)" command "Regression" subtype "Coefficients"
+- text "Page Title" command "Title"
+- table "Reading 1 record from INLINE." command "Data List" subtype "Fixed Data Records"
+- table "Data List" command "List"
+- table "Statistics" command "Frequencies"
+- table "v0" command "Frequencies" subtype "Frequencies"
+- table "v1" command "Frequencies" subtype "Frequencies"
+- table "v2" command "Frequencies" subtype "Frequencies"
+- table "Model Summary (v2)" command "Regression" subtype "Model Summary"
+- table "ANOVA (v2)" command "Regression" subtype "ANOVA"
+- table "Coefficients (v2)" command "Regression" subtype "Coefficients"
 ])
 AT_CLEANUP
 
 AT_SETUP([pspp-output --commands equal])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --commands='reg*'],
   [0], [dnl
 ])
 AT_CLEANUP
 
 AT_SETUP([pspp-output --commands equal])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --commands='reg*'],
   [0], [dnl
-- heading "Regression" command "Regression"
+- group "Regression" command "Regression"
     - table "Model Summary (v2)" command "Regression" subtype "Model Summary"
     - table "ANOVA (v2)" command "Regression" subtype "ANOVA"
     - table "Coefficients (v2)" command "Regression" subtype "Coefficients"
     - table "Model Summary (v2)" command "Regression" subtype "Model Summary"
     - table "ANOVA (v2)" command "Regression" subtype "ANOVA"
     - table "Coefficients (v2)" command "Regression" subtype "Coefficients"
@@ -79,15 +81,15 @@ AT_CLEANUP
 AT_SETUP([pspp-output --commands unequal])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --commands='^reg*'],
   [0], [dnl
 AT_SETUP([pspp-output --commands unequal])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --commands='^reg*'],
   [0], [dnl
-- heading "Set" command "Set"
-- heading "Title" command "Title"
+- group "Set" command "Set"
+- group "Title" command "Title"
     - text "Page Title" command "Title"
     - text "Page Title" command "Title"
-- heading "Data List" command "Data List"
+- group "Data List" command "Data List"
     - table "Reading 1 record from INLINE." command "Data List" subtype "Fixed Data Records"
     - table "Reading 1 record from INLINE." command "Data List" subtype "Fixed Data Records"
-- heading "Begin Data" command "Begin Data"
-- heading "List" command "List"
+- group "Begin Data" command "Begin Data"
+- group "List" command "List"
     - table "Data List" command "List"
     - table "Data List" command "List"
-- heading "Frequencies" command "Frequencies"
+- group "Frequencies" command "Frequencies"
     - table "Statistics" command "Frequencies"
     - table "v0" command "Frequencies" subtype "Frequencies"
     - table "v1" command "Frequencies" subtype "Frequencies"
     - table "Statistics" command "Frequencies"
     - table "v0" command "Frequencies" subtype "Frequencies"
     - table "v1" command "Frequencies" subtype "Frequencies"
@@ -98,10 +100,10 @@ AT_CLEANUP
 AT_SETUP([pspp-output --nth-commands])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --nth-commands=2,4,6],
   [0], [dnl
 AT_SETUP([pspp-output --nth-commands])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --nth-commands=2,4,6],
   [0], [dnl
-- heading "Title" command "Title"
+- group "Title" command "Title"
     - text "Page Title" command "Title"
     - text "Page Title" command "Title"
-- heading "Begin Data" command "Begin Data"
-- heading "Frequencies" command "Frequencies"
+- group "Begin Data" command "Begin Data"
+- group "Frequencies" command "Frequencies"
     - table "Statistics" command "Frequencies"
     - table "v0" command "Frequencies" subtype "Frequencies"
     - table "v1" command "Frequencies" subtype "Frequencies"
     - table "Statistics" command "Frequencies"
     - table "v0" command "Frequencies" subtype "Frequencies"
     - table "v1" command "Frequencies" subtype "Frequencies"
@@ -112,26 +114,26 @@ AT_CLEANUP
 AT_SETUP([pspp-output --subtypes equal])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --subtypes='freq*'],
   [0], [dnl
 AT_SETUP([pspp-output --subtypes equal])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --subtypes='freq*'],
   [0], [dnl
-    - table "v0" command "Frequencies" subtype "Frequencies"
-    - table "v1" command "Frequencies" subtype "Frequencies"
-    - table "v2" command "Frequencies" subtype "Frequencies"
+- table "v0" command "Frequencies" subtype "Frequencies"
+- table "v1" command "Frequencies" subtype "Frequencies"
+- table "v2" command "Frequencies" subtype "Frequencies"
 ])
 AT_CLEANUP
 
 AT_SETUP([pspp-output --subtypes unequal])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --subtypes='^freq*'],
   [0], [dnl
 ])
 AT_CLEANUP
 
 AT_SETUP([pspp-output --subtypes unequal])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --subtypes='^freq*'],
   [0], [dnl
-- heading "Set" command "Set"
-- heading "Title" command "Title"
+- group "Set" command "Set"
+- group "Title" command "Title"
     - text "Page Title" command "Title"
     - text "Page Title" command "Title"
-- heading "Data List" command "Data List"
+- group "Data List" command "Data List"
     - table "Reading 1 record from INLINE." command "Data List" subtype "Fixed Data Records"
     - table "Reading 1 record from INLINE." command "Data List" subtype "Fixed Data Records"
-- heading "Begin Data" command "Begin Data"
-- heading "List" command "List"
+- group "Begin Data" command "Begin Data"
+- group "List" command "List"
     - table "Data List" command "List"
     - table "Data List" command "List"
-- heading "Frequencies" command "Frequencies"
+- group "Frequencies" command "Frequencies"
     - table "Statistics" command "Frequencies"
     - table "Statistics" command "Frequencies"
-- heading "Regression" command "Regression"
+- group "Regression" command "Regression"
     - table "Model Summary (v2)" command "Regression" subtype "Model Summary"
     - table "ANOVA (v2)" command "Regression" subtype "ANOVA"
     - table "Coefficients (v2)" command "Regression" subtype "Coefficients"
     - table "Model Summary (v2)" command "Regression" subtype "Model Summary"
     - table "ANOVA (v2)" command "Regression" subtype "ANOVA"
     - table "Coefficients (v2)" command "Regression" subtype "Coefficients"
@@ -141,27 +143,27 @@ AT_CLEANUP
 AT_SETUP([pspp-output --labels equal])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --labels='v*'],
   [0], [dnl
 AT_SETUP([pspp-output --labels equal])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --labels='v*'],
   [0], [dnl
-    - table "v0" command "Frequencies" subtype "Frequencies"
-    - table "v1" command "Frequencies" subtype "Frequencies"
-    - table "v2" command "Frequencies" subtype "Frequencies"
+- table "v0" command "Frequencies" subtype "Frequencies"
+- table "v1" command "Frequencies" subtype "Frequencies"
+- table "v2" command "Frequencies" subtype "Frequencies"
 ])
 AT_CLEANUP
 
 AT_SETUP([pspp-output --labels unequal])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --labels='^data*'],
   [0], [dnl
 ])
 AT_CLEANUP
 
 AT_SETUP([pspp-output --labels unequal])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --labels='^data*'],
   [0], [dnl
-- heading "Set" command "Set"
-- heading "Title" command "Title"
+- group "Set" command "Set"
+- group "Title" command "Title"
     - text "Page Title" command "Title"
     - text "Page Title" command "Title"
-    - table "Reading 1 record from INLINE." command "Data List" subtype "Fixed Data Records"
-- heading "Begin Data" command "Begin Data"
-- heading "List" command "List"
-- heading "Frequencies" command "Frequencies"
+- table "Reading 1 record from INLINE." command "Data List" subtype "Fixed Data Records"
+- group "Begin Data" command "Begin Data"
+- group "List" command "List"
+- group "Frequencies" command "Frequencies"
     - table "Statistics" command "Frequencies"
     - table "v0" command "Frequencies" subtype "Frequencies"
     - table "v1" command "Frequencies" subtype "Frequencies"
     - table "v2" command "Frequencies" subtype "Frequencies"
     - table "Statistics" command "Frequencies"
     - table "v0" command "Frequencies" subtype "Frequencies"
     - table "v1" command "Frequencies" subtype "Frequencies"
     - table "v2" command "Frequencies" subtype "Frequencies"
-- heading "Regression" command "Regression"
+- group "Regression" command "Regression"
     - table "Model Summary (v2)" command "Regression" subtype "Model Summary"
     - table "ANOVA (v2)" command "Regression" subtype "ANOVA"
     - table "Coefficients (v2)" command "Regression" subtype "Coefficients"
     - table "Model Summary (v2)" command "Regression" subtype "Model Summary"
     - table "ANOVA (v2)" command "Regression" subtype "ANOVA"
     - table "Coefficients (v2)" command "Regression" subtype "Coefficients"
@@ -171,11 +173,11 @@ AT_CLEANUP
 AT_SETUP([pspp-output --instances])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --instances=1],
   [0], [dnl
 AT_SETUP([pspp-output --instances])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --instances=1],
   [0], [dnl
-    - text "Page Title" command "Title"
-    - table "Reading 1 record from INLINE." command "Data List" subtype "Fixed Data Records"
-    - table "Data List" command "List"
-    - table "Statistics" command "Frequencies"
-    - table "Model Summary (v2)" command "Regression" subtype "Model Summary"
+- text "Page Title" command "Title"
+- table "Reading 1 record from INLINE." command "Data List" subtype "Fixed Data Records"
+- table "Data List" command "List"
+- table "Statistics" command "Frequencies"
+- table "Model Summary (v2)" command "Regression" subtype "Model Summary"
 ])
 AT_CLEANUP
 
 ])
 AT_CLEANUP
 
@@ -183,11 +185,11 @@ AT_SETUP([pspp-output --instances=last])
 AT_KEYWORDS([--instances last])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --instances=last],
   [0], [dnl
 AT_KEYWORDS([--instances last])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --instances=last],
   [0], [dnl
-    - text "Page Title" command "Title"
-    - table "Reading 1 record from INLINE." command "Data List" subtype "Fixed Data Records"
-    - table "Data List" command "List"
-    - table "v2" command "Frequencies" subtype "Frequencies"
-    - table "Coefficients (v2)" command "Regression" subtype "Coefficients"
+- text "Page Title" command "Title"
+- table "Reading 1 record from INLINE." command "Data List" subtype "Fixed Data Records"
+- table "Data List" command "List"
+- table "v2" command "Frequencies" subtype "Frequencies"
+- table "Coefficients (v2)" command "Regression" subtype "Coefficients"
 ])
 AT_CLEANUP
 
 ])
 AT_CLEANUP
 
@@ -195,18 +197,18 @@ dnl XXX Currently PSPP doesn't output hidden items so no tests
 dnl XXX for --show-hidden.
 
 AT_SETUP([pspp-output --or])
 dnl XXX for --show-hidden.
 
 AT_SETUP([pspp-output --or])
-AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --select=headings --or --labels='v*'],
+AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --select=outlineheaders --or --labels='v*'],
   [0], [dnl
   [0], [dnl
-- heading "Set" command "Set"
-- heading "Title" command "Title"
-- heading "Data List" command "Data List"
-- heading "Begin Data" command "Begin Data"
-- heading "List" command "List"
-- heading "Frequencies" command "Frequencies"
+- group "Set" command "Set"
+- group "Title" command "Title"
+- group "Data List" command "Data List"
+- group "Begin Data" command "Begin Data"
+- group "List" command "List"
+- group "Frequencies" command "Frequencies"
     - table "v0" command "Frequencies" subtype "Frequencies"
     - table "v1" command "Frequencies" subtype "Frequencies"
     - table "v2" command "Frequencies" subtype "Frequencies"
     - table "v0" command "Frequencies" subtype "Frequencies"
     - table "v1" command "Frequencies" subtype "Frequencies"
     - table "v2" command "Frequencies" subtype "Frequencies"
-- heading "Regression" command "Regression"
+- group "Regression" command "Regression"
 ])
 AT_CLEANUP
 
 ])
 AT_CLEANUP
 
index 755ba7ee65ba56d85d31571a7a6022d6a749464a..3f1fcb81d41282e801c3850c3004caa6517f1893 100644 (file)
 #include "libpspp/message.h"
 #include "libpspp/string-map.h"
 #include "libpspp/string-set.h"
 #include "libpspp/message.h"
 #include "libpspp/string-map.h"
 #include "libpspp/string-set.h"
+#include "libpspp/zip-reader.h"
 #include "output/driver.h"
 #include "output/output-item.h"
 #include "output/pivot-table.h"
 #include "output/driver.h"
 #include "output/output-item.h"
 #include "output/pivot-table.h"
+#include "output/page-setup.h"
+#include "output/select.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/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.h"
 
 #include "output/spv/spv-table-look.h"
 #include "output/spv/spv.h"
 
@@ -62,7 +63,7 @@ static struct string_map output_options
 static bool show_member_names;
 
 /* --show-hidden, --select, --commands, ...: Selection criteria. */
 static bool show_member_names;
 
 /* --show-hidden, --select, --commands, ...: Selection criteria. */
-static struct spv_criteria *criteria;
+static struct output_criteria *criteria;
 static size_t n_criteria, allocated_criteria;
 
 /* --or: Add new element to 'criteria' array. */
 static size_t n_criteria, allocated_criteria;
 
 /* --or: Add new element to 'criteria' array. */
@@ -95,102 +96,85 @@ static void usage (void);
 static void developer_usage (void);
 static void parse_options (int argc, char **argv);
 
 static void developer_usage (void);
 static void parse_options (int argc, char **argv);
 
-static void
-dump_item (const struct spv_item *item)
+static struct output_item *
+annotate_member_names (const struct output_item *in)
 {
 {
-  if (show_member_names && (item->xml_member || 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
-                 ? xasprintf ("%s and %s:", x, b)
-                 : xasprintf ("%s:", x ? x : b));
-      output_item_submit (text_item_create_nocopy (TEXT_ITEM_TITLE, s,
-                                                   xstrdup ("Member Names")));
-    }
-
-  switch (spv_item_get_type (item))
+  if (in->type == OUTPUT_ITEM_GROUP)
     {
     {
-    case SPV_ITEM_HEADING:
-      break;
-
-    case SPV_ITEM_TEXT:
-      spv_text_submit (item);
-      break;
-
-    case SPV_ITEM_TABLE:
-      pivot_table_submit (pivot_table_ref (spv_item_get_table (item)));
-      break;
-
-    case SPV_ITEM_GRAPH:
-      break;
-
-    case SPV_ITEM_MODEL:
-      break;
-
-    case SPV_ITEM_IMAGE:
-      output_item_submit (image_item_create (cairo_surface_reference (
-                                               spv_item_get_image (item))));
-      break;
-
-    case SPV_ITEM_TREE:
-      break;
+      struct output_item *out = group_item_clone_empty (in);
+      for (size_t i = 0; i < in->group.n_children; i++)
+        {
+          const struct output_item *item = in->group.children[i];
+          const char *members[4];
+          size_t n = spv_info_get_members (item->spv_info, members,
+                                           sizeof members / sizeof *members);
+          if (n)
+            {
+              struct string s = DS_EMPTY_INITIALIZER;
+              ds_put_cstr (&s, members[0]);
+              for (size_t i = 1; i < n; i++)
+                ds_put_format (&s, " and %s", members[i]);
+              group_item_add_child (out, text_item_create_nocopy (
+                                      TEXT_ITEM_TITLE, ds_steal_cstr (&s),
+                                      xstrdup ("Member Names")));
+            }
 
 
-    default:
-      abort ();
+          group_item_add_child (out, output_item_ref (item));
+        }
+      return out;
     }
     }
+  else
+    return output_item_ref (in);
 }
 
 static void
 }
 
 static void
-print_item_directory (const struct spv_item *item)
+print_item_directory (const struct output_item *item, int level)
 {
 {
-  for (int i = 1; i < spv_item_get_level (item); i++)
+  for (int i = 0; i < level; i++)
     printf ("    ");
 
     printf ("    ");
 
-  enum spv_item_type type = spv_item_get_type (item);
-  printf ("- %s", spv_item_type_to_string (type));
+  printf ("- %s", output_item_type_to_string (item->type));
 
 
-  const char *label = spv_item_get_label (item);
+  const char *label = output_item_get_label (item);
   if (label)
     printf (" \"%s\"", label);
 
   if (label)
     printf (" \"%s\"", label);
 
-  if (type == SPV_ITEM_TABLE)
+  if (item->type == OUTPUT_ITEM_TABLE)
     {
     {
-      const struct pivot_table *table = spv_item_get_table (item);
-      char *title = pivot_value_to_string (table->title, table);
+      char *title = pivot_value_to_string (item->table->title, item->table);
       if (!label || strcmp (title, label))
         printf (" title \"%s\"", title);
       free (title);
     }
 
       if (!label || strcmp (title, label))
         printf (" title \"%s\"", title);
       free (title);
     }
 
-  const char *command_id = spv_item_get_command_id (item);
-  if (command_id)
-    printf (" command \"%s\"", command_id);
+  if (item->command_name)
+    printf (" command \"%s\"", item->command_name);
 
 
-  const char *subtype = spv_item_get_subtype (item);
-  if (subtype && (!label || strcmp (label, subtype)))
-    printf (" subtype \"%s\"", subtype);
+  char *subtype = output_item_get_subtype (item);
+  if (subtype)
+    {
+      if (!label || strcmp (label, subtype))
+        printf (" subtype \"%s\"", subtype);
+      free (subtype);
+    }
 
 
-  if (!spv_item_is_visible (item))
-    printf (" (hidden)");
+  if (!item->show)
+    printf (" (%s)", item->type == OUTPUT_ITEM_GROUP ? "collapsed" : "hidden");
 
   if (show_member_names)
     {
 
   if (show_member_names)
     {
-      const char *members[] = {
-        item->xml_member,
-        item->bin_member,
-        item->png_member,
-      };
-      size_t n = 0;
+      const char *members[4];
+      size_t n = spv_info_get_members (item->spv_info, members,
+                                       sizeof members / sizeof *members);
 
 
-      for (size_t i = 0; i < sizeof members / sizeof *members; i++)
-        if (members[i])
-          printf (" %s %s", n++ == 0 ? "in" : "and", members[i]);
+      for (size_t i = 0; i < n; i++)
+          printf (" %s %s", i == 0 ? "in" : "and", members[i]);
     }
   putchar ('\n');
     }
   putchar ('\n');
+
+  if (item->type == OUTPUT_ITEM_GROUP)
+    for (size_t i = 0; i < item->group.n_children; i++)
+      print_item_directory (item->group.children[i], level + 1);
 }
 
 static void
 }
 
 static void
@@ -201,109 +185,49 @@ run_detect (int argc UNUSED, char **argv)
     error (1, 0, "%s", err);
 }
 
     error (1, 0, "%s", err);
 }
 
-static void
-run_directory (int argc UNUSED, char **argv)
+static struct output_item *
+read_and_filter_spv (const char *name, struct page_setup **psp)
 {
 {
-  struct spv_reader *spv;
-  char *err = spv_open (argv[1], &spv);
+  struct output_item *root;
+  char *err = spv_read (name, &root, psp);
   if (err)
     error (1, 0, "%s", err);
   if (err)
     error (1, 0, "%s", err);
-
-  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++)
-    print_item_directory (items[i]);
-  free (items);
-
-  spv_close (spv);
-}
-
-struct item_path
-  {
-    const struct spv_item **nodes;
-    size_t n;
-
-#define N_STUB 10
-    const struct spv_item *stub[N_STUB];
-  };
-
-static void
-swap_nodes (const struct spv_item **a, const struct spv_item **b)
-{
-  const struct spv_item *tmp = *a;
-  *a = *b;
-  *b = tmp;
-}
-
-static void
-get_path (const struct spv_item *item, struct item_path *path)
-{
-  size_t allocated = 10;
-  path->nodes = path->stub;
-  path->n = 0;
-
-  while (item)
-    {
-      if (path->n >= allocated)
-        {
-          if (path->nodes == path->stub)
-            path->nodes = xmemdup (path->stub, sizeof path->stub);
-          path->nodes = x2nrealloc (path->nodes, &allocated,
-                                    sizeof *path->nodes);
-        }
-      path->nodes[path->n++] = item;
-      item = item->parent;
-    }
-
-  for (size_t i = 0; i < path->n / 2; i++)
-    swap_nodes (&path->nodes[i], &path->nodes[path->n - i - 1]);
+  return output_select (root, criteria, n_criteria);
 }
 
 static void
 }
 
 static void
-free_path (struct item_path *path)
+run_directory (int argc UNUSED, char **argv)
 {
 {
-  if (path && path->nodes != path->stub)
-    free (path->nodes);
+  struct output_item *root = read_and_filter_spv (argv[1], NULL);
+  for (size_t i = 0; i < root->group.n_children; i++)
+    print_item_directory (root->group.children[i], 0);
+  output_item_unref (root);
 }
 
 static void
 }
 
 static void
-dump_heading_transition (const struct spv_item *old,
-                         const struct spv_item *new)
+set_table_look_recursively (struct output_item *item,
+                            const struct pivot_table_look *look)
 {
 {
-  if (old == new)
-    return;
-
-  struct item_path old_path, new_path;
-  get_path (old, &old_path);
-  get_path (new, &new_path);
-
-  size_t common = 0;
-  for (; common < old_path.n && common < new_path.n; common++)
-    if (old_path.nodes[common] != new_path.nodes[common])
-      break;
-
-  for (size_t i = common; i < old_path.n; i++)
-    output_item_submit (group_close_item_create ());
-  for (size_t i = common; i < new_path.n; i++)
-    output_item_submit (group_open_item_create (
-                          new_path.nodes[i]->command_id,
-                          new_path.nodes[i]->label));
-
-  free_path (&old_path);
-  free_path (&new_path);
+  if (item->type == OUTPUT_ITEM_TABLE)
+    pivot_table_set_look (item->table, look);
+  else if (item->type == OUTPUT_ITEM_GROUP)
+    for (size_t i = 0; i < item->group.n_children; i++)
+      set_table_look_recursively (item->group.children[i], look);
 }
 
 static void
 run_convert (int argc UNUSED, char **argv)
 {
 }
 
 static void
 run_convert (int argc UNUSED, char **argv)
 {
-  struct spv_reader *spv;
-  char *err = spv_open (argv[1], &spv);
-  if (err)
-    error (1, 0, "%s", err);
-
+  struct page_setup *ps;
+  struct output_item *root = read_and_filter_spv (argv[1], &ps);
   if (table_look)
   if (table_look)
-    spv_item_set_table_look (spv_get_root (spv), table_look);
+    set_table_look_recursively (root, table_look);
+  if (show_member_names)
+    {
+      struct output_item *new_root = annotate_member_names (root);
+      output_item_unref (root);
+      root = new_root;
+    }
 
   output_engine_push ();
   output_set_filename (argv[1]);
 
   output_engine_push ();
   output_set_filename (argv[1]);
@@ -313,26 +237,12 @@ run_convert (int argc UNUSED, char **argv)
     exit (EXIT_FAILURE);
   output_driver_register (driver);
 
     exit (EXIT_FAILURE);
   output_driver_register (driver);
 
-  const struct page_setup *ps = spv_get_page_setup (spv);
   if (ps)
   if (ps)
-    output_item_submit (page_setup_item_create (ps));
-
-  struct spv_item **items;
-  size_t n_items;
-  spv_select (spv, criteria, n_criteria, &items, &n_items);
-  struct spv_item *prev_heading = spv_get_root (spv);
-  for (size_t i = 0; i < n_items; i++)
     {
     {
-      struct spv_item *heading
-        = items[i]->type == SPV_ITEM_HEADING ? items[i] : items[i]->parent;
-      dump_heading_transition (prev_heading, heading);
-      dump_item (items[i]);
-      prev_heading = heading;
+      output_item_submit (page_setup_item_create (ps));
+      page_setup_destroy (ps);
     }
     }
-  dump_heading_transition (prev_heading, spv_get_root (spv));
-  free (items);
-
-  spv_close (spv);
+  output_item_submit_children (root);
 
   output_engine_pop ();
   fh_done ();
 
   output_engine_pop ();
   fh_done ();
@@ -346,20 +256,19 @@ run_convert (int argc UNUSED, char **argv)
 }
 
 static const struct pivot_table *
 }
 
 static const struct pivot_table *
-get_first_table (const struct spv_reader *spv)
+get_first_table (const struct output_item *item)
 {
 {
-  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]))
+  if (item->type == OUTPUT_ITEM_TABLE)
+    return item->table;
+  else if (item->type == OUTPUT_ITEM_GROUP)
+    for (size_t i = 0; i < item->group.n_children; i++)
       {
       {
-        free (items);
-        return spv_item_get_table (items[i]);
+        const struct pivot_table *table
+          = get_first_table (item->group.children[i]);
+        if (table)
+          return table;
       }
 
       }
 
-  free (items);
   return NULL;
 }
 
   return NULL;
 }
 
@@ -369,18 +278,14 @@ run_get_table_look (int argc UNUSED, char **argv)
   struct pivot_table_look *look;
   if (strcmp (argv[1], "-"))
     {
   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);
+      struct output_item *root = read_and_filter_spv (argv[1], NULL);
+      const struct pivot_table *table = get_first_table (root);
       if (!table)
         error (1, 0, "%s: no tables found", argv[1]);
 
       look = pivot_table_look_ref (pivot_table_get_look (table));
 
       if (!table)
         error (1, 0, "%s: no tables found", argv[1]);
 
       look = pivot_table_look_ref (pivot_table_get_look (table));
 
-      spv_close (spv);
+      output_item_unref (root);
     }
   else
     look = pivot_table_look_ref (pivot_table_look_builtin_default ());
     }
   else
     look = pivot_table_look_ref (pivot_table_look_builtin_default ());
@@ -411,23 +316,9 @@ run_convert_table_look (int argc UNUSED, char **argv)
 static void
 run_dump (int argc UNUSED, char **argv)
 {
 static void
 run_dump (int argc UNUSED, char **argv)
 {
-  struct spv_reader *spv;
-  char *err = spv_open (argv[1], &spv);
-  if (err)
-    error (1, 0, "%s", err);
-
-  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 (items[i]->type == SPV_ITEM_TABLE)
-      {
-        pivot_table_dump (spv_item_get_table (items[i]), 0);
-        putchar ('\n');
-      }
-  free (items);
-
-  spv_close (spv);
+  struct output_item *root = read_and_filter_spv (argv[1], NULL);
+  output_item_dump (root, 0);
+  output_item_unref (root);
 }
 
 static int
 }
 
 static int
@@ -452,117 +343,108 @@ compare_cells (const void *a_, const void *b_)
   return a < b ? -1 : a > b;
 }
 
   return a < b ? -1 : a > b;
 }
 
+static char * WARN_UNUSED_RESULT
+dump_raw (struct zip_reader *zr, const char *member_name)
+{
+  void *data;
+  size_t size;
+  char *error = zip_member_read_all (zr, member_name, &data, &size);
+  if (!error)
+    {
+      fwrite (data, size, 1, stdout);
+      free (data);
+    }
+  return error;
+}
+
+static void
+dump_light_table (const struct output_item *item)
+{
+  char *error;
+  if (raw)
+    error = dump_raw (item->spv_info->zip_reader,
+                      item->spv_info->bin_member);
+  else
+    {
+      struct spvlb_table *table;
+      error = spv_read_light_table (item->spv_info->zip_reader,
+                                    item->spv_info->bin_member, &table);
+      if (!error)
+        {
+          if (sort)
+            {
+              qsort (table->borders->borders, table->borders->n_borders,
+                     sizeof *table->borders->borders, compare_borders);
+              qsort (table->cells->cells, table->cells->n_cells,
+                     sizeof *table->cells->cells, compare_cells);
+            }
+          spvlb_print_table (item->spv_info->bin_member, 0, table);
+          spvlb_free_table (table);
+        }
+    }
+  if (error)
+    {
+      msg (ME, "%s", error);
+      free (error);
+    }
+}
+
 static void
 run_dump_light_table (int argc UNUSED, char **argv)
 {
   if (raw && isatty (STDOUT_FILENO))
     error (1, 0, "not writing binary data to tty");
 
 static void
 run_dump_light_table (int argc UNUSED, char **argv)
 {
   if (raw && isatty (STDOUT_FILENO))
     error (1, 0, "not writing binary data to tty");
 
-  struct spv_reader *spv;
-  char *err = spv_open (argv[1], &spv);
-  if (err)
-    error (1, 0, "%s", err);
+  struct output_item *root = read_and_filter_spv (argv[1], NULL);
+  struct output_iterator iter;
+  OUTPUT_ITEM_FOR_EACH (&iter, root)
+    if (iter.cur->type == OUTPUT_ITEM_TABLE && !iter.cur->spv_info->xml_member)
+      dump_light_table (iter.cur);
+  output_item_unref (root);
+}
 
 
-  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++)
+static void
+dump_legacy_data (const struct output_item *item)
+{
+  char *error;
+  if (raw)
+    error = dump_raw (item->spv_info->zip_reader,
+                      item->spv_info->bin_member);
+  else
     {
     {
-      if (!spv_item_is_light_table (items[i]))
-        continue;
-
-      char *error;
-      if (raw)
-        {
-          void *data;
-          size_t size;
-          error = spv_item_get_raw_light_table (items[i], &data, &size);
-          if (!error)
-            {
-              fwrite (data, size, 1, stdout);
-              free (data);
-            }
-        }
-      else
+      struct spv_data data;
+      error = spv_read_legacy_data (item->spv_info->zip_reader,
+                                    item->spv_info->bin_member, &data);
+      if (!error)
         {
         {
-          struct spvlb_table *table;
-          error = spv_item_get_light_table (items[i], &table);
-          if (!error)
-            {
-              if (sort)
-                {
-                  qsort (table->borders->borders, table->borders->n_borders,
-                         sizeof *table->borders->borders, compare_borders);
-                  qsort (table->cells->cells, table->cells->n_cells,
-                         sizeof *table->cells->cells, compare_cells);
-                }
-              spvlb_print_table (items[i]->bin_member, 0, table);
-              spvlb_free_table (table);
-            }
-        }
-      if (error)
-        {
-          msg (ME, "%s", error);
-          free (error);
+          printf ("%s:\n", item->spv_info->bin_member);
+          spv_data_dump (&data, stdout);
+          spv_data_uninit (&data);
+          printf ("\n");
         }
     }
 
         }
     }
 
-  free (items);
-
-  spv_close (spv);
+  if (error)
+    {
+      msg (ME, "%s", error);
+      free (error);
+    }
 }
 
 static void
 run_dump_legacy_data (int argc UNUSED, char **argv)
 {
 }
 
 static void
 run_dump_legacy_data (int argc UNUSED, char **argv)
 {
-  struct spv_reader *spv;
-  char *err = spv_open (argv[1], &spv);
-  if (err)
-    error (1, 0, "%s", err);
-
   if (raw && isatty (STDOUT_FILENO))
     error (1, 0, "not writing binary data to tty");
 
   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);
-  for (size_t i = 0; i < n_items; i++)
-    if (spv_item_is_legacy_table (items[i]))
-      {
-        struct spv_data data;
-        char *error;
-        if (raw)
-          {
-            void *data;
-            size_t size;
-            error = spv_item_get_raw_legacy_data (items[i], &data, &size);
-            if (!error)
-              {
-                fwrite (data, size, 1, stdout);
-                free (data);
-              }
-          }
-        else
-          {
-            error = spv_item_get_legacy_data (items[i], &data);
-            if (!error)
-              {
-                printf ("%s:\n", items[i]->bin_member);
-                spv_data_dump (&data, stdout);
-                spv_data_uninit (&data);
-                printf ("\n");
-              }
-          }
-
-        if (error)
-          {
-            msg (ME, "%s", error);
-            free (error);
-          }
-      }
-  free (items);
-
-  spv_close (spv);
+  struct output_item *root = read_and_filter_spv (argv[1], NULL);
+  struct output_iterator iter;
+  OUTPUT_ITEM_FOR_EACH (&iter, root)
+    if (iter.cur->type == OUTPUT_ITEM_TABLE
+        && iter.cur->spv_info->xml_member
+        && iter.cur->spv_info->bin_member)
+      dump_legacy_data (iter.cur);
+  output_item_unref (root);
 }
 
 /* This is really bogus.
 }
 
 /* This is really bogus.
@@ -671,77 +553,78 @@ dump_xml (int argc, char **argv, const char *member_name,
 }
 
 static void
 }
 
 static void
-run_dump_legacy_table (int argc, char **argv)
+dump_legacy_table (int argc, char **argv, const struct output_item *item)
 {
 {
-  struct spv_reader *spv;
-  char *err = spv_open (argv[1], &spv);
-  if (err)
-    error (1, 0, "%s", err);
+  xmlDoc *doc;
+  char *error_s = spv_read_xml_member (item->spv_info->zip_reader,
+                                       item->spv_info->xml_member,
+                                       false, "visualization", &doc);
+  dump_xml (argc, argv, item->spv_info->xml_member, error_s, doc);
+}
 
 
-  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_legacy_table (items[i]))
-      {
-        xmlDoc *doc;
-        char *error_s = spv_item_get_legacy_table (items[i], &doc);
-        dump_xml (argc, argv, items[i]->xml_member, error_s, doc);
-      }
-  free (items);
+static void
+run_dump_legacy_table (int argc, char **argv)
+{
+  struct output_item *root = read_and_filter_spv (argv[1], NULL);
+  struct output_iterator iter;
+  OUTPUT_ITEM_FOR_EACH (&iter, root)
+    if (iter.cur->type == OUTPUT_ITEM_TABLE
+        && iter.cur->spv_info->xml_member)
+      dump_legacy_table (argc, argv, iter.cur);
+  output_item_unref (root);
+}
 
 
-  spv_close (spv);
+static void
+dump_structure (int argc, char **argv, const struct output_item *item)
+{
+  xmlDoc *doc;
+  char *error_s = spv_read_xml_member (item->spv_info->zip_reader,
+                                       item->spv_info->structure_member,
+                                       true, "heading", &doc);
+  dump_xml (argc, argv, item->spv_info->structure_member, error_s, doc);
 }
 
 static void
 run_dump_structure (int argc, char **argv)
 {
 }
 
 static void
 run_dump_structure (int argc, char **argv)
 {
-  struct spv_reader *spv;
-  char *err = spv_open (argv[1], &spv);
-  if (err)
-    error (1, 0, "%s", err);
+  struct output_item *root = read_and_filter_spv (argv[1], NULL);
 
 
-  struct spv_item **items;
-  size_t n_items;
-  spv_select (spv, criteria, n_criteria, &items, &n_items);
   const char *last_structure_member = NULL;
   const char *last_structure_member = NULL;
-  for (size_t i = 0; i < n_items; i++)
-    if (!last_structure_member || strcmp (items[i]->structure_member,
-                                          last_structure_member))
-      {
-        last_structure_member = items[i]->structure_member;
-
-        xmlDoc *doc;
-        char *error_s = spv_item_get_structure (items[i], &doc);
-        dump_xml (argc, argv, items[i]->structure_member, error_s, doc);
-      }
-  free (items);
+  struct output_iterator iter;
+  OUTPUT_ITEM_FOR_EACH (&iter, root)
+    {
+      const struct output_item *item = iter.cur;
+      if (item->spv_info->structure_member
+          && (!last_structure_member
+              || strcmp (item->spv_info->structure_member,
+                         last_structure_member)))
+        {
+          last_structure_member = item->spv_info->structure_member;
+          dump_structure (argc, argv, item);
+        }
+    }
+  output_item_unref (root);
+}
 
 
-  spv_close (spv);
+static bool
+is_any_legacy (const struct output_item *item)
+{
+  if (item->type == OUTPUT_ITEM_TABLE)
+    return item->spv_info->xml_member != NULL;
+  else if (item->type == OUTPUT_ITEM_GROUP)
+    for (size_t i = 0; i < item->group.n_children; i++)
+      if (is_any_legacy (item->group.children[i]))
+        return true;
+
+  return false;
 }
 
 static void
 run_is_legacy (int argc UNUSED, char **argv)
 {
 }
 
 static void
 run_is_legacy (int argc UNUSED, char **argv)
 {
-  struct spv_reader *spv;
-  char *err = spv_open (argv[1], &spv);
-  if (err)
-    error (1, 0, "%s", err);
-
-  bool is_legacy = false;
-
-  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_legacy_table (items[i]))
-      {
-        is_legacy = true;
-        break;
-      }
-  free (items);
-
-  spv_close (spv);
+  struct output_item *root = read_and_filter_spv (argv[1], NULL);
+  bool is_legacy = is_any_legacy (root);
+  output_item_unref (root);
 
   exit (is_legacy ? EXIT_SUCCESS : EXIT_FAILURE);
 }
 
   exit (is_legacy ? EXIT_SUCCESS : EXIT_FAILURE);
 }
@@ -801,68 +684,75 @@ dump_strings (const char *encoding, struct string_array *strings)
     }
 }
 
     }
 }
 
+struct encoded_strings
+  {
+    char *encoding;
+    struct string_array strings;
+  };
+
+struct encoded_strings_table
+  {
+    struct encoded_strings *es;
+    size_t n, allocated;
+  };
+
 static void
 static void
-run_strings (int argc UNUSED, char **argv)
+collect_strings (const struct output_item *item,
+                 struct encoded_strings_table *t)
 {
 {
-  struct spv_reader *spv;
-  char *err = spv_open (argv[1], &spv);
-  if (err)
-    error (1, 0, "%s", err);
-
-  struct encoded_strings
+  char *error;
+  struct spvlb_table *table;
+  error = spv_read_light_table (item->spv_info->zip_reader,
+                                item->spv_info->bin_member, &table);
+  if (error)
     {
     {
-      char *encoding;
-      struct string_array strings;
+      msg (ME, "%s", error);
+      free (error);
+      return;
     }
     }
-  *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++)
+
+  const char *table_encoding = spvlb_table_get_encoding (table);
+  size_t j = 0;
+  for (j = 0; j < t->n; j++)
+    if (!strcmp (t->es[j].encoding, table_encoding))
+      break;
+  if (j >= t->n)
     {
     {
-      if (!spv_item_is_light_table (items[i]))
-        continue;
+      if (t->n >= t->allocated)
+        t->es = x2nrealloc (t->es, &t->allocated, sizeof *t->es);
+      t->es[t->n++] = (struct encoded_strings) {
+        .encoding = xstrdup (table_encoding),
+        .strings = STRING_ARRAY_INITIALIZER,
+      };
+    }
+  collect_spvlb_strings (table, &t->es[j].strings);
+}
 
 
-      char *error;
-      struct spvlb_table *table;
-      error = spv_item_get_light_table (items[i], &table);
-      if (error)
-        {
-          msg (ME, "%s", error);
-          free (error);
-          continue;
-        }
+static void
+run_strings (int argc UNUSED, char **argv)
+{
+  struct output_item *root = read_and_filter_spv (argv[1], NULL);
 
 
-      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);
+  struct encoded_strings_table t = { .es = NULL };
+  struct output_iterator iter;
+  OUTPUT_ITEM_FOR_EACH (&iter, root)
+    {
+      const struct output_item *item = iter.cur;
+      if (item->type == OUTPUT_ITEM_TABLE
+          && !item->spv_info->xml_member
+          && item->spv_info->bin_member)
+        collect_strings (item, &t);
     }
     }
-  free (items);
 
 
-  for (size_t i = 0; i < n_es; i++)
+  for (size_t i = 0; i < t.n; i++)
     {
     {
-      dump_strings (es[i].encoding, &es[i].strings);
-      free (es[i].encoding);
-      string_array_destroy (&es[i].strings);
+      dump_strings (t.es[i].encoding, &t.es[i].strings);
+      free (t.es[i].encoding);
+      string_array_destroy (&t.es[i].strings);
     }
     }
-  free (es);
+  free (t.es);
 
 
-  spv_close (spv);
+  output_item_unref (root);
 }
 
 struct command
 }
 
 struct command
@@ -967,7 +857,7 @@ main (int argc, char **argv)
   return n_warnings ? EXIT_FAILURE : EXIT_SUCCESS;
 }
 
   return n_warnings ? EXIT_FAILURE : EXIT_SUCCESS;
 }
 
-static struct spv_criteria *
+static struct output_criteria *
 get_criteria (void)
 {
   if (!n_criteria || new_criteria)
 get_criteria (void)
 {
   if (!n_criteria || new_criteria)
@@ -976,7 +866,8 @@ get_criteria (void)
       if (n_criteria >= allocated_criteria)
         criteria = x2nrealloc (criteria, &allocated_criteria,
                                sizeof *criteria);
       if (n_criteria >= allocated_criteria)
         criteria = x2nrealloc (criteria, &allocated_criteria,
                                sizeof *criteria);
-      criteria[n_criteria++] = (struct spv_criteria) SPV_CRITERIA_INITIALIZER;
+      criteria[n_criteria++]
+        = (struct output_criteria) OUTPUT_CRITERIA_INITIALIZER;
     }
 
   return &criteria[n_criteria - 1];
     }
 
   return &criteria[n_criteria - 1];
@@ -992,32 +883,32 @@ parse_select (char *arg)
   for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
     {
       if (!strcmp (arg, "all"))
   for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
     {
       if (!strcmp (arg, "all"))
-        classes = SPV_ALL_CLASSES;
+        classes = OUTPUT_ALL_CLASSES;
       else if (!strcmp (arg, "help"))
         {
           puts (_("The following object classes are supported:"));
       else if (!strcmp (arg, "help"))
         {
           puts (_("The following object classes are supported:"));
-          for (int class = 0; class < SPV_N_CLASSES; class++)
-            printf ("- %s\n", spv_item_class_to_string (class));
+          for (int class = 0; class < OUTPUT_N_CLASSES; class++)
+            printf ("- %s\n", output_item_class_to_string (class));
           exit (0);
         }
       else
         {
           exit (0);
         }
       else
         {
-          int class = spv_item_class_from_string (token);
-          if (class == SPV_N_CLASSES)
-            error (1, 0, _("%s: unknown object class (use --select=help "
+          int class = output_item_class_from_string (token);
+          if (class == OUTPUT_N_CLASSES)
+            error (1, 0, _("unknown object class \"%s\" (use --select=help "
                            "for help)"), arg);
           classes |= 1u << class;
         }
     }
 
                            "for help)"), arg);
           classes |= 1u << class;
         }
     }
 
-  struct spv_criteria *c = get_criteria ();
-  c->classes = invert ? classes ^ SPV_ALL_CLASSES : classes;
+  struct output_criteria *c = get_criteria ();
+  c->classes = invert ? classes ^ OUTPUT_ALL_CLASSES : classes;
 }
 
 }
 
-static struct spv_criteria_match *
+static struct output_criteria_match *
 get_criteria_match (const char **arg)
 {
 get_criteria_match (const char **arg)
 {
-  struct spv_criteria *c = get_criteria ();
+  struct output_criteria *c = get_criteria ();
   if ((*arg)[0] == '^')
     {
       (*arg)++;
   if ((*arg)[0] == '^')
     {
       (*arg)++;
@@ -1030,28 +921,28 @@ get_criteria_match (const char **arg)
 static void
 parse_commands (const char *arg)
 {
 static void
 parse_commands (const char *arg)
 {
-  struct spv_criteria_match *cm = get_criteria_match (&arg);
+  struct output_criteria_match *cm = get_criteria_match (&arg);
   string_array_parse (&cm->commands, ss_cstr (arg), ss_cstr (","));
 }
 
 static void
 parse_subtypes (const char *arg)
 {
   string_array_parse (&cm->commands, ss_cstr (arg), ss_cstr (","));
 }
 
 static void
 parse_subtypes (const char *arg)
 {
-  struct spv_criteria_match *cm = get_criteria_match (&arg);
+  struct output_criteria_match *cm = get_criteria_match (&arg);
   string_array_parse (&cm->subtypes, ss_cstr (arg), ss_cstr (","));
 }
 
 static void
 parse_labels (const char *arg)
 {
   string_array_parse (&cm->subtypes, ss_cstr (arg), ss_cstr (","));
 }
 
 static void
 parse_labels (const char *arg)
 {
-  struct spv_criteria_match *cm = get_criteria_match (&arg);
+  struct output_criteria_match *cm = get_criteria_match (&arg);
   string_array_parse (&cm->labels, ss_cstr (arg), ss_cstr (","));
 }
 
 static void
 parse_instances (char *arg)
 {
   string_array_parse (&cm->labels, ss_cstr (arg), ss_cstr (","));
 }
 
 static void
 parse_instances (char *arg)
 {
-  struct spv_criteria *c = get_criteria ();
+  struct output_criteria *c = get_criteria ();
   size_t allocated_instances = c->n_instances;
 
   for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
   size_t allocated_instances = c->n_instances;
 
   for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
@@ -1068,7 +959,7 @@ parse_instances (char *arg)
 static void
 parse_nth_commands (char *arg)
 {
 static void
 parse_nth_commands (char *arg)
 {
-  struct spv_criteria *c = get_criteria ();
+  struct output_criteria *c = get_criteria ();
   size_t allocated_commands = c->n_commands;
 
   for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
   size_t allocated_commands = c->n_commands;
 
   for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
@@ -1084,7 +975,7 @@ parse_nth_commands (char *arg)
 static void
 parse_members (const char *arg)
 {
 static void
 parse_members (const char *arg)
 {
-  struct spv_criteria *cm = get_criteria ();
+  struct output_criteria *cm = get_criteria ();
   string_array_parse (&cm->members, ss_cstr (arg), ss_cstr (","));
 }
 
   string_array_parse (&cm->members, ss_cstr (arg), ss_cstr (","));
 }
 
@@ -1093,7 +984,7 @@ parse_table_look (const char *arg)
 {
   pivot_table_look_unref (table_look);
 
 {
   pivot_table_look_unref (table_look);
 
-  char *error_s = spv_table_look_read (arg, &table_look);
+  char *error_s = pivot_table_look_read (arg, &table_look);
   if (error_s)
     error (1, 0, "%s", error_s);
 }
   if (error_s)
     error (1, 0, "%s", error_s);
 }