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 "output/driver.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;
+  size_t nesting_level = SIZE_MAX;
   enum cmd_result result;
-  bool opened = false;
   int n_tokens;
 
   /* Read the command's first token. */
@@ -198,10 +199,10 @@ do_parse_command (struct lexer *lexer,
       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)
     {
@@ -247,8 +248,8 @@ finish:
     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;
 }
index 7b888d06ec5e02886359742e9c039b976a30dce1..cf649b0aecdffc2e7c07d0f2da24ccfa944c0c72 100644 (file)
@@ -584,11 +584,9 @@ ascii_output_table_item_unref (struct ascii_driver *a,
 }
 
 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);
-
   if (a->error)
     return;
 
@@ -620,9 +618,8 @@ ascii_submit (struct output_driver *driver,
     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 (
@@ -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 (
-              output_item_ref (item))));
+            output_item_ref (item))));
       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;
@@ -665,10 +663,10 @@ const struct output_driver_factory list_driver_factory =
 
 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,
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/select.c \
+       src/output/select.h \
        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;
 
-    case OUTPUT_ITEM_GROUP_OPEN:
-    case OUTPUT_ITEM_GROUP_CLOSE:
+    case OUTPUT_ITEM_GROUP:
     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;
 
-    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:
@@ -1213,8 +1211,7 @@ xr_fsm_draw_region (struct xr_fsm *fsm, cairo_t *cr,
       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:
@@ -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;
 
-    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:
index 06de07b82eb0ed22188e8979f792b0c18c9d63a8..1dbffd8270aa37d3bee3deffadae794fedb14ffb 100644 (file)
@@ -101,20 +101,19 @@ struct xr_pager
 
     /* Current output item. */
     struct xr_fsm *fsm;
-    struct output_item *item;
+    struct output_iterator iter;
+    struct output_item *root_item;
     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;
@@ -248,16 +247,14 @@ xr_pager_destroy (struct xr_pager *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);
-      output_item_unref (p->item);
+      output_iterator_destroy (&p->iter);
+      output_item_unref (p->root_item);
 
       if (p->cr)
         {
@@ -271,15 +268,15 @@ xr_pager_destroy (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)
 {
-  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);
 }
 
@@ -339,7 +336,7 @@ xr_pager_finish_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;
@@ -363,106 +360,104 @@ add_outline (cairo_t *cr, int parent_id,
 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 =
 {
-  "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_GROUP_OPEN:
-      break;
-
-    case OUTPUT_ITEM_GROUP_CLOSE:
-      break;
+    case OUTPUT_ITEM_GROUP:
+      NOT_REACHED ();
 
     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 =
   {
-    "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;
+struct output_iterator;
 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);
-  };
 
+    /* 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
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. */
 
-    /* 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;
 
@@ -121,8 +118,8 @@ output_engine_pop (void)
   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);
@@ -137,40 +134,85 @@ output_get_supported_formats (struct string_set *formats)
     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)
 {
+  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);
 
-      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);
@@ -222,47 +264,7 @@ output_submit (struct output_item *item)
     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);
 }
@@ -277,8 +279,8 @@ output_get_command_name (void)
     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;
 }
@@ -290,6 +292,40 @@ output_get_uppercase_command_name (void)
   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
@@ -357,14 +393,6 @@ output_set_filename (const char *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,
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);
 
-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);
index dcff0af085bcfe4e5feed0e843923c16b68c922a..e3483b4ebc9318a042cd7baaf9a207b1b6285c62 100644 (file)
@@ -247,7 +247,8 @@ html_destroy (struct output_driver *driver)
 }
 
 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);
 
@@ -270,10 +271,9 @@ html_submit (struct output_driver *driver, const struct output_item *item)
         }
       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:
@@ -320,8 +320,7 @@ html_submit (struct output_driver *driver, const struct output_item *item)
 
           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;
@@ -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. */
@@ -740,8 +745,8 @@ struct output_driver_factory html_driver_factory =
 
 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;
 
+    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_GROUP_OPEN:
-    case OUTPUT_ITEM_GROUP_CLOSE:
     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 =
   {
-    "journal",
-    journal_destroy,
-    journal_submit,
-    NULL                        /* flush */
+    .name = "journal",
+    .destroy = journal_destroy,
+    .submit = journal_submit,
   };
-
 \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 =
   {
-    "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_GROUP_OPEN:
-      break;
-
-    case OUTPUT_ITEM_GROUP_CLOSE:
-      break;
+    case OUTPUT_ITEM_GROUP:
+      NOT_REACHED ();
 
     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 =
 {
-  "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/zip-reader.h"
 #include "output/chart.h"
 #include "output/driver.h"
 #include "output/page-setup.h"
 #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
@@ -66,10 +85,9 @@ output_item_unref (struct output_item *item)
               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:
@@ -99,6 +117,7 @@ output_item_unref (struct output_item *item)
           free (item->label);
           free (item->command_name);
           free (item->cached_label);
+          spv_info_destroy (item->spv_info);
           free (item);
         }
     }
@@ -112,14 +131,10 @@ output_item_is_shared (const struct output_item *item)
   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,
@@ -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,
+    .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;
 
-    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:
@@ -173,6 +206,28 @@ output_item_submit (struct output_item *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)
@@ -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_GROUP_OPEN:
+    case OUTPUT_ITEM_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";
 
@@ -249,6 +300,145 @@ output_item_set_label_nocopy (struct output_item *item, char *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)
@@ -262,33 +452,55 @@ chart_item_create (struct chart *chart)
 }
 \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 *
-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) {
-    OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_GROUP_OPEN),
+    OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_GROUP),
     .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 *
