-/* PSPP - computes sample statistics.
+/* PSPP - a program for statistical analysis.
Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
- Written by Ben Pfaff <blp@gnu.org>.
- 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 <http://www.gnu.org/licenses/>. */
#include <config.h>
#include "chart.h"
#include "htmlP.h"
-#include <libpspp/message.h>
#include <errno.h>
+#include <stdint.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
-
-#if HAVE_UNISTD_H
#include <unistd.h>
-#endif
-#include <libpspp/alloc.h>
+#include <libpspp/assertion.h>
#include <libpspp/compiler.h>
-#include <libpspp/message.h>
-#include <data/filename.h>
-#include "getline.h"
-#include "getlogin_r.h"
+#include <data/file-name.h>
+#include "error.h"
#include "output.h"
#include "manager.h"
#include "table.h"
#include <libpspp/version.h>
-#include <data/make-file.h>
+
+#include "xalloc.h"
#include "gettext.h"
#define _(msgid) gettext (msgid)
-/* Prototypes. */
-static int postopen (struct file_ext *);
-static int preclose (struct file_ext *);
+/* HTML driver options: (defaults listed first)
-static int
-html_open_global (struct outp_class *this UNUSED)
-{
- return 1;
-}
+ output-file="pspp.html"
+ chart-files="pspp-#.png"
+*/
-static int
-html_close_global (struct outp_class *this UNUSED)
-{
- return 1;
-}
+static void escape_string (FILE *file,
+ const char *text, size_t length,
+ const char *space);
+static bool handle_option (struct outp_driver *this,
+ const char *key, const struct string *val);
+static void print_title_tag (FILE *file, const char *name,
+ const char *content);
-static int
-html_preopen_driver (struct outp_driver *this)
+static bool
+html_open_driver (struct outp_driver *this, struct substring options)
{
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->file_name = xstrdup ("pspp.html");
+ x->chart_file_name = xstrdup ("pspp-#.png");
+ x->file = NULL;
+ x->chart_cnt = 0;
- x->prologue_fn = NULL;
+ outp_parse_options (options, handle_option, this);
- 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;
+ x->file = fn_open (x->file_name, "w");
+ if (x->file == NULL)
+ {
+ error (0, errno, _("opening HTML output file: %s"), x->file_name);
+ goto error;
+ }
- return 1;
+ fputs ("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n"
+ " \"http://www.w3.org/TR/html4/loose.dtd\">\n", x->file);
+ fputs ("<HTML>\n", x->file);
+ fputs ("<HEAD>\n", x->file);
+ /* The <TITLE> tag is required, so we use a default if the user
+ didn't provide one. */
+ print_title_tag (x->file,
+ "TITLE", outp_title ? outp_title : _("PSPP Output"));
+ fprintf (x->file, "<META NAME=\"generator\" CONTENT=\"%s\">\n", version);
+ fputs ("<META HTTP-EQUIV=\"Content-Type\" "
+ "CONTENT=\"text/html; charset=ISO-8859-1\">\n", x->file);
+ fputs ("</HEAD>\n", x->file);
+ fputs ("<BODY BGCOLOR=\"#ffffff\" TEXT=\"#000000\"\n", x->file);
+ fputs (" LINK=\"#1f00ff\" ALINK=\"#ff0000\" VLINK=\"#9900dd\">\n", x->file);
+ print_title_tag (x->file, "H1", outp_title);
+ print_title_tag (x->file, "H2", outp_subtitle);
+
+ return true;
+
+ error:
+ this->class->close_driver (this);
+ return false;
}
-static int
-html_postopen_driver (struct outp_driver *this)
+/* Emits <NAME>CONTENT</NAME> 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 == 0);
- if (NULL == x->file.filename)
- x->file.filename = xstrdup ("pspp.html");
-
- if (x->prologue_fn == NULL)
- x->prologue_fn = xstrdup ("html-prologue");
-
- msg (VM (2), _("%s: Initialization complete."), this->name);
- this->driver_open = 1;
-
- return 1;
+ if (content != NULL)
+ {
+ fprintf (file, "<%s>", name);
+ escape_string (file, content, strlen (content), " ");
+ fprintf (file, "</%s>\n", name);
+ }
}
-static int
+static bool
html_close_driver (struct outp_driver *this)
{
struct html_driver_ext *x = this->ext;
+ bool ok;
- 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);
+ if (x->file != NULL)
+ {
+ fprintf (x->file,
+ "</BODY>\n"
+ "</HTML>\n"
+ "<!-- end of file -->\n");
+ ok = fn_close (x->file_name, x->file) == 0;
+ x->file = NULL;
+ }
+ else
+ ok = true;
+ free (x->chart_file_name);
+ free (x->file_name);
free (x);
- msg (VM (3), _("%s: Finished closing."), this->name);
- this->driver_open = 0;
-
- return 1;
-}
-
-/* Link the image contained in FILENAME to the
- HTML stream in file F. */
-static int
-link_image (struct file_ext *f, char *filename)
-{
- fprintf (f->file,
- "<IMG SRC=\"%s\"/>", filename);
-
- if (ferror (f->file))
- return 0;
-
- return 1;
+ return ok;
}
+/* Link the image contained in FILE_NAME to the
+ HTML stream in FILE. */
+static void
+link_image (FILE *file, char *file_name)
+{
+ fprintf (file, "<IMG SRC=\"%s\"/>", file_name);
+ }
/* Generic option types. */
enum
-{
- boolean_arg = -10,
- string_arg,
- nonneg_int_arg
-};
+ {
+ string_arg,
+ nonneg_int_arg
+ };
/* 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;
+static const struct outp_option option_tab[] =
+ {
+ {"output-file", string_arg, 0},
+ {"chart-files", string_arg, 1},
+ {NULL, 0, 0},
+ };
-static void
-html_option (struct outp_driver *this, const char *key, const struct string *val)
+static bool
+handle_option (struct outp_driver *this,
+ const char *key, const struct string *val)
{
struct html_driver_ext *x = this->ext;
- int cat, subcat;
+ int subcat;
- cat = outp_match_keyword (key, option_tab, &option_info, &subcat);
- switch (cat)
+ switch (outp_match_keyword (key, option_tab, &subcat))
{
- case 0:
- msg (SE, _("Unknown configuration parameter `%s' for HTML device "
- "driver."), key);
- break;
- case 1:
- free (x->file.filename);
- x->file.filename = xstrdup (ds_c_str (val));
+ case -1:
+ error (0, 0,
+ _("unknown configuration parameter `%s' for HTML device driver"),
+ key);
break;
case string_arg:
- {
- 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));
- }
+ switch (subcat)
+ {
+ case 0:
+ free (x->file_name);
+ x->file_name = ds_xstrdup (val);
+ break;
+ case 1:
+ if (ds_find_char (val, '#') != SIZE_MAX)
+ {
+ free (x->chart_file_name);
+ x->chart_file_name = ds_xstrdup (val);
+ }
+ else
+ error (0, 0, _("`chart-files' value must contain `#'"));
+ break;
+ default:
+ NOT_REACHED ();
+ }
break;
default:
- assert (0);
+ NOT_REACHED ();
}
-}
-
-/* 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)
-{
- struct html_variable *v;
- for (v = html_var_tab; v->key; v++)
- if (!strcmp (key, v->key))
- return v->value;
- return NULL;
-}
-
-/* Writes the HTML prologue to file F. */
-static int
-postopen (struct file_ext *f)
-{
- 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)
- {
- msg (IE, _("Cannot find HTML prologue. The use of `-vv' "
- "on the command line is suggested as a debugging aid."));
- return 0;
- }
-
- msg (VM (1), _("%s: %s: Opening HTML prologue..."), this->name, prologue_fn);
- prologue_file = fopen (prologue_fn, "rb");
- if (prologue_file == NULL)
- {
- fclose (prologue_file);
- free (prologue_fn);
- msg (IE, "%s: %s", prologue_fn, strerror (errno));
- goto error;
- }
-
- dict[0].value = version;
-
- 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;
-
- dict[4].value = outp_title ? outp_title : "";
- dict[5].value = outp_subtitle ? outp_subtitle : "";
-
- html_var_tab = dict;
- while (-1 != getline (&buf, &buf_size, prologue_file))
- {
- char *buf2;
- int len;
-
- 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);
- }
- 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;
-
- msg (VM (2), _("%s: HTML prologue read successfully."), this->name);
- return 1;
-
-error:
- msg (VM (1), _("%s: Error reading HTML prologue."), this->name);
- return 0;
-}
-
-/* Writes the HTML epilogue to file F. */
-static int
-preclose (struct file_ext *f)
-{
- fprintf (f->file,
- "</BODY>\n"
- "</HTML>\n"
- "<!-- end of file -->\n");
-
- if (ferror (f->file))
- return 0;
- return 1;
-}
-
-static int
-html_open_page (struct outp_driver *this)
-{
- struct html_driver_ext *x = this->ext;
-
- assert (this->driver_open && this->page_open == 0);
- x->sequence_no++;
- if (!fn_open_ext (&x->file))
- {
- if (errno)
- msg (ME, _("HTML output driver: %s: %s"), x->file.filename,
- strerror (errno));
- return 0;
- }
-
- if (!ferror (x->file.file))
- this->page_open = 1;
- return !ferror (x->file.file);
-}
-
-static int
-html_close_page (struct outp_driver *this)
-{
- struct html_driver_ext *x = this->ext;
-
- assert (this->driver_open && this->page_open);
- this->page_open = 0;
- return !ferror (x->file.file);
+ return true;
}
static void output_tab_table (struct outp_driver *, struct tab_table *);
{
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;
- }
- assert ( s->class == &tab_table_class ) ;
+ assert (s->class == &tab_table_class ) ;
- switch (s->type)
+ switch (s->type)
{
case SOM_TABLE:
output_tab_table ( this, (struct tab_table *) s->ext);
break;
case SOM_CHART:
- link_image( &x->file, ((struct chart *)s->ext)->filename);
+ link_image (x->file, ((struct chart *)s->ext)->file_name);
break;
default:
- assert(0);
- break;
+ NOT_REACHED ();
}
-
}
-/* Write string S of length LEN to file F, escaping characters as
- necessary for HTML. */
+/* Write LENGTH characters in TEXT to file F, escaping characters
+ as necessary for HTML. Spaces are replaced by SPACE, which
+ should be " " or " ". */
static void
-escape_string (FILE *f, char *s, int len)
+escape_string (FILE *file,
+ const char *text, size_t length,
+ const char *space)
{
- char *ep = &s[len];
- char *bp, *cp;
+ while (length-- > 0)
+ {
+ char c = *text++;
+ switch (c)
+ {
+ case '&':
+ fputs ("&", file);
+ break;
+ case '<':
+ fputs ("<", file);
+ break;
+ case '>':
+ fputs (">", file);
+ break;
+ case ' ':
+ fputs (space, file);
+ break;
+ default:
+ putc (c, file);
+ break;
+ }
+ }
+}
- for (bp = cp = s; bp < ep; bp = cp)
+/* Outputs content for a cell with options OPTS and contents
+ TEXT. */
+void
+html_put_cell_contents (struct outp_driver *this,
+ unsigned int opts, const struct substring text)
+{
+ struct html_driver_ext *x = this->ext;
+
+ if (!(opts & TAB_EMPTY))
{
- 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);
- }
+ if (opts & TAB_EMPH)
+ fputs ("<EM>", x->file);
+ if (opts & TAB_FIX)
+ {
+ fputs ("<TT>", x->file);
+ escape_string (x->file, ss_data (text), ss_length (text), " ");
+ fputs ("</TT>", x->file);
+ }
+ else
+ {
+ size_t initial_spaces = ss_span (text, ss_cstr (CC_SPACES));
+ escape_string (x->file,
+ ss_data (text) + initial_spaces,
+ ss_length (text) - initial_spaces,
+ " ");
+ }
+ if (opts & TAB_EMPH)
+ fputs ("</EM>", x->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)
{
- fputs ("<P>", x->file.file);
- if (!ls_empty_p (t->cc))
- escape_string (x->file.file, ls_c_str (t->cc), ls_length (t->cc));
- fputs ("</P>\n", x->file.file);
-
+ fputs ("<P>", x->file);
+ html_put_cell_contents (this, t->ct[0], *t->cc);
+ fputs ("</P>\n", x->file);
+
return;
}
- fputs ("<TABLE BORDER=1>\n", x->file.file);
-
- if (!ls_empty_p (&t->title))
+ fputs ("<TABLE BORDER=1>\n", x->file);
+
+ if (t->title != NULL)
{
- fprintf (x->file.file, " <TR>\n <TH COLSPAN=%d>", t->nc);
- escape_string (x->file.file, ls_c_str (&t->title),
- ls_length (&t->title));
- fputs ("</TH>\n </TR>\n", x->file.file);
+ fprintf (x->file, " <CAPTION>");
+ escape_string (x->file, t->title, strlen (t->title), " ");
+ fputs ("</CAPTION>\n", x->file);
}
-
+
{
int r;
unsigned char *ct = t->ct;
for (r = 0; r < t->nr; r++)
{
int c;
-
- fputs (" <TR>\n", x->file.file);
+
+ fputs (" <TR>\n", x->file);
for (c = 0; c < t->nc; c++, ct++)
{
- struct fixed_string *cc;
- int tag;
- char header[128];
- char *cp;
+ struct substring *cc;
+ const char *tag;
struct tab_joined_cell *j = NULL;
cc = t->cc + c + r * t->nc;
- if (*ct & TAB_JOIN)
+ if (*ct & TAB_JOIN)
{
- j = (struct tab_joined_cell *) ls_c_str (cc);
+ j = (struct tab_joined_cell *) ss_data (*cc);
cc = &j->contents;
if (j->x1 != c || j->y1 != r)
- continue;
+ 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, " <T");
- *cp++ = tag;
-
- switch (*ct & TAB_ALIGN_MASK)
- {
- case TAB_RIGHT:
- cp = stpcpy (cp, " ALIGN=RIGHT");
- break;
- case TAB_LEFT:
- break;
- case TAB_CENTER:
- cp = stpcpy (cp, " ALIGN=CENTER");
- break;
- default:
- assert (0);
- }
-
+ /* Output <TD> or <TH> tag. */
+ tag = (r < t->t || r >= t->nr - t->b
+ || c < t->l || c >= t->nc - t->r) ? "TH" : "TD";
+ fprintf (x->file, " <%s ALIGN=%s",
+ tag,
+ (*ct & TAB_ALIGN_MASK) == TAB_LEFT ? "LEFT"
+ : (*ct & TAB_ALIGN_MASK) == TAB_RIGHT ? "RIGHT"
+ : "CENTER");
if (*ct & TAB_JOIN)
{
if (j->x2 - j->x1 > 1)
- cp = spprintf (cp, " COLSPAN=%d", j->x2 - j->x1);
+ fprintf (x->file, " 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, " ROWSPAN=%d", j->y2 - j->y1);
}
+ putc ('>', x->file);
- fprintf (x->file.file, "</T%c>\n", tag);
+ /* Output cell contents. */
+ html_put_cell_contents (this, *ct, *cc);
+
+ /* Output </TH> or </TD>. */
+ fprintf (x->file, "</%s>\n", tag);
}
- fputs (" </TR>\n", x->file.file);
+ fputs (" </TR>\n", x->file);
}
}
-
- fputs ("</TABLE>\n\n", x->file.file);
+
+ fputs ("</TABLE>\n\n", x->file);
}
static void
-html_initialise_chart(struct outp_driver *d UNUSED, struct chart *ch)
+html_initialise_chart (struct outp_driver *this UNUSED, struct chart *ch)
{
-
- FILE *fp;
-
- make_unique_file_stream(&fp, &ch->filename);
-
-#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
-
+ struct html_driver_ext *x = this->ext;
+ chart_init_separate (ch, "png", x->chart_file_name, ++x->chart_cnt);
}
-static void
+static void
html_finalise_chart(struct outp_driver *d UNUSED, struct chart *ch)
{
- free(ch->filename);
+ chart_finalise_separate (ch);
}
/* HTML driver class. */
-struct outp_class html_class =
-{
- "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
-
-};
+const struct outp_class html_class =
+ {
+ "html",
+ 1,
+
+ html_open_driver,
+ html_close_driver,
+
+ NULL,
+ NULL,
+ NULL,
+
+ html_submit,
+
+ NULL,
+ NULL,
+ NULL,
+ html_initialise_chart,
+ html_finalise_chart
+ };