/* PSPP - a program for statistical analysis.
- Copyright (C) 1997-9, 2000, 2007, 2009 Free Software Foundation, Inc.
+ Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2012, 2014, 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
#include <config.h>
-#include <output/driver.h>
-#include <output/driver-provider.h>
+#include "output/driver.h"
+#include "output/driver-provider.h"
#include <ctype.h>
#include <errno.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
-
-#include <data/file-name.h>
-#include <data/settings.h>
-#include <libpspp/array.h>
-#include <libpspp/assertion.h>
-#include <libpspp/string-map.h>
-#include <libpspp/string-set.h>
-#include <libpspp/str.h>
-#include <output/output-item.h>
-#include <output/text-item.h>
-
-#include "error.h"
-#include "xalloc.h"
-#include "xmemdup0.h"
+#include <time.h>
+
+#include "data/file-handle-def.h"
+#include "data/settings.h"
+#include "libpspp/array.h"
+#include "libpspp/assertion.h"
+#include "libpspp/i18n.h"
+#include "libpspp/message.h"
+#include "libpspp/llx.h"
+#include "libpspp/string-map.h"
+#include "libpspp/string-set.h"
+#include "libpspp/str.h"
+#include "output/group-item.h"
+#include "output/message-item.h"
+#include "output/output-item.h"
+#include "output/text-item.h"
+
+#include "gl/error.h"
+#include "gl/xalloc.h"
+#include "gl/xmemdup0.h"
#include "gettext.h"
#define _(msgid) gettext (msgid)
-static const struct output_driver_class *driver_classes[];
+struct output_engine
+ {
+ struct ll ll; /* Node for this engine. */
+ struct llx_list drivers; /* Contains "struct output_driver"s. */
+ struct text_item *deferred_text; /* Output text being accumulated. */
+ char *command_name; /* Name of command being processed. */
+ char *title, *subtitle; /* Components of page title. */
+
+ /* Output grouping stack.
-static struct output_driver **drivers;
-static size_t n_drivers, allocated_drivers;
+ 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. */
+ size_t n_groups;
+ size_t allocated_groups;
-static unsigned int enabled_device_types = ((1u << OUTPUT_DEVICE_UNKNOWN)
- | (1u << OUTPUT_DEVICE_LISTING)
- | (1u << OUTPUT_DEVICE_SCREEN)
- | (1u << OUTPUT_DEVICE_PRINTER));
+ struct string_map heading_vars;
+ };
-static struct output_item *deferred_syntax;
-static bool in_command;
+static struct ll_list engine_stack = LL_INITIALIZER (engine_stack);
-void
-output_close (void)
+static const struct output_driver_factory *factories[];
+
+static struct output_engine *
+engine_stack_top (void)
{
- while (n_drivers > 0)
- {
- struct output_driver *d = drivers[--n_drivers];
- output_driver_destroy (d);
- }
+ struct ll *head = ll_head (&engine_stack);
+ if (ll_is_empty (&engine_stack))
+ return NULL;
+ return ll_data (head, struct output_engine, ll);
}
static void
-expand_macro (const char *name, struct string *dst, void *macros_)
+put_strftime (const char *key, const char *format,
+ const struct tm *tm, struct string_map *vars)
{
- const struct string_map *macros = macros_;
-
- if (!strcmp (name, "viewwidth"))
- ds_put_format (dst, "%d", settings_get_viewwidth ());
- else if (!strcmp (name, "viewlength"))
- ds_put_format (dst, "%d", settings_get_viewlength ());
- else
+ if (!string_map_find (vars, key))
{
- const char *value = string_map_find (macros, name);
- if (value != NULL)
- ds_put_cstr (dst, value);
+ char value[128];
+ strftime (value, sizeof value, format, tm);
+ string_map_insert (vars, key, value);
}
}
-/* Defines one configuration macro based on the text in BP, which
- should be of the form `KEY=VALUE'. Returns true if
- successful, false if S is not in the proper form. */
-bool
-output_define_macro (const char *s, struct string_map *macros)
+void
+output_engine_push (void)
{
- const char *key_start, *value;
- size_t key_len;
- char *key;
-
- s += strspn (s, CC_SPACES);
+ struct output_engine *e = xzalloc (sizeof (*e));
- key_start = s;
- key_len = strcspn (s, "=" CC_SPACES);
- if (key_len == 0)
- return false;
- s += key_len;
-
- s += strspn (s, CC_SPACES);
- if (*s == '=')
- s++;
+ llx_init (&e->drivers);
- s += strspn (s, CC_SPACES);
- value = s;
-
- key = xmemdup0 (key_start, key_len);
- if (!string_map_contains (macros, key))
- {
- struct string expanded_value = DS_EMPTY_INITIALIZER;
+ string_map_init (&e->heading_vars);
- fn_interp_vars (ss_cstr (value), expand_macro, ¯os, &expanded_value);
- string_map_insert_nocopy (macros, key, ds_steal_cstr (&expanded_value));
- }
- else
- free (key);
+ time_t t = time (NULL);
+ const struct tm *tm = localtime (&t);
+ put_strftime ("Date", "%x", tm, &e->heading_vars);
+ put_strftime ("Time", "%X", tm, &e->heading_vars);
- return true;
-}
-
-static void
-add_driver_names (char *to, struct string_set *names)
-{
- char *save_ptr = NULL;
- char *name;
-
- for (name = strtok_r (to, CC_SPACES, &save_ptr); name != NULL;
- name = strtok_r (NULL, CC_SPACES, &save_ptr))
- string_set_insert (names, name);
-}
-
-static void
-init_default_drivers (void)
-{
- error (0, 0, _("using default output driver configuration"));
- output_configure_driver ("list:ascii:listing:"
- "length=66 width=79 output-file=\"pspp.list\"");
-}
-
-static void
-warn_unused_drivers (const struct string_set *unused_drivers,
- const struct string_set *requested_drivers)
-{
- const struct string_set_node *node;
- const char *name;
-
- STRING_SET_FOR_EACH (name, node, unused_drivers)
- if (string_set_contains (requested_drivers, name))
- error (0, 0, _("unknown output driver `%s'"), name);
- else
- error (0, 0, _("output driver `%s' referenced but never defined"), name);
+ ll_push_head (&engine_stack, &e->ll);
}
void
-output_read_configuration (const struct string_map *macros_,
- const struct string_set *driver_names_)
+output_engine_pop (void)
{
- struct string_map macros = STRING_MAP_INITIALIZER (macros);
- struct string_set driver_names = STRING_SET_INITIALIZER (driver_names);
- char *devices_file_name = NULL;
- FILE *devices_file = NULL;
- struct string line = DS_EMPTY_INITIALIZER;
- int line_number;
-
- ds_init_empty (&line);
+ struct ll *head = ll_pop_head (&engine_stack);
+ struct output_engine *e =ll_data (head, struct output_engine, ll);
- devices_file_name = fn_search_path ("devices", config_path);
- if (devices_file_name == NULL)
- {
- error (0, 0, _("cannot find output initialization file "
- "(use `-vv' to view search path)"));
- goto exit;
- }
- devices_file = fopen (devices_file_name, "r");
- if (devices_file == NULL)
+ while (!llx_is_empty (&e->drivers))
{
- error (0, errno, _("cannot open \"%s\""), devices_file_name);
- goto exit;
- }
-
- string_map_replace_map (¯os, macros_);
- string_set_union (&driver_names, driver_names_);
- if (string_set_is_empty (&driver_names))
- string_set_insert (&driver_names, "default");
-
- line_number = 0;
- for (;;)
- {
- char *cp, *delimiter, *name;
-
- if (!ds_read_config_line (&line, &line_number, devices_file))
- {
- if (ferror (devices_file))
- error (0, errno, _("reading \"%s\""), devices_file_name);
- break;
- }
-
- cp = ds_cstr (&line);
- cp += strspn (cp, CC_SPACES);
-
- if (*cp == '\0')
- continue;
- else if (!strncmp ("define", cp, 6) && isspace ((unsigned char) cp[6]))
- {
- if (!output_define_macro (&cp[7], ¯os))
- error_at_line (0, 0, devices_file_name, line_number,
- _("\"%s\" is not a valid macro definition"),
- &cp[7]);
- continue;
- }
-
- delimiter = cp + strcspn (cp, ":=");
- name = xmemdup0 (cp, delimiter - cp);
- if (*delimiter == '=')
- {
- if (string_set_delete (&driver_names, name))
- add_driver_names (delimiter + 1, &driver_names);
- }
- else if (*delimiter == ':')
- {
- if (string_set_delete (&driver_names, name))
- {
- fn_interp_vars (ds_ss (&line), expand_macro, ¯os, &line);
- output_configure_driver (ds_cstr (&line));
- }
- }
- else
- error_at_line (0, 0, devices_file_name, line_number,
- _("syntax error"));
- free (name);
+ struct output_driver *d = llx_pop_head (&e->drivers, &llx_malloc_mgr);
+ output_driver_destroy (d);
}
+ text_item_unref (e->deferred_text);
+ free (e->command_name);
+ free (e->title);
+ free (e->subtitle);
+ for (size_t i = 0; i < e->n_groups; i++)
+ free (e->groups[i]);
+ free (e->groups);
+ string_map_destroy (&e->heading_vars);
+ free (e);
+}
- warn_unused_drivers (&driver_names, driver_names_);
-
-exit:
- if (devices_file != NULL)
- fclose (devices_file);
- free (devices_file_name);
- ds_destroy (&line);
- string_set_destroy (&driver_names);
- string_map_destroy (¯os);
+void
+output_get_supported_formats (struct string_set *formats)
+{
+ const struct output_driver_factory **fp;
- if (n_drivers == 0)
- {
- error (0, 0, _("no active output drivers"));
- init_default_drivers ();
- }
+ for (fp = factories; *fp != NULL; fp++)
+ string_set_insert (formats, (*fp)->extension);
}
-/* Obtains a token from S and advances its position. Errors are
- reported against the given DRIVER_NAME.
- The token is stored in TOKEN. Returns true if successful,
- false on syntax error.
-
- Caller is responsible for skipping leading spaces. */
-static bool
-get_option_token (char **s_, const char *driver_name,
- struct string *token)
+static void
+output_submit__ (struct output_engine *e, struct output_item *item)
{
- struct substring s = ss_cstr (*s_);
- int c;
+ struct llx *llx, *next;
- ds_clear (token);
- c = ss_get_char (&s);
- if (c == EOF)
+ for (llx = llx_head (&e->drivers); llx != llx_null (&e->drivers); llx = next)
{
- error (0, 0, _("syntax error parsing options for \"%s\" driver"),
- driver_name);
- return false;
- }
- else if (c == '\'' || c == '"')
- {
- int quote = c;
+ struct output_driver *d = llx_data (llx);
+ enum settings_output_type type;
+
+ next = llx_next (llx);
- for (;;)
+ if (is_message_item (item))
{
- c = ss_get_char (&s);
- if (c == quote)
- break;
- else if (c == EOF)
- {
- error (0, 0,
- _("reached end of options inside quoted string "
- "parsing options for \"%s\" driver"),
- driver_name);
- return false;
- }
- else if (c != '\\')
- ds_put_char (token, c);
+ const struct msg *m = message_item_get_msg (to_message_item (item));
+ if (m->severity == MSG_S_NOTE)
+ type = SETTINGS_OUTPUT_NOTE;
else
- {
- int out;
-
- c = ss_get_char (&s);
- switch (c)
- {
- case '\'':
- out = '\'';
- break;
- case '"':
- out = '"';
- break;
- case '\\':
- out = '\\';
- break;
- case 'a':
- out = '\a';
- break;
- case 'b':
- out = '\b';
- break;
- case 'f':
- out = '\f';
- break;
- case 'n':
- out = '\n';
- break;
- case 'r':
- out = '\r';
- break;
- case 't':
- out = '\t';
- break;
- case 'v':
- out = '\v';
- break;
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- out = c - '0';
- while (ss_first (s) >= '0' && ss_first (s) <= '7')
- out = out * 8 + (ss_get_char (&s) - '0');
- break;
- case 'x':
- case 'X':
- out = 0;
- while (isxdigit (ss_first (s)))
- {
- c = ss_get_char (&s);
- out *= 16;
- if (isdigit (c))
- out += c - '0';
- else
- out += tolower (c) - 'a' + 10;
- }
- break;
- default:
- error (0, 0, _("syntax error in string constant "
- "parsing options for \"%s\" driver"),
- driver_name);
- return false;
- }
- ds_put_char (token, out);
- }
+ type = SETTINGS_OUTPUT_ERROR;
}
- }
- else
- {
- for (;;)
- {
- ds_put_char (token, c);
+ else if (is_text_item (item)
+ && text_item_get_type (to_text_item (item)) == TEXT_ITEM_SYNTAX)
+ type = SETTINGS_OUTPUT_SYNTAX;
+ else
+ type = SETTINGS_OUTPUT_RESULT;
- c = ss_first (s);
- if (c == EOF || c == '=' || isspace (c))
- break;
- ss_advance (&s, 1);
- }
+ if (settings_get_output_routing (type) & d->device_type)
+ d->class->submit (d, item);
}
- *s_ = s.string;
- return 1;
+ output_item_unref (item);
}
static void
-parse_options (const char *driver_name, char *options,
- struct string_map *option_map)
+flush_deferred_text (struct output_engine *e)
{
- struct string key = DS_EMPTY_INITIALIZER;
- struct string value = DS_EMPTY_INITIALIZER;
-
- for (;;)
+ struct text_item *deferred_text = e->deferred_text;
+ if (deferred_text)
{
- options += strspn (options, CC_SPACES);
- if (*options == '\0')
- break;
-
- if (!get_option_token (&options, driver_name, &key))
- break;
-
- options += strspn (options, CC_SPACES);
- if (*options != '=')
- {
- error (0, 0, _("syntax error expecting `=' "
- "parsing options for driver \"%s\""),
- driver_name);
- break;
- }
- options++;
-
- options += strspn (options, CC_SPACES);
- if (!get_option_token (&options, driver_name, &value))
- break;
-
- if (string_map_contains (option_map, ds_cstr (&key)))
- error (0, 0, _("driver \"%s\" defines option \"%s\" multiple times"),
- driver_name, ds_cstr (&key));
- else
- string_map_insert (option_map, ds_cstr (&key), ds_cstr (&value));
+ e->deferred_text = NULL;
+ output_submit__ (e, text_item_super (deferred_text));
}
-
- ds_destroy (&key);
- ds_destroy (&value);
}
-static char *
-trim_token (char *token)
+static bool
+defer_text (struct output_engine *e, struct output_item *output_item)
{
- if (token != NULL)
- {
- char *end;
-
- /* Trim leading spaces. */
- while (isspace ((unsigned char) *token))
- token++;
-
- /* Trim trailing spaces. */
- end = strchr (token, '\0');
- while (end > token && isspace ((unsigned char) end[-1]))
- *--end = '\0';
+ if (!is_text_item (output_item))
+ return false;
- /* An empty token is a null token. */
- if (*token == '\0')
- return NULL;
+ 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 token;
+ return true;
}
-static const struct output_driver_class *
-find_output_class (const char *name)
+/* Submits ITEM to the configured output drivers, and transfers ownership to
+ the output subsystem. */
+void
+output_submit (struct output_item *item)
{
- const struct output_driver_class **classp;
+ struct output_engine *e = engine_stack_top ();
- for (classp = driver_classes; *classp != NULL; classp++)
- if (!strcmp ((*classp)->name, name))
- break;
+ if (e == NULL)
+ return;
- return *classp;
-}
+ if (item == NULL)
+ return;
-static struct output_driver *
-create_driver (const char *driver_name, const char *class_name,
- const char *device_type, struct string_map *options)
-{
- const struct output_driver_class *class;
- enum output_device_type type;
- struct output_driver *driver;
+ if (defer_text (e, item))
+ return;
+ flush_deferred_text (e);
- type = OUTPUT_DEVICE_UNKNOWN;
- if (device_type != NULL && device_type[0] != '\0')
+ if (is_group_open_item (item))
{
- if (!strcmp (device_type, "listing"))
- type = OUTPUT_DEVICE_LISTING;
- else if (!strcmp (device_type, "screen"))
- type = OUTPUT_DEVICE_SCREEN;
- else if (!strcmp (device_type, "printer"))
- type = OUTPUT_DEVICE_PRINTER;
- else
- error (0, 0, _("unknown device type `%s'"), device_type);
+ 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);
- class = find_output_class (class_name);
- if (class != NULL)
- driver = class->create (driver_name, type, options);
- else
+ 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))
{
- error (0, 0, _("unknown output driver class `%s'"), class_name);
- driver = NULL;
+ 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);
}
- string_map_destroy (options);
-
- return driver;
+ output_submit__ (e, item);
}
-struct output_driver *
-output_driver_create (const char *class_name, struct string_map *options)
-{
- return create_driver (class_name, class_name, NULL, options);
-}
+/* Returns the name of the command currently being parsed, in all uppercase.
+ The caller must free the returned value.
-/* String LINE is in format:
- DRIVERNAME:CLASSNAME:DEVICETYPE:OPTIONS
-*/
-void
-output_configure_driver (const char *line_)
+ Returns NULL if no command is being parsed. */
+char *
+output_get_command_name (void)
{
- char *save_ptr = NULL;
- char *line = xstrdup (line_);
- char *driver_name = trim_token (strtok_r (line, ":", &save_ptr));
- char *class_name = trim_token (strtok_r (NULL, ":", &save_ptr));
- char *device_type = trim_token (strtok_r (NULL, ":", &save_ptr));
- char *options = trim_token (strtok_r (NULL, "", &save_ptr));
-
- if (driver_name && class_name)
- {
- struct string_map option_map;
- struct output_driver *driver;
+ struct output_engine *e = engine_stack_top ();
+ if (e == NULL)
+ return NULL;
- string_map_init (&option_map);
- if (options != NULL)
- parse_options (driver_name, options, &option_map);
+ for (size_t i = e->n_groups; i-- > 0;)
+ if (e->groups[i])
+ return utf8_to_upper (e->groups[i]);
- driver = create_driver (driver_name, class_name,
- device_type, &option_map);
- if (driver != NULL)
- output_driver_register (driver);
- }
- else
- error (0, 0,
- _("driver definition line missing driver name or class name"));
-
- free (line);
+ return NULL;
}
-/* Display on stdout a list of all registered driver classes. */
+/* Flushes output to screen devices, so that the user can see
+ output that doesn't fill up an entire page. */
void
-output_list_classes (void)
+output_flush (void)
{
- const struct output_driver_class **classp;
-
- printf (_("Driver classes:"));
- for (classp = driver_classes; *classp != NULL; classp++)
- printf (" %s", (*classp)->name);
- putc ('\n', stdout);
-}
+ struct output_engine *e = engine_stack_top ();
+ struct llx *llx;
-static bool
-driver_is_enabled (const struct output_driver *d)
-{
- return (1u << d->device_type) & enabled_device_types;
+ flush_deferred_text (e);
+ for (llx = llx_head (&e->drivers); llx != llx_null (&e->drivers);
+ llx = llx_next (llx))
+ {
+ struct output_driver *d = llx_data (llx);
+ if (d->device_type & SETTINGS_DEVICE_TERMINAL && d->class->flush != NULL)
+ d->class->flush (d);
+ }
}
static void
-output_submit__ (struct output_item *item)
+output_set_title__ (struct output_engine *e, char **dst, const char *src)
{
- size_t i;
-
- for (i = 0; i < n_drivers; i++)
- {
- struct output_driver *d = drivers[i];
- if (driver_is_enabled (d))
- d->class->submit (d, item);
- }
-
- output_item_unref (item);
+ free (*dst);
+ *dst = src ? xstrdup (src) : NULL;
+
+ char *page_title
+ = (e->title && e->subtitle ? xasprintf ("%s\n%s", e->title, e->subtitle)
+ : e->title ? xstrdup (e->title)
+ : e->subtitle ? xstrdup (e->subtitle)
+ : xzalloc (1));
+ text_item_submit (text_item_create_nocopy (TEXT_ITEM_PAGE_TITLE,
+ page_title, NULL));
}
-/* Submits ITEM to the configured output drivers, and transfers ownership to
- the output subsystem. */
void
-output_submit (struct output_item *item)
+output_set_title (const char *title)
{
- if (is_text_item (item))
- {
- struct text_item *text = to_text_item (item);
- switch (text_item_get_type (text))
- {
- case TEXT_ITEM_SYNTAX:
- if (!in_command)
- {
- if (deferred_syntax != NULL)
- output_submit__ (deferred_syntax);
- deferred_syntax = item;
- return;
- }
- break;
-
- case TEXT_ITEM_COMMAND_OPEN:
- output_submit__ (item);
- if (deferred_syntax != NULL)
- {
- output_submit__ (deferred_syntax);
- deferred_syntax = NULL;
- }
- in_command = true;
- return;
+ struct output_engine *e = engine_stack_top ();
- case TEXT_ITEM_COMMAND_CLOSE:
- in_command = false;
- break;
-
- default:
- break;
- }
- }
-
- output_submit__ (item);
+ output_set_title__ (e, &e->title, title);
}
-/* Flushes output to screen devices, so that the user can see
- output that doesn't fill up an entire page. */
void
-output_flush (void)
+output_set_subtitle (const char *subtitle)
{
- size_t i;
+ struct output_engine *e = engine_stack_top ();
- for (i = 0; i < n_drivers; i++)
- {
- struct output_driver *d = drivers[i];
- if (driver_is_enabled (d) && d->class->flush != NULL)
- d->class->flush (d);
- }
-}
-
-unsigned int
-output_get_enabled_types (void)
-{
- return enabled_device_types;
+ output_set_title__ (e, &e->subtitle, subtitle);
}
void
-output_set_enabled_types (unsigned int types)
+output_set_filename (const char *filename)
{
- enabled_device_types = types;
+ struct output_engine *e = engine_stack_top ();
+
+ string_map_replace (&e->heading_vars, "Filename", filename);
}
-void
-output_set_type_enabled (bool enable, enum output_device_type type)
+size_t
+output_get_group_level (void)
{
- unsigned int bit = 1u << type;
- if (enable)
- enabled_device_types |= bit;
- else
- enabled_device_types |= ~bit;
+ struct output_engine *e = engine_stack_top ();
+
+ return e->n_groups;
}
\f
void
output_driver_init (struct output_driver *driver,
const struct output_driver_class *class,
- const char *name, enum output_device_type type)
+ const char *name, enum settings_output_devices type)
{
driver->class = class;
driver->name = xstrdup (name);
return driver->name;
}
\f
+static struct output_engine *
+output_driver_get_engine (const struct output_driver *driver)
+{
+ struct output_engine *e;
+
+ ll_for_each (e, struct output_engine, ll, &engine_stack)
+ {
+ if (llx_find (llx_head (&e->drivers), llx_null (&e->drivers), driver))
+ return e;
+ }
+
+ return NULL;
+}
+
void
output_driver_register (struct output_driver *driver)
{
+ struct output_engine *e = engine_stack_top ();
+
assert (!output_driver_is_registered (driver));
- if (n_drivers >= allocated_drivers)
- drivers = x2nrealloc (drivers, &allocated_drivers, sizeof *drivers);
- drivers[n_drivers++] = driver;
+ llx_push_tail (&e->drivers, driver, &llx_malloc_mgr);
}
void
output_driver_unregister (struct output_driver *driver)
{
- size_t i;
-
- for (i = 0; i < n_drivers; i++)
- if (drivers[i] == driver)
- {
- remove_element (drivers, n_drivers, sizeof *drivers, i);
- return;
- }
- NOT_REACHED ();
+ struct output_engine *e = output_driver_get_engine (driver);
+
+ assert (e != NULL);
+ llx_remove (llx_find (llx_head (&e->drivers), llx_null (&e->drivers), driver),
+ &llx_malloc_mgr);
}
bool
output_driver_is_registered (const struct output_driver *driver)
{
- size_t i;
-
- for (i = 0; i < n_drivers; i++)
- if (drivers[i] == driver)
- return true;
- return false;
+ return output_driver_get_engine (driver) != NULL;
}
\f
-/* Known driver classes. */
+extern const struct output_driver_factory txt_driver_factory;
+extern const struct output_driver_factory list_driver_factory;
+extern const struct output_driver_factory html_driver_factory;
+extern const struct output_driver_factory csv_driver_factory;
+extern const struct output_driver_factory odt_driver_factory;
+extern const struct output_driver_factory spv_driver_factory;
+#ifdef HAVE_CAIRO
+extern const struct output_driver_factory pdf_driver_factory;
+extern const struct output_driver_factory ps_driver_factory;
+extern const struct output_driver_factory svg_driver_factory;
+extern const struct output_driver_factory png_driver_factory;
+#endif
+extern const struct output_driver_factory tex_driver_factory;
-static const struct output_driver_class *driver_classes[] =
+static const struct output_driver_factory *factories[] =
{
- &ascii_class,
+ &txt_driver_factory,
+ &list_driver_factory,
+ &html_driver_factory,
+ &csv_driver_factory,
+ &odt_driver_factory,
+ &spv_driver_factory,
#ifdef HAVE_CAIRO
- &cairo_class,
+ &pdf_driver_factory,
+ &ps_driver_factory,
+ &svg_driver_factory,
+ &png_driver_factory,
#endif
- &html_class,
- &odt_class,
- &csv_class,
- NULL,
+ &tex_driver_factory,
+ NULL
};
+static const struct output_driver_factory *
+find_factory (const char *format)
+{
+ const struct output_driver_factory **fp;
+
+ for (fp = factories; *fp != NULL; fp++)
+ {
+ const struct output_driver_factory *f = *fp;
+
+ if (!strcmp (f->extension, format))
+ return f;
+ }
+ return &txt_driver_factory;
+}
+
+static enum settings_output_devices
+default_device_type (const char *file_name)
+{
+ return (!strcmp (file_name, "-")
+ ? SETTINGS_DEVICE_TERMINAL
+ : SETTINGS_DEVICE_LISTING);
+}
+
+struct output_driver *
+output_driver_create (struct string_map *options)
+{
+ enum settings_output_devices device_type;
+ const struct output_driver_factory *f;
+ struct output_driver *driver;
+ char *device_string;
+ char *file_name;
+ char *format;
+
+ format = string_map_find_and_delete (options, "format");
+ file_name = string_map_find_and_delete (options, "output-file");
+
+ if (format == NULL)
+ {
+ if (file_name != NULL)
+ {
+ const char *extension = strrchr (file_name, '.');
+ format = xstrdup (extension != NULL ? extension + 1 : "");
+ }
+ else
+ format = xstrdup ("txt");
+ }
+ f = find_factory (format);
+
+ if (file_name == NULL)
+ file_name = xstrdup (f->default_file_name);
+
+ /* XXX should use parse_enum(). */
+ device_string = string_map_find_and_delete (options, "device");
+ if (device_string == NULL || device_string[0] == '\0')
+ device_type = default_device_type (file_name);
+ else if (!strcmp (device_string, "terminal"))
+ device_type = SETTINGS_DEVICE_TERMINAL;
+ else if (!strcmp (device_string, "listing"))
+ device_type = SETTINGS_DEVICE_LISTING;
+ else
+ {
+ msg (MW, _("%s is not a valid device type (the choices are `%s' and `%s')"),
+ device_string, "terminal", "listing");
+ device_type = default_device_type (file_name);
+ }
+
+ struct file_handle *fh = fh_create_file (NULL, file_name, NULL, fh_default_properties ());
+
+ driver = f->create (fh, device_type, options);
+ if (driver != NULL)
+ {
+ const struct string_map_node *node;
+ const char *key;
+
+ STRING_MAP_FOR_EACH_KEY (key, node, options)
+ msg (MW, _("%s: unknown option `%s'"), file_name, key);
+ }
+ string_map_clear (options);
+
+ free (file_name);
+ free (format);
+ free (device_string);
+
+ return driver;
+}
+
+void
+output_driver_parse_option (const char *option, struct string_map *options)
+{
+ const char *equals = strchr (option, '=');
+ if (equals == NULL)
+ {
+ error (0, 0, _("%s: output option missing `='"), option);
+ return;
+ }
+
+ char *key = xmemdup0 (option, equals - option);
+ if (string_map_contains (options, key))
+ {
+ error (0, 0, _("%s: output option specified more than once"), key);
+ free (key);
+ return;
+ }
+
+ char *value = xmemdup0 (equals + 1, strlen (equals + 1));
+ string_map_insert_nocopy (options, key, value);
+}
+\f
+/* Extracts the actual text content from the given Pango MARKUP and returns it
+ as as a malloc()'d string. */
+char *
+output_get_text_from_markup (const char *markup)
+{
+ xmlParserCtxt *parser = xmlCreatePushParserCtxt (NULL, NULL, NULL, 0, NULL);
+ if (!parser)
+ return xstrdup (markup);
+
+ xmlParseChunk (parser, "<xml>", strlen ("<xml>"), false);
+ xmlParseChunk (parser, markup, strlen (markup), false);
+ xmlParseChunk (parser, "</xml>", strlen ("</xml>"), true);
+
+ char *content
+ = (parser->wellFormed
+ ? CHAR_CAST (char *,
+ xmlNodeGetContent (xmlDocGetRootElement (parser->myDoc)))
+ : xstrdup (markup));
+ xmlFreeDoc (parser->myDoc);
+ xmlFreeParserCtxt (parser);
+
+ return content;
+}
+
+char *
+output_driver_substitute_heading_vars (const char *src, int page_number)
+{
+ struct output_engine *e = engine_stack_top ();
+ struct string dst = DS_EMPTY_INITIALIZER;
+ ds_extend (&dst, strlen (src));
+ for (const char *p = src; *p;)
+ {
+ if (!strncmp (p, "&[", 6))
+ {
+ if (page_number != INT_MIN)
+ {
+ const char *start = p + 6;
+ const char *end = strchr (start, ']');
+ if (end)
+ {
+ const char *value = string_map_find__ (&e->heading_vars,
+ start, end - start);
+ if (value)
+ ds_put_cstr (&dst, value);
+ else if (ss_equals (ss_buffer (start, end - start),
+ ss_cstr ("Page")))
+ ds_put_format (&dst, "%d", page_number);
+ p = end + 1;
+ continue;
+ }
+ }
+ ds_put_cstr (&dst, "&");
+ p += 5;
+ }
+ else
+ ds_put_byte (&dst, *p++);
+ }
+ return ds_steal_cstr (&dst);
+}