-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
@@ -554,4 +766,55 @@ text_item_subtype_to_string (enum text_item_subtype subtype)
       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 "libpspp/string-array.h"
 
 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,
@@ -39,6 +39,8 @@ enum output_item_type
     OUTPUT_ITEM_TEXT,
   };
 
+const char *output_item_type_to_string (enum output_item_type);
+
 /* A single output item. */
 struct output_item
   {
@@ -59,7 +61,7 @@ struct output_item
        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
@@ -67,6 +69,10 @@ struct output_item
        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
       {
@@ -74,6 +80,14 @@ struct output_item
 
         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;
@@ -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 *);
+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 *);
+
+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
-/* 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. */
 
@@ -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);
+\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 */
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 =
   {
-    "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/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-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 \
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;
-  memset (out, 0, sizeof *out);
+  *out = (struct spv_data) SPV_DATA_INITIALIZER;
 
   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);
-  memset (out, 0, sizeof *out);
+  *out = (struct spv_data) SPV_DATA_INITIALIZER;
   spvob_free_legacy_binary (lb);
   return error;
 }
index 1e8fa2d151a510e7492db36a6fa200318af47258..3323123e7e4cea5de5cdb43eb1bc1580b28aa70b 100644 (file)
@@ -33,6 +33,8 @@ struct spv_data
     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 *);
 
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;
 
-    case OUTPUT_ITEM_GROUP_OPEN:
+    case OUTPUT_ITEM_GROUP:
       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;
 
index fc058281eccd633f1e1824cd78c12a728de8fb51..f37dff17f2173a455427afc6d3557ce715ba6e5b 100644 (file)
@@ -19,6 +19,7 @@
 #include "output/spv/spv.h"
 
 #include <assert.h>
+#include <cairo.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 "output/output-item.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;
   };
 
-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)
 {
@@ -550,24 +277,27 @@ decode_embedded_html (const xmlNode *node, struct font_style *font_style)
   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
@@ -613,40 +343,15 @@ decode_page_paragraph (const struct spvsx_page_paragraph *page_paragraph,
   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
-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;
 
-  if (!spv_item_is_light_table (item))
-    return xstrdup ("not a light binary table object");
-
   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;
 
@@ -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);
-  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;
 }
 
-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;
-  char *error = spv_item_get_light_table (item, &raw_table);
+  char *error = spv_read_light_table (zip, bin_member, &raw_table);
   if (!error)
-    error = decode_spvlb_table (raw_table, &item->table);
+    error = decode_spvlb_table (raw_table, tablep);
   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
-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;
-  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);
@@ -721,15 +403,15 @@ spv_item_get_legacy_data (const struct spv_item *item, struct spv_data *data)
   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;
-  char *error = zip_member_open (spv->zip, member_name, &zm);
+  char *error = zip_member_open (zip, xml_member, &zm);
   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);
-      return xasprintf (_("%s: Failed to create XML parser"), member_name);
+      return xasprintf (_("%s: Failed to create XML parser"), xml_member);
     }
 
   int retval;
