+
+/* 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++)
+ 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_PAGE_SETUP:
+ new->page_setup = page_setup_clone (old->page_setup);
+ 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_PAGE_SETUP:
+ /* Not marked for translation: user should never see it. */
+ return "Page Setup";
+
+ 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);
+}
+\f
+static void
+indent (int indentation)
+{
+ for (int i = 0; i < indentation * 2; i++)
+ putchar (' ');
+}
+
+void
+output_item_dump (const struct output_item *item, int indentation)
+{
+ indent (indentation);
+ if (item->label)
+ printf ("label=\"%s\" ", item->label);
+ if (item->command_name)
+ printf ("command=\"%s\" ", item->command_name);
+ if (!item->show)
+ printf ("(%s) ", item->type == OUTPUT_ITEM_GROUP ? "collapsed" : "hidden");
+
+ switch (item->type)
+ {
+ case OUTPUT_ITEM_CHART:
+ printf ("chart \"%s\"\n", item->chart->title ? item->chart->title : "");
+ break;
+
+ case OUTPUT_ITEM_GROUP:
+ printf ("group\n");
+ for (size_t i = 0; i < item->group.n_children; i++)
+ output_item_dump (item->group.children[i], indentation + 1);
+ break;
+
+ case OUTPUT_ITEM_IMAGE:
+ printf ("image\n");
+ break;
+
+ case OUTPUT_ITEM_MESSAGE:
+ printf ("message\n");
+ break;
+
+ case OUTPUT_ITEM_PAGE_BREAK:
+ printf ("page break\n");
+ break;
+
+ case OUTPUT_ITEM_PAGE_SETUP:
+ printf ("page setup\n");
+ break;
+
+ case OUTPUT_ITEM_TABLE:
+ pivot_table_dump (item->table, indentation + 1);
+ break;
+
+ case OUTPUT_ITEM_TEXT:
+ printf ("text %s \"%s\"\n",
+ text_item_subtype_to_string (item->text.subtype),
+ pivot_value_to_string_defaults (item->text.content));
+ break;
+ }
+}
+\f
+void
+output_iterator_init (struct output_iterator *iter,
+ const struct output_item *item)
+{
+ *iter = (struct output_iterator) OUTPUT_ITERATOR_INIT (item);
+}
+
+void
+output_iterator_destroy (struct output_iterator *iter)
+{
+ if (iter)
+ {
+ free (iter->nodes);
+ iter->nodes = NULL;
+ iter->n = iter->allocated = 0;
+ }
+}
+
+void
+output_iterator_next (struct output_iterator *iter)
+{
+ const struct output_item *cur = iter->cur;
+ if (cur)
+ {
+ if (cur->type == OUTPUT_ITEM_GROUP && cur->group.n_children > 0)
+ {
+ if (iter->n >= iter->allocated)
+ iter->nodes = x2nrealloc (iter->nodes, &iter->allocated,
+ sizeof *iter->nodes);
+ iter->nodes[iter->n++] = (struct output_iterator_node) {
+ .group = cur,
+ .idx = 0,
+ };
+ iter->cur = cur->group.children[0];
+ return;
+ }
+
+ for (; iter->n > 0; iter->n--)
+ {
+ struct output_iterator_node *node = &iter->nodes[iter->n - 1];
+ if (++node->idx < node->group->group.n_children)
+ {
+ iter->cur = node->group->group.children[node->idx];
+ return;
+ }
+ }
+
+ iter->cur = NULL;
+ output_iterator_destroy (iter);
+ }
+}
+\f
+struct output_item *
+chart_item_create (struct chart *chart)
+{
+ struct output_item *item = xmalloc (sizeof *item);
+ *item = (struct output_item) {
+ OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_CHART),
+ .chart = chart,
+ };
+ return item;
+}