X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Foutput%2Fhtml.c;h=a2e63f943888169a1e30458f6b7d0b100bc6e102;hb=a4b365ed435256d40b6617408d1e375c5139ffba;hp=1cf65839bb3751eb56069fd0b7e39371e0d409df;hpb=b815c86970094c38080967a527c2d39312f601f1;p=pspp diff --git a/src/output/html.c b/src/output/html.c index 1cf65839bb..a2e63f9438 100644 --- a/src/output/html.c +++ b/src/output/html.c @@ -1,647 +1,747 @@ -/* PSPP - computes sample statistics. - Copyright (C) 1997-9, 2000 Free Software Foundation, Inc. - Written by Ben Pfaff . +/* PSPP - a program for statistical analysis. + Copyright (C) 1997-9, 2000, 2009, 2010, 2011, 2012, 2013, 2014, 2017, + 2020 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 2 of the - License, or (at your option) any later version. + 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. + 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, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - 02110-1301, USA. */ + along with this program. If not, see . */ #include -#include "chart.h" -#include "htmlP.h" -#include "message.h" + #include +#include #include #include #include - -#if HAVE_UNISTD_H #include -#endif - -#include "alloc.h" -#include "compiler.h" -#include "message.h" -#include "filename.h" -#include "getline.h" -#include "getlogin_r.h" -#include "output.h" -#include "manager.h" -#include "table.h" -#include "version.h" -#include "make-file.h" +#include + +#include "data/file-name.h" +#include "data/file-handle-def.h" +#include "libpspp/assertion.h" +#include "libpspp/cast.h" +#include "libpspp/compiler.h" +#include "libpspp/i18n.h" +#include "libpspp/message.h" +#include "libpspp/version.h" +#include "output/cairo-chart.h" +#include "output/chart.h" +#include "output/driver-provider.h" +#include "output/options.h" +#include "output/output-item.h" +#include "output/pivot-output.h" +#include "output/pivot-table.h" +#include "output/table-provider.h" + +#include "gl/minmax.h" +#include "gl/xalloc.h" #include "gettext.h" #define _(msgid) gettext (msgid) -/* Prototypes. */ -static int postopen (struct file_ext *); -static int preclose (struct file_ext *); +/* This file uses TABLE_HORZ and TABLE_VERT enough to warrant abbreviating. */ +#define H TABLE_HORZ +#define V TABLE_VERT + +struct html_driver + { + struct output_driver driver; + struct cell_color fg; + struct cell_color bg; + struct file_handle *handle; + char *chart_file_name; + + FILE *file; + size_t n_charts; + + bool bare; + bool css; + bool borders; + }; + +static const struct output_driver_class html_driver_class; + +static void html_output_table (struct html_driver *, + const struct output_item *); +static void escape_string (FILE *file, const char *text, + const char *space, const char *newline); +static void print_title_tag (FILE *file, const char *name, + const char *content); -static int -html_open_global (struct outp_class *this UNUSED) +static struct html_driver * +html_driver_cast (struct output_driver *driver) { - return 1; + assert (driver->class == &html_driver_class); + return UP_CAST (driver, struct html_driver, driver); } -static int -html_close_global (struct outp_class *this UNUSED) +static struct driver_option * +opt (struct output_driver *d, struct string_map *options, const char *key, + const char *default_value) { - return 1; + return driver_option_get (d, options, key, default_value); } -static int -html_preopen_driver (struct outp_driver *this) +static void +put_header (struct html_driver *html) { - struct html_driver_ext *x; - - assert (this->driver_open == 0); - msg (VM (1), _("HTML driver initializing as `%s'..."), this->name); - - this->ext = x = xmalloc (sizeof *x); - this->res = 0; - this->horiz = this->vert = 0; - this->width = this->length = 0; - - this->cp_x = this->cp_y = 0; - - x->prologue_fn = NULL; - - x->file.filename = NULL; - x->file.mode = "w"; - x->file.file = NULL; - x->file.sequence_no = &x->sequence_no; - x->file.param = this; - x->file.postopen = postopen; - x->file.preclose = preclose; - - x->sequence_no = 0; - - return 1; + fputs ("\n", html->file); + fprintf (html->file, "file, " lang=\"%s\"", ln); + free (ln); + fprintf (html->file, ">\n"); + fputs ("\n", html->file); + print_title_tag (html->file, "title", _("PSPP Output")); + fprintf (html->file, "\n", version); + fputs ("\n", html->file); + + if (html->css) + { + fputs ("\n", + html->file); + } + fputs ("\n", html->file); + fputs ("\n", html->file); } -static int -html_postopen_driver (struct outp_driver *this) +static struct output_driver * +html_create (struct file_handle *fh, enum settings_output_devices device_type, + struct string_map *o) { - struct html_driver_ext *x = this->ext; + struct output_driver *d; + struct html_driver *html = XZALLOC (struct html_driver); + d = &html->driver; + output_driver_init (&html->driver, &html_driver_class, fh_get_file_name (fh), + device_type); + html->bare = parse_boolean (opt (d, o, "bare", "false")); + html->css = parse_boolean (opt (d, o, "css", "true")); + html->borders = parse_boolean (opt (d, o, "borders", "true")); + + html->handle = fh; + html->chart_file_name = parse_chart_file_name (opt (d, o, "charts", + fh_get_file_name (fh))); + html->file = NULL; + html->n_charts = 1; + html->bg = parse_color (opt (d, o, "background-color", "#FFFFFFFFFFFF")); + html->fg = parse_color (opt (d, o, "foreground-color", "#000000000000")); + html->file = fn_open (html->handle, "w"); + if (html->file == NULL) + { + msg_error (errno, _("error opening output file `%s'"), fh_get_file_name (html->handle)); + goto error; + } - assert (this->driver_open == 0); - if (NULL == x->file.filename) - x->file.filename = xstrdup ("pspp.html"); - - if (x->prologue_fn == NULL) - x->prologue_fn = xstrdup ("html-prologue"); + if (!html->bare) + put_header (html); - msg (VM (2), _("%s: Initialization complete."), this->name); - this->driver_open = 1; + return d; - return 1; + error: + output_driver_destroy (d); + return NULL; } -static int -html_close_driver (struct outp_driver *this) +/* Emits CONTENT to the output, escaping CONTENT as + necessary for HTML. */ +static void +print_title_tag (FILE *file, const char *name, const char *content) { - struct html_driver_ext *x = this->ext; - - assert (this->driver_open); - msg (VM (2), _("%s: Beginning closing..."), this->name); - fn_close_ext (&x->file); - free (x->prologue_fn); - free (x->file.filename); - free (x); - msg (VM (3), _("%s: Finished closing."), this->name); - this->driver_open = 0; - - return 1; + if (content != NULL) + { + fprintf (file, "<%s>", name); + escape_string (file, content, " ", " - "); + fprintf (file, "\n", name); + } } +static void +html_destroy (struct output_driver *driver) +{ + struct html_driver *html = html_driver_cast (driver); + + if (html->file != NULL) + { + if (!html->bare) + fprintf (html->file, + "\n" + "\n" + "\n"); + fn_close (html->handle, html->file); + } + free (html->chart_file_name); + fh_unref (html->handle); + free (html); +} -/* Link the image contained in FILENAME to the - HTML stream in file F. */ -static int -link_image (struct file_ext *f, char *filename) +static void +html_submit__ (struct output_driver *driver, const struct output_item *item, + int level) { - fprintf (f->file, - "", filename); + struct html_driver *html = html_driver_cast (driver); - if (ferror (f->file)) - return 0; + switch (item->type) + { + case OUTPUT_ITEM_CHART: + if (html->chart_file_name) + { + char *file_name = xr_draw_png_chart (item->chart, + html->chart_file_name, + html->n_charts++, + &html->fg, &html->bg); + if (file_name != NULL) + { + const char *title = chart_get_title (item->chart); + fprintf (html->file, "\"chart:", + file_name, title ? title : _("No description")); + free (file_name); + } + } + break; - return 1; -} + 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: + if (html->chart_file_name) + { + char *file_name = xr_write_png_image ( + item->image, html->chart_file_name, ++html->n_charts); + if (file_name != NULL) + { + fprintf (html->file, "", file_name); + free (file_name); + } + } + break; -/* Generic option types. */ -enum -{ - boolean_arg = -10, - string_arg, - nonneg_int_arg -}; + case OUTPUT_ITEM_MESSAGE: + fprintf (html->file, "