@@ -764,7 +446,7 @@ spv_read_xml_member (struct spv_reader *spv, const char *member_name,
   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);
@@ -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"),
-                       member_name,
+                       xml_member,
                        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;
 }
 
-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 *
-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
@@ -843,204 +510,240 @@ spv_item_format_path (const struct spv_item *item, struct string *s)
         }
     }
 }
+#endif
 
 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)
-    {
-      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;
-  error = spv_read_xml_member (item->spv, item->xml_member, false,
+  error = spv_read_xml_member (zip, xml_member, false,
                                "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_);
-
-  if (!error)
-    error = decode_spvdx_table (v, item->subtype, item->table_look,
-                                &data, &item->table);
-
   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);
+exit_free_doc:
   if (doc)
     xmlFreeDoc (doc);
-
+exit_free_data:
+  spv_data_uninit (&data);
+exit:
   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
-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];
+
+  struct output_item *item = NULL;
+  char *error;
   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);
-      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);
-      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))
-    item->type = SPV_ITEM_TREE;
+    error = xstrdup ("trees not yet implemented");
   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 spv_item *parent)
+                     struct output_item *parent)
 {
   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);
-          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);
-          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 ();
 
-      if (error)
-        return error;
+      set_structure_member (child, zip, structure_member);
+      group_item_add_child (parent, child);
     }
-
-  return NULL;
 }
 
 static struct page_setup *
@@ -1085,44 +788,52 @@ decode_page_setup (const struct spvsx_page_setup *in, const char *file_name)
   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;
-  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)
-    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_);
-
-  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)
     {
-      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
@@ -1165,81 +876,43 @@ spv_detect (const char *filename)
 }
 
 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);
-  char *error = zip_reader_create (filename, &spv->zip);
+  struct zip_reader *zip;
+  char *error = zip_reader_create (filename, &zip);
   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)
     {
-      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++)
     {
-      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;
 
-      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;
 }
 
-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)
 {
index eda7704dd70e746b257175df905dcdd67cafe59f..5bf4f5692f8ca859cba2f1f75f41b6a1224497c2 100644 (file)
 #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 <stddef.h>
 #include <stdint.h>
+#include <libxml/tree.h>
 
 #include "libpspp/compiler.h"
 
 struct fmt_spec;
-struct pivot_table;
+struct output_item;
+struct page_setup;
 struct spv_data;
-struct spv_reader;
 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;
 
-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 */
index b9a90df2d78a3850354495c0177efcca3a792dd0..254d6a686183e64b9ebc60b76e301eb1995bc371 100644 (file)
@@ -328,11 +328,8 @@ tex_submit (struct output_driver *driver, const struct output_item *item)
         }
       break;
 
-    case OUTPUT_ITEM_GROUP_OPEN:
-      break;
-
-    case OUTPUT_ITEM_GROUP_CLOSE:
-      break;
+    case OUTPUT_ITEM_GROUP:
+      NOT_REACHED ();
 
     case OUTPUT_ITEM_IMAGE:
       {
@@ -614,8 +611,7 @@ struct output_driver_factory tex_driver_factory =
 
 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 (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);
@@ -396,19 +396,10 @@ psppire_output_view_put (struct psppire_output_view *view,
   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';
@@ -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));
-  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 ();
 
