Patch #6441. Reviewed by John Darrington.
[pspp-builds.git] / src / output / html.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18 #include "chart.h"
19 #include "htmlP.h"
20 #include <errno.h>
21 #include <stdint.h>
22 #include <stdlib.h>
23 #include <ctype.h>
24 #include <time.h>
25 #include <unistd.h>
26
27 #include <libpspp/assertion.h>
28 #include <libpspp/compiler.h>
29 #include <data/file-name.h>
30 #include "error.h"
31 #include "output.h"
32 #include "manager.h"
33 #include "table.h"
34 #include <libpspp/version.h>
35
36 #include "xalloc.h"
37
38 #include "gettext.h"
39 #define _(msgid) gettext (msgid)
40
41 /* HTML driver options: (defaults listed first)
42
43    output-file="pspp.html"
44    chart-files="pspp-#.png"
45 */
46
47 static void escape_string (FILE *file,
48                            const char *text, size_t length,
49                            const char *space);
50 static bool handle_option (struct outp_driver *this,
51                            const char *key, const struct string *val);
52 static void print_title_tag (FILE *file, const char *name,
53                              const char *content);
54
55 static bool
56 html_open_driver (struct outp_driver *this, struct substring options)
57 {
58   struct html_driver_ext *x;
59
60   this->ext = x = xmalloc (sizeof *x);
61   x->file_name = xstrdup ("pspp.html");
62   x->chart_file_name = xstrdup ("pspp-#.png");
63   x->file = NULL;
64   x->chart_cnt = 0;
65
66   outp_parse_options (options, handle_option, this);
67
68   x->file = fn_open (x->file_name, "w");
69   if (x->file == NULL)
70     {
71       error (0, errno, _("opening HTML output file: %s"), x->file_name);
72       goto error;
73     }
74
75   fputs ("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n"
76          "   \"http://www.w3.org/TR/html4/loose.dtd\">\n", x->file);
77   fputs ("<HTML>\n", x->file);
78   fputs ("<HEAD>\n", x->file);
79   /* The <TITLE> tag is required, so we use a default if the user
80      didn't provide one. */
81   print_title_tag (x->file,
82                    "TITLE", outp_title ? outp_title : _("PSPP Output"));
83   fprintf (x->file, "<META NAME=\"generator\" CONTENT=\"%s\">\n", version);
84   fputs ("<META HTTP-EQUIV=\"Content-Type\" "
85          "CONTENT=\"text/html; charset=ISO-8859-1\">\n", x->file);
86   fputs ("</HEAD>\n", x->file);
87   fputs ("<BODY BGCOLOR=\"#ffffff\" TEXT=\"#000000\"\n", x->file);
88   fputs (" LINK=\"#1f00ff\" ALINK=\"#ff0000\" VLINK=\"#9900dd\">\n", x->file);
89   print_title_tag (x->file, "H1", outp_title);
90   print_title_tag (x->file, "H2", outp_subtitle);
91
92   return true;
93
94  error:
95   this->class->close_driver (this);
96   return false;
97 }
98
99 /* Emits <NAME>CONTENT</NAME> to the output, escaping CONTENT as
100    necessary for HTML. */
101 static void
102 print_title_tag (FILE *file, const char *name, const char *content)
103 {
104   if (content != NULL)
105     {
106       fprintf (file, "<%s>", name);
107       escape_string (file, content, strlen (content), " ");
108       fprintf (file, "</%s>\n", name);
109     }
110 }
111
112 static bool
113 html_close_driver (struct outp_driver *this)
114 {
115   struct html_driver_ext *x = this->ext;
116   bool ok;
117
118   if (x->file != NULL)
119     {
120       fprintf (x->file,
121                "</BODY>\n"
122                "</HTML>\n"
123                "<!-- end of file -->\n");
124       ok = fn_close (x->file_name, x->file) == 0;
125       x->file = NULL;
126     }
127   else
128     ok = true;
129   free (x->chart_file_name);
130   free (x->file_name);
131   free (x);
132
133   return ok;
134 }
135
136 /* Link the image contained in FILE_NAME to the
137    HTML stream in FILE. */
138 static void
139 link_image (FILE *file, char *file_name)
140 {
141   fprintf (file, "<IMG SRC=\"%s\"/>", file_name);
142  }
143
144 /* Generic option types. */
145 enum
146   {
147     string_arg,
148     nonneg_int_arg
149   };
150
151 /* All the options that the HTML driver supports. */
152 static const struct outp_option option_tab[] =
153   {
154     {"output-file",             string_arg,     0},
155     {"chart-files",            string_arg,     1},
156     {NULL, 0, 0},
157   };
158
159 static bool
160 handle_option (struct outp_driver *this,
161                const char *key, const struct string *val)
162 {
163   struct html_driver_ext *x = this->ext;
164   int subcat;
165
166   switch (outp_match_keyword (key, option_tab, &subcat))
167     {
168     case -1:
169       error (0, 0,
170              _("unknown configuration parameter `%s' for HTML device driver"),
171              key);
172       break;
173     case string_arg:
174       switch (subcat)
175         {
176         case 0:
177           free (x->file_name);
178           x->file_name = ds_xstrdup (val);
179           break;
180         case 1:
181           if (ds_find_char (val, '#') != SIZE_MAX)
182             {
183               free (x->chart_file_name);
184               x->chart_file_name = ds_xstrdup (val);
185             }
186           else
187             error (0, 0, _("`chart-files' value must contain `#'"));
188           break;
189         default:
190           NOT_REACHED ();
191         }
192       break;
193     default:
194       NOT_REACHED ();
195     }
196
197   return true;
198 }
199
200 static void output_tab_table (struct outp_driver *, struct tab_table *);
201
202 static void
203 html_submit (struct outp_driver *this, struct som_entity *s)
204 {
205   extern struct som_table_class tab_table_class;
206   struct html_driver_ext *x = this->ext;
207
208   assert (s->class == &tab_table_class ) ;
209
210   switch (s->type)
211     {
212     case SOM_TABLE:
213       output_tab_table ( this, (struct tab_table *) s->ext);
214       break;
215     case SOM_CHART:
216       link_image (x->file, ((struct chart *)s->ext)->file_name);
217       break;
218     default:
219       NOT_REACHED ();
220     }
221 }
222
223 /* Write LENGTH characters in TEXT to file F, escaping characters
224    as necessary for HTML.  Spaces are replaced by SPACE, which
225    should be " " or "&nbsp;". */
226 static void
227 escape_string (FILE *file,
228                const char *text, size_t length,
229                const char *space)
230 {
231   while (length-- > 0)
232     {
233       char c = *text++;
234       switch (c)
235         {
236         case '&':
237           fputs ("&amp;", file);
238           break;
239         case '<':
240           fputs ("&lt;", file);
241           break;
242         case '>':
243           fputs ("&gt;", file);
244           break;
245         case ' ':
246           fputs (space, file);
247           break;
248         default:
249           putc (c, file);
250           break;
251         }
252     }
253 }
254
255 /* Outputs content for a cell with options OPTS and contents
256    TEXT. */
257 void
258 html_put_cell_contents (struct outp_driver *this,
259                         unsigned int opts, const struct substring text)
260 {
261   struct html_driver_ext *x = this->ext;
262
263   if (!(opts & TAB_EMPTY))
264     {
265       if (opts & TAB_EMPH)
266         fputs ("<EM>", x->file);
267       if (opts & TAB_FIX)
268         {
269           fputs ("<TT>", x->file);
270           escape_string (x->file, ss_data (text), ss_length (text), "&nbsp;");
271           fputs ("</TT>", x->file);
272         }
273       else
274         {
275           size_t initial_spaces = ss_span (text, ss_cstr (CC_SPACES));
276           escape_string (x->file,
277                          ss_data (text) + initial_spaces,
278                          ss_length (text) - initial_spaces,
279                          " ");
280         }
281       if (opts & TAB_EMPH)
282         fputs ("</EM>", x->file);
283     }
284 }
285
286 /* Write table T to THIS output driver. */
287 static void
288 output_tab_table (struct outp_driver *this, struct tab_table *t)
289 {
290   struct html_driver_ext *x = this->ext;
291
292   if (t->nr == 1 && t->nc == 1)
293     {
294       fputs ("<P>", x->file);
295       html_put_cell_contents (this, t->ct[0], *t->cc);
296       fputs ("</P>\n", x->file);
297
298       return;
299     }
300
301   fputs ("<TABLE BORDER=1>\n", x->file);
302
303   if (t->title != NULL)
304     {
305       fprintf (x->file, "  <CAPTION>");
306       escape_string (x->file, t->title, strlen (t->title), " ");
307       fputs ("</CAPTION>\n", x->file);
308     }
309
310   {
311     int r;
312     unsigned char *ct = t->ct;
313
314     for (r = 0; r < t->nr; r++)
315       {
316         int c;
317
318         fputs ("  <TR>\n", x->file);
319         for (c = 0; c < t->nc; c++, ct++)
320           {
321             struct substring *cc;
322             const char *tag;
323             struct tab_joined_cell *j = NULL;
324
325             cc = t->cc + c + r * t->nc;
326             if (*ct & TAB_JOIN)
327               {
328                 j = (struct tab_joined_cell *) ss_data (*cc);
329                 cc = &j->contents;
330                 if (j->x1 != c || j->y1 != r)
331                   continue;
332               }
333
334             /* Output <TD> or <TH> tag. */
335             tag = (r < t->t || r >= t->nr - t->b
336                    || c < t->l || c >= t->nc - t->r) ? "TH" : "TD";
337             fprintf (x->file, "    <%s ALIGN=%s",
338                      tag,
339                      (*ct & TAB_ALIGN_MASK) == TAB_LEFT ? "LEFT"
340                      : (*ct & TAB_ALIGN_MASK) == TAB_RIGHT ? "RIGHT"
341                      : "CENTER");
342             if (*ct & TAB_JOIN)
343               {
344                 if (j->x2 - j->x1 > 1)
345                   fprintf (x->file, " COLSPAN=%d", j->x2 - j->x1);
346                 if (j->y2 - j->y1 > 1)
347                   fprintf (x->file, " ROWSPAN=%d", j->y2 - j->y1);
348               }
349             putc ('>', x->file);
350
351             /* Output cell contents. */
352             html_put_cell_contents (this, *ct, *cc);
353
354             /* Output </TH> or </TD>. */
355             fprintf (x->file, "</%s>\n", tag);
356           }
357         fputs ("  </TR>\n", x->file);
358       }
359   }
360
361   fputs ("</TABLE>\n\n", x->file);
362 }
363
364 static void
365 html_initialise_chart (struct outp_driver *this UNUSED, struct chart *ch)
366 {
367   struct html_driver_ext *x = this->ext;
368   chart_init_separate (ch, "png", x->chart_file_name, ++x->chart_cnt);
369 }
370
371 static void
372 html_finalise_chart(struct outp_driver *d UNUSED, struct chart *ch)
373 {
374   chart_finalise_separate (ch);
375 }
376
377
378
379 /* HTML driver class. */
380 const struct outp_class html_class =
381   {
382     "html",
383     1,
384
385     html_open_driver,
386     html_close_driver,
387
388     NULL,
389     NULL,
390     NULL,
391
392     html_submit,
393
394     NULL,
395     NULL,
396     NULL,
397     html_initialise_chart,
398     html_finalise_chart
399   };