"); -/* All the options that the HTML driver supports. */ -static struct outp_option option_tab[] = -{ - /* *INDENT-OFF* */ - {"output-file", 1, 0}, - {"prologue-file", string_arg, 0}, - {"", 0, 0}, - /* *INDENT-ON* */ -}; -static struct outp_option_info option_info; + char *s = msg_to_string (item->message); + escape_string (html->file, s, " ", "
"); + free (s); -static void -html_option (struct outp_driver *this, const char *key, const struct string *val) -{ - struct html_driver_ext *x = this->ext; - int cat, subcat; + fprintf (html->file, "

\n"); + break; - cat = outp_match_keyword (key, option_tab, &option_info, &subcat); - switch (cat) - { - case 0: - msg (SE, _("Unknown configuration parameter `%s' for HTML device " - "driver."), key); + case OUTPUT_ITEM_PAGE_BREAK: break; - case 1: - free (x->file.filename); - x->file.filename = xstrdup (ds_c_str (val)); + + case OUTPUT_ITEM_TABLE: + html_output_table (html, item); break; - case string_arg: + + case OUTPUT_ITEM_TEXT: { - char **dest; - switch (subcat) - { - case 0: - dest = &x->prologue_fn; - break; - default: - assert (0); - abort (); - } - if (*dest) - free (*dest); - *dest = xstrdup (ds_c_str (val)); + char *s = text_item_get_plain_text (item); + + switch (item->text.subtype) + { + case TEXT_ITEM_PAGE_TITLE: + break; + + case TEXT_ITEM_TITLE: + { + char tag[3] = { 'H', MIN (5, level) + '0', '\0' }; + print_title_tag (html->file, tag, s); + } + break; + + case TEXT_ITEM_SYNTAX: + fprintf (html->file, "
");
+            escape_string (html->file, s, " ", "
"); + fprintf (html->file, "
\n"); + break; + + case TEXT_ITEM_LOG: + fprintf (html->file, "

"); + escape_string (html->file, s, " ", "
"); + fprintf (html->file, "

\n"); + break; + } + + free (s); } break; - default: - assert (0); } } -/* Variables for the prologue. */ -struct html_variable - { - const char *key; - const char *value; - }; - -static struct html_variable *html_var_tab; - -/* Searches html_var_tab for a html_variable with key KEY, and returns - the associated value. */ -static const char * -html_get_var (const char *key) +static void +html_submit (struct output_driver *driver, const struct output_item *item) { - struct html_variable *v; - - for (v = html_var_tab; v->key; v++) - if (!strcmp (key, v->key)) - return v->value; - return NULL; + html_submit__ (driver, item, 1); } -/* Writes the HTML prologue to file F. */ -static int -postopen (struct file_ext *f) +/* 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. */ +static void +escape_string (FILE *file, const char *text, + const char *space, const char *newline) { - static struct html_variable dict[] = - { - {"generator", 0}, - {"date", 0}, - {"user", 0}, - {"host", 0}, - {"title", 0}, - {"subtitle", 0}, - {0, 0}, - }; - char login[128], host[128]; - time_t curtime; - struct tm *loctime; - - struct outp_driver *this = f->param; - struct html_driver_ext *x = this->ext; - - char *prologue_fn = fn_search_path (x->prologue_fn, config_path, NULL); - FILE *prologue_file; - - char *buf = NULL; - size_t buf_size = 0; - - if (prologue_fn == NULL) + for (;;) { - msg (IE, _("Cannot find HTML prologue. The use of `-vv' " - "on the command line is suggested as a debugging aid.")); - return 0; + char c = *text++; + switch (c) + { + case 0: + return; + case '\n': + fputs (newline, file); + break; + case '&': + fputs ("&", file); + break; + case '<': + fputs ("<", file); + break; + case '>': + fputs (">", file); + break; + case ' ': + fputs (space, file); + break; + case '"': + fputs (""", file); + break; + default: + putc (c, file); + break; + } } +} - msg (VM (1), _("%s: %s: Opening HTML prologue..."), this->name, prologue_fn); - prologue_file = fopen (prologue_fn, "rb"); - if (prologue_file == NULL) +static const char * +border_to_css (int border) +{ + switch (border) { - fclose (prologue_file); - free (prologue_fn); - msg (IE, "%s: %s", prologue_fn, strerror (errno)); - goto error; - } + case TABLE_STROKE_NONE: + return NULL; - dict[0].value = version; + case TABLE_STROKE_SOLID: + return "solid"; - curtime = time (NULL); - loctime = localtime (&curtime); - dict[1].value = asctime (loctime); - { - char *cp = strchr (dict[1].value, '\n'); - if (cp) - *cp = 0; - } - - if (getenv ("LOGNAME") != NULL) - str_copy_rpad (login, sizeof login, getenv ("LOGNAME")); - else if (getlogin_r (login, sizeof login)) - strcpy (login, _("nobody")); - dict[2].value = login; - -#ifdef HAVE_UNISTD_H - if (gethostname (host, 128) == -1) - { - if (errno == ENAMETOOLONG) - host[127] = 0; - else - strcpy (host, _("nowhere")); - } -#else - strcpy (host, _("nowhere")); -#endif - dict[3].value = host; + case TABLE_STROKE_DASHED: + return "dashed"; - dict[4].value = outp_title ? outp_title : ""; - dict[5].value = outp_subtitle ? outp_subtitle : ""; + case TABLE_STROKE_THICK: + return "thick solid"; - html_var_tab = dict; - while (-1 != getline (&buf, &buf_size, prologue_file)) - { - char *buf2; - int len; + case TABLE_STROKE_THIN: + return "thin solid"; - if (strstr (buf, "!!!")) - continue; - - { - char *cp = strstr (buf, "!title"); - if (cp) - { - if (outp_title == NULL) - continue; - else - *cp = '\0'; - } - } - - { - char *cp = strstr (buf, "!subtitle"); - if (cp) - { - if (outp_subtitle == NULL) - continue; - else - *cp = '\0'; - } - } - - /* PORTME: Line terminator. */ - buf2 = fn_interp_vars (buf, html_get_var); - len = strlen (buf2); - fwrite (buf2, len, 1, f->file); - if (buf2[len - 1] != '\n') - putc ('\n', f->file); - free (buf2); + case TABLE_STROKE_DOUBLE: + return "double"; + + default: + return NULL; } - if (ferror (f->file)) - msg (IE, _("Reading `%s': %s."), prologue_fn, strerror (errno)); - fclose (prologue_file); - free (prologue_fn); - free (buf); +} - if (ferror (f->file)) - goto error; +struct css_style +{ + FILE *file; + int n_styles; +}; - msg (VM (2), _("%s: HTML prologue read successfully."), this->name); - return 1; +static void +style_start (struct css_style *cs, FILE *file) +{ + *cs = (struct css_style) { + .file = file, + .n_styles = 0, + }; +} -error: - msg (VM (1), _("%s: Error reading HTML prologue."), this->name); - return 0; +static void +style_end (struct css_style *cs) +{ + if (cs->n_styles > 0) + fputs ("'", cs->file); } -/* Writes the HTML epilogue to file F. */ -static int -preclose (struct file_ext *f) +static void +next_style (struct css_style *st) { - fprintf (f->file, - "\n" - "\n" - "\n"); - - if (ferror (f->file)) - return 0; - return 1; + bool first = !st->n_styles++; + fputs (first ? " style='" : "; ", st->file); } -static int -html_open_page (struct outp_driver *this) +static void +put_style (struct css_style *st, const char *name, const char *value) { - struct html_driver_ext *x = this->ext; + next_style (st); + fprintf (st->file, "%s: %s", name, value); +} - assert (this->driver_open && this->page_open == 0); - x->sequence_no++; - if (!fn_open_ext (&x->file)) +static bool +format_color (const struct cell_color color, + const struct cell_color default_color, + char *buf, size_t bufsize) +{ + bool retval = !cell_color_equal (&color, &default_color); + if (retval) { - if (errno) - msg (ME, _("HTML output driver: %s: %s"), x->file.filename, - strerror (errno)); - return 0; + if (color.alpha == 255) + snprintf (buf, bufsize, "#%02x%02x%02x", color.r, color.g, color.b); + else + snprintf (buf, bufsize, "rgba(%d, %d, %d, %.3f)", + color.r, color.g, color.b, color.alpha / 255.0); } - - if (!ferror (x->file.file)) - this->page_open = 1; - return !ferror (x->file.file); + return retval; } -static int -html_close_page (struct outp_driver *this) +static void +put_border (const struct table *table, const struct table_cell *cell, + struct css_style *style, + enum table_axis axis, int h, int v, + const char *border_name) { - struct html_driver_ext *x = this->ext; + struct cell_color color; + const char *css = border_to_css ( + table_get_rule (table, axis, cell->d[H][h], cell->d[V][v], &color)); + if (css) + { + next_style (style); + fprintf (style->file, "border-%s: %s", border_name, css); - assert (this->driver_open && this->page_open); - this->page_open = 0; - return !ferror (x->file.file); + char buf[32]; + if (format_color (color, (struct cell_color) CELL_COLOR_BLACK, + buf, sizeof buf)) + fprintf (style->file, " %s", buf); + } } -static void output_tab_table (struct outp_driver *, struct tab_table *); - static void -html_submit (struct outp_driver *this, struct som_entity *s) +html_put_table_cell (struct html_driver *html, const struct pivot_table *pt, + const struct table_cell *cell, + const char *tag, const struct table *t) { - extern struct som_table_class tab_table_class; - struct html_driver_ext *x = this->ext; - - assert (this->driver_open && this->page_open); - if (x->sequence_no == 0 && !html_open_page (this)) - { - msg (ME, _("Cannot open first page on HTML device %s."), this->name); - return; - } + fprintf (html->file, "<%s", tag); + + struct css_style style; + style_start (&style, html->file); - assert ( s->class == &tab_table_class ) ; + struct string body = DS_EMPTY_INITIALIZER; + bool numeric = pivot_value_format_body (cell->value, pt, &body); - switch (s->type) + enum table_halign halign = table_halign_interpret (cell->cell_style->halign, + numeric); + + switch (halign) { - case SOM_TABLE: - output_tab_table ( this, (struct tab_table *) s->ext); + case TABLE_HALIGN_RIGHT: + put_style (&style, "text-align", "right"); break; - case SOM_CHART: - link_image( &x->file, ((struct chart *)s->ext)->filename); + case TABLE_HALIGN_CENTER: + put_style (&style, "text-align", "center"); break; default: - assert(0); + /* Do nothing */ break; } -} + if (cell->options & TAB_ROTATE) + put_style (&style, "writing-mode", "sideways-lr"); -/* Write string S of length LEN to file F, escaping characters as - necessary for HTML. */ -static void -escape_string (FILE *f, char *s, int len) -{ - char *ep = &s[len]; - char *bp, *cp; + if (cell->cell_style->valign != TABLE_VALIGN_TOP) + { + put_style (&style, "vertical-align", + (cell->cell_style->valign == TABLE_VALIGN_BOTTOM + ? "bottom" : "middle")); + } + + const struct font_style *fs = cell->font_style; + char bgcolor[32]; + if (format_color (fs->bg[cell->d[V][0] % 2], + (struct cell_color) CELL_COLOR_WHITE, + bgcolor, sizeof bgcolor)) + put_style (&style, "background", bgcolor); + + char fgcolor[32]; + if (format_color (fs->fg[cell->d[V][0] % 2], + (struct cell_color) CELL_COLOR_BLACK, + fgcolor, sizeof fgcolor)) + put_style (&style, "color", fgcolor); - for (bp = cp = s; bp < ep; bp = cp) + if (fs->typeface) { - while (cp < ep && *cp != '&' && *cp != '<' && *cp != '>' && *cp) - cp++; - if (cp > bp) - fwrite (bp, 1, cp - bp, f); - if (cp < ep) - switch (*cp++) - { - case '&': - fputs ("&", f); - break; - case '<': - fputs ("<", f); - break; - case '>': - fputs (">", f); - break; - case 0: - break; - default: - assert (0); - } + put_style (&style, "font-family", "\""); + escape_string (html->file, fs->typeface, " ", "\n"); + putc ('"', html->file); } -} - -/* Write table T to THIS output driver. */ -static void -output_tab_table (struct outp_driver *this, struct tab_table *t) -{ - struct html_driver_ext *x = this->ext; - - if (t->nr == 1 && t->nc == 1) + if (fs->bold) + put_style (&style, "font-weight", "bold"); + if (fs->italic) + put_style (&style, "font-style", "italic"); + if (fs->underline) + put_style (&style, "text-decoration", "underline"); + if (fs->size) { - fputs ("

", x->file.file); - if (!ls_empty_p (t->cc)) - escape_string (x->file.file, ls_c_str (t->cc), ls_length (t->cc)); - fputs ("

\n", x->file.file); - - return; + char buf[32]; + snprintf (buf, sizeof buf, "%dpt", fs->size); + put_style (&style, "font-size", buf); } - fputs ("\n", x->file.file); - - if (!ls_empty_p (&t->title)) + if (t && html->borders) { - fprintf (x->file.file, " \n \n \n", x->file.file); + put_border (t, cell, &style, V, 0, 0, "top"); + put_border (t, cell, &style, H, 0, 0, "left"); + + if (cell->d[V][1] == t->n[V]) + put_border (t, cell, &style, V, 0, 1, "bottom"); + if (cell->d[H][1] == t->n[H]) + put_border (t, cell, &style, H, 1, 0, "right"); } - - { - int r; - unsigned char *ct = t->ct; + style_end (&style); - for (r = 0; r < t->nr; r++) - { - int c; - - fputs (" \n", x->file.file); - for (c = 0; c < t->nc; c++, ct++) - { - struct fixed_string *cc; - int tag; - char header[128]; - char *cp; - struct tab_joined_cell *j = NULL; - - cc = t->cc + c + r * t->nc; - if (*ct & TAB_JOIN) - { - j = (struct tab_joined_cell *) ls_c_str (cc); - cc = &j->contents; - if (j->x1 != c || j->y1 != r) - continue; - } - - if (r < t->t || r >= t->nr - t->b - || c < t->l || c >= t->nc - t->r) - tag = 'H'; - else - tag = 'D'; - cp = stpcpy (header, " x2 - j->x1 > 1) - cp = spprintf (cp, " COLSPAN=%d", j->x2 - j->x1); - if (j->y2 - j->y1 > 1) - cp = spprintf (cp, " ROWSPAN=%d", j->y2 - j->y1); - - cc = &j->contents; - } - - strcpy (cp, ">"); - fputs (header, x->file.file); - - if ( ! (*ct & TAB_EMPTY) ) - { - char *s = ls_c_str (cc); - size_t l = ls_length (cc); - - while (l && isspace ((unsigned char) *s)) - { - l--; - s++; - } - - escape_string (x->file.file, s, l); - } - - fprintf (x->file.file, "\n", tag); - } - fputs (" \n", x->file.file); - } - } - - fputs ("
", t->nc); - escape_string (x->file.file, ls_c_str (&t->title), - ls_length (&t->title)); - fputs ("
\n\n", x->file.file); + int colspan = table_cell_colspan (cell); + if (colspan > 1) + fprintf (html->file, " colspan=\"%d\"", colspan); + + int rowspan = table_cell_rowspan (cell); + if (rowspan > 1) + fprintf (html->file, " rowspan=\"%d\"", rowspan); + + putc ('>', html->file); + + const char *s = ds_cstr (&body); + s += strspn (s, CC_SPACES); + escape_string (html->file, s, " ", "
"); + ds_destroy (&body); + + const struct pivot_value_ex *ex = pivot_value_ex (cell->value); + if (ex->n_subscripts) + { + fputs ("", html->file); + for (size_t i = 0; i < ex->n_subscripts; i++) + { + if (i) + putc (',', html->file); + escape_string (html->file, ex->subscripts[i], " ", "
"); + } + fputs ("
", html->file); + } + if (ex->n_footnotes > 0) + { + fputs ("", html->file); + size_t n_footnotes = 0; + for (size_t i = 0; i < ex->n_footnotes; i++) + { + const struct pivot_footnote *f + = pt->footnotes[ex->footnote_indexes[i]]; + if (f->show) + { + if (n_footnotes++ > 0) + putc (',', html->file); + + char *marker = pivot_footnote_marker_string (f, pt); + escape_string (html->file, marker, " ", "
"); + free (marker); + } + } + fputs ("
", html->file); + } + + /* output or . */ + fprintf (html->file, "\n", tag); } static void -html_initialise_chart(struct outp_driver *d UNUSED, struct chart *ch) +html_output_table_layer (struct html_driver *html, const struct pivot_table *pt, + const size_t *layer_indexes) { + struct table *title, *layers, *body, *caption, *footnotes; + pivot_output (pt, layer_indexes, true, &title, &layers, &body, + &caption, &footnotes, NULL, NULL); - FILE *fp; + fputs ("file); + if (pt->notes) + { + fputs (" title=\"", html->file); + escape_string (html->file, pt->notes, " ", "\n"); + putc ('"', html->file); + } + fputs (">\n", html->file); - make_unique_file_stream(&fp, &ch->filename); + if (title) + { + struct table_cell cell; + table_get_cell (title, 0, 0, &cell); + html_put_table_cell (html, pt, &cell, "caption", NULL); + } -#ifdef NO_CHARTS - ch->lp = 0; -#else - ch->pl_params = pl_newplparams(); - ch->lp = pl_newpl_r ("png", 0, fp, stderr, ch->pl_params); -#endif + if (layers) + { + fputs ("\n", html->file); + for (size_t y = 0; y < layers->n[V]; y++) + { + fputs ("\n", html->file); + + struct table_cell cell; + table_get_cell (layers, 0, y, &cell); + cell.d[H][1] = body->n[H]; + html_put_table_cell (html, pt, &cell, "td", NULL); + + fputs ("\n", html->file); + } + fputs ("\n", html->file); + } -} + fputs ("\n", html->file); + for (int y = 0; y < body->n[V]; y++) + { + fputs ("\n", html->file); + for (int x = 0; x < body->n[H]; ) + { + struct table_cell cell; + table_get_cell (body, x, y, &cell); + if (x == cell.d[TABLE_HORZ][0] && y == cell.d[TABLE_VERT][0]) + { + bool is_header = (y < body->h[V][0] + || y >= body->n[V] - body->h[V][1] + || x < body->h[H][0] + || x >= body->n[H] - body->h[H][1]); + const char *tag = is_header ? "th" : "td"; + html_put_table_cell (html, pt, &cell, tag, body); + } + + x = cell.d[TABLE_HORZ][1]; + } + fputs ("\n", html->file); + } + fputs ("\n", html->file); -static void -html_finalise_chart(struct outp_driver *d UNUSED, struct chart *ch) -{ - free(ch->filename); -} + if (caption || footnotes) + { + fprintf (html->file, "\n"); + + if (caption) + { + fputs ("\n", html->file); + + struct table_cell cell; + table_get_cell (caption, 0, 0, &cell); + cell.d[H][1] = body->n[H]; + html_put_table_cell (html, pt, &cell, "td", NULL); + + fputs ("\n", html->file); + } + + if (footnotes) + for (size_t y = 0; y < footnotes->n[V]; y++) + { + fputs ("\n", html->file); + + struct table_cell cell; + table_get_cell (footnotes, 0, y, &cell); + cell.d[H][1] = body->n[H]; + html_put_table_cell (html, pt, &cell, "td", NULL); + fputs ("\n", html->file); + } + fputs ("\n", html->file); + } + + fputs ("\n\n", html->file); + table_unref (title); + table_unref (layers); + table_unref (body); + table_unref (caption); + table_unref (footnotes); +} -/* HTML driver class. */ -struct outp_class html_class = +static void +html_output_table (struct html_driver *html, const struct output_item *item) { - "html", - 0xfaeb, - 1, - - html_open_global, - html_close_global, - NULL, - - html_preopen_driver, - html_option, - html_postopen_driver, - html_close_driver, - - html_open_page, - html_close_page, - - html_submit, - - NULL, - NULL, - NULL, - - NULL, - NULL, - NULL, - NULL, - - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - - html_initialise_chart, - html_finalise_chart + size_t *layer_indexes; + PIVOT_OUTPUT_FOR_EACH_LAYER (layer_indexes, item->table, true) + html_output_table_layer (html, item->table, layer_indexes); +} -}; +struct output_driver_factory html_driver_factory = + { "html", "pspp.html", html_create }; + +static const struct output_driver_class html_driver_class = + { + .name = "html", + .destroy = html_destroy, + .submit = html_submit, + .handles_groups = true, + };