+static void
+flush_deferred_text (struct output_engine *e)
+{
+ struct text_item *deferred_text = e->deferred_text;
+ if (deferred_text)
+ {
+ e->deferred_text = NULL;
+ output_submit__ (e, text_item_super (deferred_text));
+ }
+}
+
+static bool
+defer_text (struct output_engine *e, struct output_item *output_item)
+{
+ if (!is_text_item (output_item))
+ return false;
+
+ struct text_item *text = to_text_item (output_item);
+ if (!e->deferred_text)
+ e->deferred_text = text_item_unshare (text);
+ else if (text_item_append (e->deferred_text, text))
+ text_item_unref (text);
+ else
+ {
+ flush_deferred_text (e);
+ e->deferred_text = text_item_unshare (text);
+ }
+ return true;
+}
+
+/* Submits ITEM to the configured output drivers, and transfers ownership to
+ the output subsystem. */
+void
+output_submit (struct output_item *item)
+{
+ struct output_engine *e = engine_stack_top ();
+
+ if (e == NULL)
+ return;
+
+ if (item == NULL)
+ return;
+
+ if (defer_text (e, item))
+ return;
+ flush_deferred_text (e);
+
+ if (is_group_open_item (item))
+ {
+ const struct group_open_item *group_open_item
+ = to_group_open_item (item);
+ if (e->n_groups >= e->allocated_groups)
+ e->groups = x2nrealloc (e->groups, &e->allocated_groups,
+ sizeof *e->groups);
+ e->groups[e->n_groups] = (group_open_item->command_name
+ ? xstrdup (group_open_item->command_name)
+ : NULL);
+ e->n_groups++;
+ }
+ else if (is_group_close_item (item))
+ {
+ assert (e->n_groups > 0);
+
+ size_t idx = --e->n_groups;
+ free (e->groups[idx]);
+ if (idx >= 1 && idx <= 4)
+ {
+ char *key = xasprintf ("Head%zu", idx);
+ free (string_map_find_and_delete (&e->heading_vars, key));
+ free (key);
+ }
+ }
+ else if (is_text_item (item))
+ {
+ const struct text_item *text_item = to_text_item (item);
+ enum text_item_type type = text_item_get_type (text_item);
+ const char *text = text_item_get_text (text_item);
+ if (type == TEXT_ITEM_TITLE
+ && e->n_groups >= 1 && e->n_groups <= 4)
+ {
+ char *key = xasprintf ("Head%zu", e->n_groups);
+ string_map_replace (&e->heading_vars, key, text);
+ free (key);
+ }
+ else if (type == TEXT_ITEM_PAGE_TITLE)
+ string_map_replace (&e->heading_vars, "PageTitle", text);
+ }
+
+ output_submit__ (e, item);
+}
+
+/* Returns the name of the command currently being parsed, in all uppercase.
+ The caller must free the returned value.
+
+ Returns NULL if no command is being parsed. */
+char *
+output_get_command_name (void)
+{
+ struct output_engine *e = engine_stack_top ();
+ if (e == NULL)
+ return NULL;
+
+ for (size_t i = e->n_groups; i-- > 0;)
+ if (e->groups[i])
+ return utf8_to_upper (e->groups[i]);
+
+ return NULL;
+}
+