@@ -473,13 +462,7 @@ psppire_output_view_put (struct psppire_output_view *view,
       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,
@@ -1102,10 +1085,10 @@ psppire_output_view_submit (struct output_driver *this,
 
 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
index b7ed01a1916c143cc406518bb0fd983b96bf3256..99688e97e23f706bf50a5d030bc7fe7a8235640d 100644 (file)
@@ -156,10 +156,10 @@ psppire_output_submit (struct output_driver *this,
 
 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
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/spv/spv-output.h"
-#include "output/spv/spv-select.h"
 
 #include "helper.h"
 #include "psppire-data-window.h"
@@ -698,86 +696,11 @@ psppire_window_file_chooser_dialog (PsppireWindow *toplevel)
   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)
 {
-  struct spv_reader *spv;
-  char *error = spv_open (filename, &spv);
+  struct output_item *root;
+  char *error = spv_read (filename, &root, NULL);
   if (error)
     {
       /* XXX */
@@ -785,30 +708,7 @@ read_spv_file (const char *filename)
       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.
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
-- heading "Set" command "Set"
-- heading "Title" command "Title"
+- group "Set" command "Set"
+- group "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"
-- 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"
-- 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"
-- 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"
@@ -38,38 +38,40 @@ AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv], [0], [dnl
 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
-- 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_CHECK([pspp-output dir $srcdir/utilities/regress.spv --select=^headings],
+AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --select=^outlineheaders],
   [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
-- 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"
@@ -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
-- heading "Set" command "Set"
-- heading "Title" command "Title"
+- group "Set" command "Set"
+- group "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"
-- 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"
-- heading "Frequencies" command "Frequencies"
+- group "Frequencies" command "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
-- heading "Title" command "Title"
+- group "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"
@@ -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
-    - 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
-- heading "Set" command "Set"
-- heading "Title" command "Title"
+- group "Set" command "Set"
+- group "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"
-- 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"
-- heading "Frequencies" command "Frequencies"
+- group "Frequencies" 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"
@@ -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
-    - 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
-- heading "Set" command "Set"
-- heading "Title" command "Title"
+- group "Set" command "Set"
+- group "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"
-- 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"
@@ -171,11 +173,11 @@ AT_CLEANUP
 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
 
@@ -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
-    - 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
 
@@ -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])
-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
-- 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"
-- heading "Regression" command "Regression"
+- group "Regression" command "Regression"
 ])
 AT_CLEANUP
 
index 755ba7ee65ba56d85d31571a7a6022d6a749464a..3f1fcb81d41282e801c3850c3004caa6517f1893 100644 (file)
 #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/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/spv-output.h"
-#include "output/spv/spv-select.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 struct spv_criteria *criteria;
+static struct output_criteria *criteria;
 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
-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
-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 ("    ");
 
-  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 (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);
     }
 
-  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)
     {
-      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');
+
+  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
@@ -201,109 +185,49 @@ run_detect (int argc UNUSED, char **argv)
     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);
-
-  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
-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
-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)
 {
-  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)
-    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]);
@@ -313,26 +237,12 @@ run_convert (int argc UNUSED, char **argv)
     exit (EXIT_FAILURE);
   output_driver_register (driver);
 
-  const struct page_setup *ps = spv_get_page_setup (spv);
   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 ();
@@ -346,20 +256,19 @@ run_convert (int argc UNUSED, char **argv)
 }
 
 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;
 }
 
@@ -369,18 +278,14 @@ run_get_table_look (int argc UNUSED, char **argv)
   struct pivot_table_look *look;
   if (strcmp (argv[1], "-"))
     {
-      struct spv_reader *spv;
-      char *err = spv_open (argv[1], &spv);
-      if (err)
-        error (1, 0, "%s", err);
-
-      const struct pivot_table *table = get_first_table (spv);
+      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));
 
-      spv_close (spv);
+      output_item_unref (root);
     }
   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)
 {
-  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
@@ -452,117 +343,108 @@ compare_cells (const void *a_, const void *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");
 
-  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)
 {
-  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");
 
-  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.
@@ -671,77 +553,78 @@ dump_xml (int argc, char **argv, const char *member_name,
 }
 
 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)
 {
-  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;
-  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)
 {
-  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);
 }
@@ -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
-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
@@ -967,7 +857,7 @@ main (int argc, char **argv)
   return n_warnings ? EXIT_FAILURE : EXIT_SUCCESS;
 }
 
-static struct spv_criteria *
+static struct output_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);
-      criteria[n_criteria++] = (struct spv_criteria) SPV_CRITERIA_INITIALIZER;
+      criteria[n_criteria++]
+        = (struct output_criteria) OUTPUT_CRITERIA_INITIALIZER;
     }
 
   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"))
-        classes = SPV_ALL_CLASSES;
+        classes = OUTPUT_ALL_CLASSES;
       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
         {
-          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;
         }
     }
 
-  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)
 {
-  struct spv_criteria *c = get_criteria ();
+  struct output_criteria *c = get_criteria ();
   if ((*arg)[0] == '^')
     {
       (*arg)++;
@@ -1030,28 +921,28 @@ get_criteria_match (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)
 {
-  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)
 {
-  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)
 {
-  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, ","))
@@ -1068,7 +959,7 @@ parse_instances (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, ","))
@@ -1084,7 +975,7 @@ parse_nth_commands (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 (","));
 }
 
@@ -1093,7 +984,7 @@ parse_table_look (const char *arg)
 {
   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);
 }