X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Foutput%2Foutput-item.c;h=6b7c11121c588d998e09797d569122ed1fe565fb;hb=b051ed08f170d5211f47491da71539f996ee5655;hp=dd4e70c2ee48b8591813767374d166b8a1615b0e;hpb=fe8dc2171009e90d2335f159d05f7e6660e24780;p=pspp diff --git a/src/output/output-item.c b/src/output/output-item.c index dd4e70c2ee..6b7c11121c 100644 --- a/src/output/output-item.c +++ b/src/output/output-item.c @@ -16,16 +16,45 @@ #include -#include "output/output-item-provider.h" +#include "output/output-item.h" #include #include #include "libpspp/assertion.h" #include "libpspp/cast.h" +#include "libpspp/message.h" +#include "libpspp/str.h" +#include "libpspp/zip-reader.h" +#include "output/chart.h" +#include "output/driver.h" +#include "output/pivot-table.h" #include "gl/xalloc.h" + +#include "gettext.h" +#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_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 owner. An output item that is shared among multiple owners must not be modified. */ @@ -33,6 +62,7 @@ struct output_item * output_item_ref (const struct output_item *item_) { struct output_item *item = CONST_CAST (struct output_item *, item_); + assert (item->ref_cnt > 0); item->ref_cnt++; return item; } @@ -46,7 +76,45 @@ output_item_unref (struct output_item *item) { assert (item->ref_cnt > 0); if (--item->ref_cnt == 0) - item->class->destroy (item); + { + switch (item->type) + { + case OUTPUT_ITEM_CHART: + chart_unref (item->chart); + break; + + case OUTPUT_ITEM_GROUP: + for (size_t i = 0; i < item->group.n_children; i++) + output_item_unref (item->group.children[i]); + free (item->group.children); + break; + + case OUTPUT_ITEM_IMAGE: + cairo_surface_destroy (item->image); + break; + + case OUTPUT_ITEM_MESSAGE: + msg_destroy (item->message); + break; + + case OUTPUT_ITEM_PAGE_BREAK: + break; + + case OUTPUT_ITEM_TABLE: + pivot_table_unref (item->table); + break; + + case OUTPUT_ITEM_TEXT: + pivot_value_destroy (item->text.content); + break; + } + + free (item->label); + free (item->command_name); + free (item->cached_label); + spv_info_destroy (item->spv_info); + free (item); + } } } @@ -57,17 +125,674 @@ output_item_is_shared (const struct output_item *item) { return item->ref_cnt > 1; } + +/* Returns a clone of OLD, without initializing type-specific fields. */ +static struct output_item * +output_item_clone_common (const struct output_item *old) +{ + struct output_item *new = xmalloc (sizeof *new); + *new = (struct output_item) { + .ref_cnt = 1, + .label = xstrdup_if_nonnull (old->label), + .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: + 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; + + for (size_t i = 0; i < new->group.n_children; i++) + new->group.children[i] = output_item_ref (new->group.children[i]); + break; + + case OUTPUT_ITEM_IMAGE: + new->image = cairo_surface_reference (old->image); + break; + + case OUTPUT_ITEM_MESSAGE: + new->message = msg_dup (old->message); + break; + + case OUTPUT_ITEM_PAGE_BREAK: + break; + + case OUTPUT_ITEM_TABLE: + new->table = pivot_table_ref (old->table); + break; + + case OUTPUT_ITEM_TEXT: + new->text.subtype = old->text.subtype; + new->text.content = pivot_value_clone (old->text.content); + break; + } + return new; +} + +void +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) +{ + if (item->label) + return item->label; + + switch (item->type) + { + case OUTPUT_ITEM_CHART: + return item->chart->title ? item->chart->title : _("Chart"); + + case OUTPUT_ITEM_GROUP: + return item->command_name ? item->command_name : _("Group"); + + case OUTPUT_ITEM_IMAGE: + return "Image"; + + case OUTPUT_ITEM_MESSAGE: + return (item->message->severity == MSG_S_ERROR ? _("Error") + : item->message->severity == MSG_S_WARNING ? _("Warning") + : _("Note")); + + case OUTPUT_ITEM_PAGE_BREAK: + return _("Page Break"); + + case OUTPUT_ITEM_TABLE: + if (!item->cached_label) + { + if (!item->table->title) + return _("Table"); + + struct output_item *item_rw = CONST_CAST (struct output_item *, item); + item_rw->cached_label = pivot_value_to_string (item->table->title, + item->table); + } + return item->cached_label; + + case OUTPUT_ITEM_TEXT: + return text_item_subtype_to_string (item->text.subtype); + } + + NOT_REACHED (); +} + +/* Sets the label for ITEM to LABEL. The caller retains ownership of LABEL. + If LABEL is nonnull, it overrides any previously set label and the default + label. If LABEL is null, ITEM will now use its default label. + + ITEM must not be shared. */ +void +output_item_set_label (struct output_item *item, const char *label) +{ + output_item_set_label_nocopy (item, xstrdup_if_nonnull (label)); +} + +/* Sets the label for ITEM to LABEL, transferring ownership of LABEL to ITEM. + If LABEL is nonnull, it overrides any previously set label and the default + label. If LABEL is null, ITEM will now use its default label. + + ITEM must not be shared. */ +void +output_item_set_label_nocopy (struct output_item *item, char *label) +{ + assert (!output_item_is_shared (item)); + 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); +} -/* Initializes ITEM as an output item of the specified CLASS, initially with a - reference count of 1. +static void +indent (int indentation) +{ + for (int i = 0; i < indentation * 2; i++) + putchar (' '); +} - An output item is an abstract class, that is, a plain output_item is not - useful on its own. Thus, this function is normally called from the - initialization function of some subclass of output_item. */ void -output_item_init (struct output_item *item, - const struct output_item_class *class) +output_item_dump (const struct output_item *item, int indentation) { - item->class = class; - item->ref_cnt = 1; + 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_TABLE: + printf ("table\n"); + pivot_table_dump (item->table, indentation + 1); + break; + + case OUTPUT_ITEM_TEXT: + { + char *s = pivot_value_to_string (item->text.content, NULL); + printf ("text %s \"%s\"\n", + text_item_subtype_to_string (item->text.subtype), s); + free (s); + } + 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) +{ + struct output_item *item = xmalloc (sizeof *item); + *item = (struct output_item) { + OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_CHART), + .chart = chart, + }; + return item; +} + +struct output_item * +group_item_create (const char *command_name, const char *label) +{ + return group_item_create_nocopy ( + xstrdup_if_nonnull (command_name), + xstrdup_if_nonnull (label)); +} + +struct output_item * +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), + .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 * +root_item_create (void) +{ + 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 + IMAGE. */ +struct output_item * +image_item_create (cairo_surface_t *image) +{ + struct output_item *item = xmalloc (sizeof *item); + *item = (struct output_item) { + OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_IMAGE), + .image = image, + }; + return item; +} + +struct output_item * +message_item_create (const struct msg *msg) +{ + struct output_item *item = xmalloc (sizeof *item); + *item = (struct output_item) { + OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_MESSAGE), + .message = msg_dup (msg), + }; + return item; +} + +const struct msg * +message_item_get_msg (const struct output_item *item) +{ + assert (item->type == OUTPUT_ITEM_MESSAGE); + return item->message; +} + +struct output_item * +message_item_to_text_item (struct output_item *message_item) +{ + assert (message_item->type == OUTPUT_ITEM_MESSAGE); + struct output_item *text_item = text_item_create_nocopy ( + TEXT_ITEM_LOG, + msg_to_string (message_item->message), + xstrdup (output_item_get_label (message_item))); + output_item_unref (message_item); + return text_item; +} + +struct output_item * +page_break_item_create (void) +{ + struct output_item *item = xmalloc (sizeof *item); + *item = (struct output_item) { + OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_PAGE_BREAK), + }; + return item; +} + +/* Returns a new output_item for rendering TABLE. Takes ownership of + TABLE. */ +struct output_item * +table_item_create (struct pivot_table *table) +{ + pivot_table_assign_label_depth (table); + + struct output_item *item = xmalloc (sizeof *item); + *item = (struct output_item) { + OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_TABLE), + .command_name = xstrdup_if_nonnull (table->command_c), + .table = table, + }; + return item; +} + +/* Creates and returns a new text item containing TEXT and the specified + SUBTYPE and LABEL. The new text item takes ownership of TEXT and LABEL. If + LABEL is NULL, uses the default label for SUBTYPE. */ +struct output_item * +text_item_create_nocopy (enum text_item_subtype subtype, + char *text, char *label) +{ + return text_item_create_value (subtype, + pivot_value_new_user_text_nocopy (text), + label); +} + +/* Creates and returns a new text item containing a copy of TEXT and the + specified SUBTYPE and LABEL. The caller retains ownership of TEXT and + LABEL. If LABEL is null, uses a default label for SUBTYPE. */ +struct output_item * +text_item_create (enum text_item_subtype subtype, const char *text, + const char *label) +{ + return text_item_create_nocopy (subtype, xstrdup (text), + xstrdup_if_nonnull (label)); +} + +/* Creates and returns a new text item containing VALUE, SUBTYPE, and LABEL. + Takes ownership of VALUE and LABEL. If LABEL is null, uses a default label + for SUBTYPE. */ +struct output_item * +text_item_create_value (enum text_item_subtype subtype, + struct pivot_value *value, char *label) +{ + if (subtype == TEXT_ITEM_SYNTAX || subtype == TEXT_ITEM_LOG) + { + struct pivot_value_ex *ex = pivot_value_ex_rw (value); + if (!ex->font_style) + { + ex->font_style = xmalloc (sizeof *value->ex->font_style); + *ex->font_style = (struct font_style) FONT_STYLE_INITIALIZER; + } + + free (ex->font_style->typeface); + ex->font_style->typeface = xstrdup ("Monospaced"); + } + + struct output_item *item = XZALLOC (struct output_item); + *item = (struct output_item) { + OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_TEXT), + .command_name = xstrdup_if_nonnull (output_get_command_name ()), + .label = label, + .text = { .subtype = subtype, .content = value }, + }; + return item; +} + +/* Returns ITEM's subtype. */ +enum text_item_subtype +text_item_get_subtype (const struct output_item *item) +{ + assert (item->type == OUTPUT_ITEM_TEXT); + return item->text.subtype; +} + +/* Returns ITEM's text, which the caller must eventually free. */ +char * +text_item_get_plain_text (const struct output_item *item) +{ + assert (item->type == OUTPUT_ITEM_TEXT); + return pivot_value_to_string (item->text.content, NULL); +} + +static bool +nullable_font_style_equal (const struct font_style *a, + const struct font_style *b) +{ + return a && b ? font_style_equal (a, b) : !a && !b; +} + +/* Attempts to append the text in SRC to DST. If successful, returns true, + otherwise false. + + Only TEXT_ITEM_SYNTAX and TEXT_ITEM_LOG items can be combined, and not with + each other. + + DST must not be shared. */ +bool +text_item_append (struct output_item *dst, const struct output_item *src) +{ + assert (dst->type == OUTPUT_ITEM_TEXT); + assert (src->type == OUTPUT_ITEM_TEXT); + assert (!output_item_is_shared (dst)); + + enum text_item_subtype ds = dst->text.subtype; + enum text_item_subtype ss = src->text.subtype; + + struct pivot_value *dc = dst->text.content; + const struct pivot_value *sc = src->text.content; + + if (ds != ss + || (ds != TEXT_ITEM_SYNTAX && ds != TEXT_ITEM_LOG) + || strcmp (output_item_get_label (dst), output_item_get_label (src)) + || !nullable_font_style_equal (dc->ex ? dc->ex->font_style : NULL, + sc->ex ? sc->ex->font_style : NULL) + || (dc->ex && dc->ex->font_style && dc->ex->font_style->markup) + || sc->type != PIVOT_VALUE_TEXT + || dc->type != PIVOT_VALUE_TEXT) + return false; + else + { + /* Calculate new text. */ + char *new_text = xasprintf ("%s\n%s", dc->text.local, sc->text.local); + + /* Free the old text. */ + free (dc->text.local); + if (dc->text.c != dc->text.local) + free (dc->text.c); + if (dc->text.id != dc->text.local && dc->text.id != dc->text.c) + free (dc->text.id); + + /* Put in new text. */ + dc->text.local = new_text; + dc->text.c = new_text; + dc->text.id = new_text; + + return true; + } +} + +static const struct pivot_table_look * +text_item_table_look (void) +{ + static struct pivot_table_look *look; + if (!look) + { + look = pivot_table_look_new_builtin_default (); + + for (int a = 0; a < PIVOT_N_AREAS; a++) + memset (look->areas[a].cell_style.margin, 0, + sizeof look->areas[a].cell_style.margin); + for (int b = 0; b < PIVOT_N_BORDERS; b++) + look->borders[b].stroke = TABLE_STROKE_NONE; + } + return look; +} + +struct output_item * +text_item_to_table_item (struct output_item *text_item) +{ + assert (text_item->type == OUTPUT_ITEM_TEXT); + + /* Create a new table whose contents come from TEXT_ITEM. */ + struct pivot_table *table = pivot_table_create__ (NULL, "Text"); + pivot_table_set_look (table, text_item_table_look ()); + + struct pivot_dimension *d = pivot_dimension_create ( + table, PIVOT_AXIS_ROW, N_("Text")); + d->hide_all_labels = true; + pivot_category_create_leaf (d->root, pivot_value_new_text ("null")); + + pivot_table_put1 (table, 0, pivot_value_clone (text_item->text.content)); + + /* Free TEXT_ITEM. */ + output_item_unref (text_item); + + /* Return a new output item. */ + return table_item_create (table); +} + +const char * +text_item_subtype_to_string (enum text_item_subtype subtype) +{ + switch (subtype) + { + case TEXT_ITEM_PAGE_TITLE: + return _("Page Title"); + + case TEXT_ITEM_TITLE: + return _("Title"); + + case TEXT_ITEM_SYNTAX: + case TEXT_ITEM_LOG: + return _("Log"); + + default: + 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; }