From 507ebaea36737618ef8265a60cd3e9005d9f3457 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Wed, 13 Jan 2021 22:05:05 -0800 Subject: [PATCH 1/1] output: Make groups contain their subitems, and get rid of spv_item. 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. --- src/language/command.c | 15 +- src/output/ascii.c | 24 +- src/output/automake.mk | 2 + src/output/cairo-fsm.c | 12 +- src/output/cairo-pager.c | 197 ++++--- src/output/cairo.c | 8 +- src/output/csv.c | 15 +- src/output/driver-provider.h | 12 +- src/output/driver.c | 196 ++++--- src/output/driver.h | 3 +- src/output/html.c | 27 +- src/output/journal.c | 15 +- src/output/msglog.c | 7 +- src/output/odt.c | 14 +- src/output/output-item.c | 321 ++++++++++- src/output/output-item.h | 102 +++- src/output/select.c | 359 +++++++++++++ src/output/select.h | 114 ++++ src/output/spv-driver.c | 9 +- src/output/spv/automake.mk | 5 - src/output/spv/spv-dump.c | 85 --- src/output/spv/spv-legacy-data.c | 4 +- src/output/spv/spv-legacy-data.h | 2 + src/output/spv/spv-output.c | 38 -- src/output/spv/spv-output.h | 26 - src/output/spv/spv-select.c | 262 --------- src/output/spv/spv-select.h | 77 --- src/output/spv/spv-writer.c | 7 +- src/output/spv/spv.c | 829 +++++++++-------------------- src/output/spv/spv.h | 191 +------ src/output/tex.c | 14 +- src/ui/gui/psppire-output-view.c | 39 +- src/ui/gui/psppire-output-window.c | 8 +- src/ui/gui/psppire-window.c | 106 +--- tests/utilities/pspp-output.at | 150 +++--- utilities/pspp-output.c | 737 +++++++++++-------------- 36 files changed, 1837 insertions(+), 2195 deletions(-) create mode 100644 src/output/select.c create mode 100644 src/output/select.h delete mode 100644 src/output/spv/spv-dump.c delete mode 100644 src/output/spv/spv-output.c delete mode 100644 src/output/spv/spv-output.h delete mode 100644 src/output/spv/spv-select.c delete mode 100644 src/output/spv/spv-select.h diff --git a/src/language/command.c b/src/language/command.c index 3c98c3feda..7c0c9bc413 100644 --- a/src/language/command.c +++ b/src/language/command.c @@ -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; } diff --git a/src/output/ascii.c b/src/output/ascii.c index 7b888d06ec..cf649b0aec 100644 --- a/src/output/ascii.c +++ b/src/output/ascii.c @@ -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, }; static char *ascii_reserve (struct ascii_driver *, int y, int x0, int x1, diff --git a/src/output/automake.mk b/src/output/automake.mk index 61c812767d..822a00200b 100644 --- a/src/output/automake.mk +++ b/src/output/automake.mk @@ -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 \ diff --git a/src/output/cairo-fsm.c b/src/output/cairo-fsm.c index 183cdb0b7f..5377c28b1c 100644 --- a/src/output/cairo-fsm.c +++ b/src/output/cairo-fsm.c @@ -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: diff --git a/src/output/cairo-pager.c b/src/output/cairo-pager.c index 06de07b82e..1dbffd8270 100644 --- a/src/output/cairo-pager.c +++ b/src/output/cairo-pager.c @@ -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; } } } diff --git a/src/output/cairo.c b/src/output/cairo.c index e5ac93bcf0..81aeddc15f 100644 --- a/src/output/cairo.c +++ b/src/output/cairo.c @@ -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, }; diff --git a/src/output/csv.c b/src/output/csv.c index a34282e012..130fe69dfe 100644 --- a/src/output/csv.c +++ b/src/output/csv.c @@ -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, }; diff --git a/src/output/driver-provider.h b/src/output/driver-provider.h index 0f01fa9b30..3712663bc5 100644 --- a/src/output/driver-provider.h +++ b/src/output/driver-provider.h @@ -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 diff --git a/src/output/driver.c b/src/output/driver.c index 6dfebab854..61ce6f51aa 100644 --- a/src/output/driver.c +++ b/src/output/driver.c @@ -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; -} void output_driver_init (struct output_driver *driver, diff --git a/src/output/driver.h b/src/output/driver.h index 8c386cb065..b3e3a520b2 100644 --- a/src/output/driver.h +++ b/src/output/driver.h @@ -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); diff --git a/src/output/html.c b/src/output/html.c index dcff0af085..e3483b4ebc 100644 --- a/src/output/html.c +++ b/src/output/html.c @@ -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 " " New-lines are replaced by NEWLINE, which might be "
" 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, }; diff --git a/src/output/journal.c b/src/output/journal.c index 2dd1925843..4945db2ee9 100644 --- a/src/output/journal.c +++ b/src/output/journal.c @@ -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, }; - /* Enables journaling. */ diff --git a/src/output/msglog.c b/src/output/msglog.c index f91c7e6117..7fe6555f19 100644 --- a/src/output/msglog.c +++ b/src/output/msglog.c @@ -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, }; diff --git a/src/output/odt.c b/src/output/odt.c index 683d19bcf5..a7eeaee9ef 100644 --- a/src/output/odt.c +++ b/src/output/odt.c @@ -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, }; diff --git a/src/output/output-item.c b/src/output/output-item.c index 0168b10b4a..5bfd125413 100644 --- a/src/output/output-item.c +++ b/src/output/output-item.c @@ -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" @@ -36,6 +37,24 @@ #define _(msgid) gettext (msgid) #define N_(msgid) msgid +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 (); +} + #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); +} + +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; + } +} + +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); + } +} struct output_item * chart_item_create (struct chart *chart) @@ -262,33 +452,55 @@ chart_item_create (struct chart *chart) } 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; } /* 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"); } } + +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; +} diff --git a/src/output/output-item.h b/src/output/output-item.h index a408807f0f..72d4661510 100644 --- a/src/output/output-item.h +++ b/src/output/output-item.h @@ -25,12 +25,12 @@ #include #include #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); + +/* 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)) /* OUTPUT_ITEM_CHART. */ struct output_item *chart_item_create (struct chart *); -/* 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); - -/* 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 *); /* 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); + +/* 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 index 0000000000..52ab7f29bd --- /dev/null +++ b/src/output/select.c @@ -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 . */ + +#include + +#include "output/select.h" + +#include + +#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; + } +} + +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 index 0000000000..7759173c08 --- /dev/null +++ b/src/output/select.h @@ -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 . */ + +#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 */ diff --git a/src/output/spv-driver.c b/src/output/spv-driver.c index f0c00e5fa8..36db324ee7 100644 --- a/src/output/spv-driver.c +++ b/src/output/spv-driver.c @@ -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, }; diff --git a/src/output/spv/automake.mk b/src/output/spv/automake.mk index 01f8a334a9..c42b792011 100644 --- a/src/output/spv/automake.mk +++ b/src/output/spv/automake.mk @@ -19,17 +19,12 @@ 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 index db8a60d9ed..0000000000 --- a/src/output/spv/spv-dump.c +++ /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 . */ - -#include - -#include "output/spv/spv.h" - -#include -#include - -#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; - } -} diff --git a/src/output/spv/spv-legacy-data.c b/src/output/spv/spv-legacy-data.c index 4e3d72c4a4..59c9331e9f 100644 --- a/src/output/spv/spv-legacy-data.c +++ b/src/output/spv/spv-legacy-data.c @@ -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; } diff --git a/src/output/spv/spv-legacy-data.h b/src/output/spv/spv-legacy-data.h index 1e8fa2d151..3323123e7e 100644 --- a/src/output/spv/spv-legacy-data.h +++ b/src/output/spv/spv-legacy-data.h @@ -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 index 8006f85eb2..0000000000 --- a/src/output/spv/spv-output.c +++ /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 . */ - -#include - -#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 index b100c0cef7..0000000000 --- a/src/output/spv/spv-output.h +++ /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 . */ - -#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 index 10f35a1811..0000000000 --- a/src/output/spv/spv-select.c +++ /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 . */ - -#include - -#include "spv-select.h" - -#include - -#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 index b2a1e0e7c8..0000000000 --- a/src/output/spv/spv-select.h +++ /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 . */ - -#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 */ diff --git a/src/output/spv/spv-writer.c b/src/output/spv/spv-writer.c index 70b455d456..64bb365c61 100644 --- a/src/output/spv/spv-writer.c +++ b/src/output/spv/spv-writer.c @@ -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; diff --git a/src/output/spv/spv.c b/src/output/spv/spv.c index fc058281ec..f37dff17f2 100644 --- a/src/output/spv/spv.c +++ b/src/output/spv/spv.c @@ -19,6 +19,7 @@ #include "output/spv/spv.h" #include +#include #include #include #include @@ -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) { diff --git a/src/output/spv/spv.h b/src/output/spv/spv.h index eda7704dd7..5bf4f5692f 100644 --- a/src/output/spv/spv.h +++ b/src/output/spv/spv.h @@ -17,193 +17,36 @@ #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 #include -#include #include +#include #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 */ diff --git a/src/output/tex.c b/src/output/tex.c index b9a90df2d7..254d6a6861 100644 --- a/src/output/tex.c +++ b/src/output/tex.c @@ -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, }; diff --git a/src/ui/gui/psppire-output-view.c b/src/ui/gui/psppire-output-view.c index 6d704ae1bc..857c1c5730 100644 --- a/src/ui/gui/psppire-output-view.c +++ b/src/ui/gui/psppire-output-view.c @@ -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 diff --git a/src/ui/gui/psppire-output-window.c b/src/ui/gui/psppire-output-window.c index b7ed01a191..99688e97e2 100644 --- a/src/ui/gui/psppire-output-window.c +++ b/src/ui/gui/psppire-output-window.c @@ -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 diff --git a/src/ui/gui/psppire-window.c b/src/ui/gui/psppire-window.c index 4b40f94d1e..1475398b03 100644 --- a/src/ui/gui/psppire-window.c +++ b/src/ui/gui/psppire-window.c @@ -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. diff --git a/tests/utilities/pspp-output.at b/tests/utilities/pspp-output.at index 65a20177d4..3baa904f40 100644 --- a/tests/utilities/pspp-output.at +++ b/tests/utilities/pspp-output.at @@ -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 diff --git a/utilities/pspp-output.c b/utilities/pspp-output.c index 755ba7ee65..3f1fcb81d4 100644 --- a/utilities/pspp-output.c +++ b/utilities/pspp-output.c @@ -30,14 +30,15 @@ #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); } -- 2.30.2