492614731cf6ad199c7d981792237da8e3647e0d
[pspp-builds.git] / src / html.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18    02111-1307, USA. */
19
20 /* This #if encloses the rest of the file. */
21 #if !NO_HTML
22
23 #include <config.h>
24 #include "htmlP.h"
25 #include "error.h"
26 #include <errno.h>
27 #include <stdlib.h>
28 #include <ctype.h>
29 #include <time.h>
30
31 #if HAVE_UNISTD_H
32 #include <unistd.h>
33 #endif
34
35 #include "alloc.h"
36 #include "error.h"
37 #include "filename.h"
38 #include "getline.h"
39 #include "output.h"
40 #include "som.h"
41 #include "tab.h"
42 #include "version.h"
43
44 /* Prototypes. */
45 static int postopen (struct file_ext *);
46 static int preclose (struct file_ext *);
47
48 static int
49 html_open_global (struct outp_class *this UNUSED)
50 {
51   return 1;
52 }
53
54 static int
55 html_close_global (struct outp_class *this UNUSED)
56 {
57   return 1;
58 }
59
60 static int
61 html_preopen_driver (struct outp_driver *this)
62 {
63   struct html_driver_ext *x;
64
65   assert (this->driver_open == 0);
66   msg (VM (1), _("HTML driver initializing as `%s'..."), this->name);
67
68   this->ext = x = xmalloc (sizeof *x);
69   this->res = 0;
70   this->horiz = this->vert = 0;
71   this->width = this->length = 0;
72
73   this->cp_x = this->cp_y = 0;
74
75   x->prologue_fn = NULL;
76
77   x->file.filename = NULL;
78   x->file.mode = "w";
79   x->file.file = NULL;
80   x->file.sequence_no = &x->sequence_no;
81   x->file.param = this;
82   x->file.postopen = postopen;
83   x->file.preclose = preclose;
84
85   x->sequence_no = 0;
86
87   return 1;
88 }
89
90 static int
91 html_postopen_driver (struct outp_driver *this)
92 {
93   struct html_driver_ext *x = this->ext;
94
95   assert (this->driver_open == 0);
96   if (NULL == x->file.filename)
97     x->file.filename = xstrdup ("pspp.html");
98         
99   if (x->prologue_fn == NULL)
100     x->prologue_fn = xstrdup ("html-prologue");
101
102   msg (VM (2), _("%s: Initialization complete."), this->name);
103   this->driver_open = 1;
104
105   return 1;
106 }
107
108 static int
109 html_close_driver (struct outp_driver *this)
110 {
111   struct html_driver_ext *x = this->ext;
112
113   assert (this->driver_open);
114   msg (VM (2), _("%s: Beginning closing..."), this->name);
115   fn_close_ext (&x->file);
116   free (x->prologue_fn);
117   free (x->file.filename);
118   free (x);
119   msg (VM (3), _("%s: Finished closing."), this->name);
120   this->driver_open = 0;
121   
122   return 1;
123 }
124
125 /* Generic option types. */
126 enum
127 {
128   boolean_arg = -10,
129   string_arg,
130   nonneg_int_arg
131 };
132
133 /* All the options that the HTML driver supports. */
134 static struct outp_option option_tab[] =
135 {
136   /* *INDENT-OFF* */
137   {"output-file",               1,              0},
138   {"prologue-file",             string_arg,     0},
139   {"", 0, 0},
140   /* *INDENT-ON* */
141 };
142 static struct outp_option_info option_info;
143
144 static void
145 html_option (struct outp_driver *this, const char *key, const struct string *val)
146 {
147   struct html_driver_ext *x = this->ext;
148   int cat, subcat;
149
150   cat = outp_match_keyword (key, option_tab, &option_info, &subcat);
151   switch (cat)
152     {
153     case 0:
154       msg (SE, _("Unknown configuration parameter `%s' for HTML device "
155            "driver."), key);
156       break;
157     case 1:
158       free (x->file.filename);
159       x->file.filename = xstrdup (ds_value (val));
160       break;
161     case string_arg:
162       {
163         char **dest;
164         switch (subcat)
165           {
166           case 0:
167             dest = &x->prologue_fn;
168             break;
169           default:
170             assert (0);
171           }
172         if (*dest)
173           free (*dest);
174         *dest = xstrdup (ds_value (val));
175       }
176       break;
177     default:
178       assert (0);
179     }
180 }
181
182 /* Variables for the prologue. */
183 struct html_variable
184   {
185     const char *key;
186     const char *value;
187   };
188   
189 static struct html_variable *html_var_tab;
190
191 /* Searches html_var_tab for a html_variable with key KEY, and returns
192    the associated value. */
193 static const char *
194 html_get_var (const char *key)
195 {
196   struct html_variable *v;
197
198   for (v = html_var_tab; v->key; v++)
199     if (!strcmp (key, v->key))
200       return v->value;
201   return NULL;
202 }
203
204 /* Writes the HTML prologue to file F. */
205 static int
206 postopen (struct file_ext *f)
207 {
208   static struct html_variable dict[] =
209     {
210       {"generator", 0},
211       {"date", 0},
212       {"user", 0},
213       {"host", 0},
214       {"title", 0},
215       {"subtitle", 0},
216       {"source-file", 0},
217       {0, 0},
218     };
219 #if HAVE_UNISTD_H
220   char host[128];
221 #endif
222   time_t curtime;
223   struct tm *loctime;
224
225   struct outp_driver *this = f->param;
226   struct html_driver_ext *x = this->ext;
227
228   char *prologue_fn = fn_search_path (x->prologue_fn, config_path, NULL);
229   FILE *prologue_file;
230
231   char *buf = NULL;
232   int buf_size = 0;
233
234   if (prologue_fn == NULL)
235     {
236       msg (IE, _("Cannot find HTML prologue.  The use of `-vv' "
237                  "on the command line is suggested as a debugging aid."));
238       return 0;
239     }
240
241   msg (VM (1), _("%s: %s: Opening HTML prologue..."), this->name, prologue_fn);
242   prologue_file = fopen (prologue_fn, "rb");
243   if (prologue_file == NULL)
244     {
245       fclose (prologue_file);
246       free (prologue_fn);
247       msg (IE, "%s: %s", prologue_fn, strerror (errno));
248       goto error;
249     }
250
251   dict[0].value = version;
252
253   curtime = time (NULL);
254   loctime = localtime (&curtime);
255   dict[1].value = asctime (loctime);
256   {
257     char *cp = strchr (dict[1].value, '\n');
258     if (cp)
259       *cp = 0;
260   }
261
262   /* PORTME: Determine username, net address. */
263 #if HAVE_UNISTD_H
264   dict[2].value = getenv ("LOGNAME");
265   if (!dict[2].value)
266     dict[2].value = getlogin ();
267   if (!dict[2].value)
268     dict[2].value = _("nobody");
269
270   if (gethostname (host, 128) == -1)
271     {
272       if (errno == ENAMETOOLONG)
273         host[127] = 0;
274       else
275         strcpy (host, _("nowhere"));
276     }
277   dict[3].value = host;
278 #else /* !HAVE_UNISTD_H */
279   dict[2].value = _("nobody");
280   dict[3].value = _("nowhere");
281 #endif /* !HAVE_UNISTD_H */
282
283   dict[4].value = outp_title ? outp_title : "";
284   dict[5].value = outp_subtitle ? outp_subtitle : "";
285
286   getl_location (&dict[6].value, NULL);
287   if (dict[6].value == NULL)
288     dict[6].value = "<stdin>";
289
290   html_var_tab = dict;
291   while (-1 != getline (&buf, &buf_size, prologue_file))
292     {
293       char *buf2;
294       int len;
295
296       if (strstr (buf, "!!!"))
297         continue;
298       
299       {
300         char *cp = strstr (buf, "!title");
301         if (cp)
302           {
303             if (outp_title == NULL)
304               continue;
305             else
306               *cp = '\0';
307           }
308       }
309       
310       {
311         char *cp = strstr (buf, "!subtitle");
312         if (cp)
313           {
314             if (outp_subtitle == NULL)
315               continue;
316             else
317               *cp = '\0';
318           }
319       }
320       
321       /* PORTME: Line terminator. */
322       buf2 = fn_interp_vars (buf, html_get_var);
323       len = strlen (buf2);
324       fwrite (buf2, len, 1, f->file);
325       if (buf2[len - 1] != '\n')
326         putc ('\n', f->file);
327       free (buf2);
328     }
329   if (ferror (f->file))
330     msg (IE, _("Reading `%s': %s."), prologue_fn, strerror (errno));
331   fclose (prologue_file);
332
333   free (prologue_fn);
334   free (buf);
335
336   if (ferror (f->file))
337     goto error;
338
339   msg (VM (2), _("%s: HTML prologue read successfully."), this->name);
340   return 1;
341
342 error:
343   msg (VM (1), _("%s: Error reading HTML prologue."), this->name);
344   return 0;
345 }
346
347 /* Writes the HTML epilogue to file F. */
348 static int
349 preclose (struct file_ext *f)
350 {
351   fprintf (f->file,
352            "</BODY>\n"
353            "</HTML>\n"
354            "<!-- end of file -->\n");
355
356   if (ferror (f->file))
357     return 0;
358   return 1;
359 }
360
361 static int
362 html_open_page (struct outp_driver *this)
363 {
364   struct html_driver_ext *x = this->ext;
365
366   assert (this->driver_open && this->page_open == 0);
367   x->sequence_no++;
368   if (!fn_open_ext (&x->file))
369     {
370       if (errno)
371         msg (ME, _("HTML output driver: %s: %s"), x->file.filename,
372              strerror (errno));
373       return 0;
374     }
375
376   if (!ferror (x->file.file))
377     this->page_open = 1;
378   return !ferror (x->file.file);
379 }
380
381 static int
382 html_close_page (struct outp_driver *this)
383 {
384   struct html_driver_ext *x = this->ext;
385
386   assert (this->driver_open && this->page_open);
387   this->page_open = 0;
388   return !ferror (x->file.file);
389 }
390
391 static void output_tab_table (struct outp_driver *, struct tab_table *);
392
393 static void
394 html_submit (struct outp_driver *this, struct som_table *s)
395 {
396   extern struct som_table_class tab_table_class;
397   struct html_driver_ext *x = this->ext;
398   
399   assert (this->driver_open && this->page_open);
400   if (x->sequence_no == 0 && !html_open_page (this))
401     {
402       msg (ME, _("Cannot open first page on HTML device %s."), this->name);
403       return;
404     }
405
406   if (s->class == &tab_table_class)
407     output_tab_table (this, (struct tab_table *) s->ext);
408   else
409     assert (0);
410 }
411
412 /* Write string S of length LEN to file F, escaping characters as
413    necessary for HTML. */
414 static void
415 escape_string (FILE *f, char *s, int len)
416 {
417   char *ep = &s[len];
418   char *bp, *cp;
419
420   for (bp = cp = s; bp < ep; bp = cp)
421     {
422       while (cp < ep && *cp != '&' && *cp != '<' && *cp != '>' && *cp)
423         cp++;
424       if (cp > bp)
425         fwrite (bp, 1, cp - bp, f);
426       if (cp < ep)
427         switch (*cp++)
428           {
429           case '&':
430             fputs ("&amp;", f);
431             break;
432           case '<':
433             fputs ("&lt;", f);
434             break;
435           case '>':
436             fputs ("&gt;", f);
437             break;
438           case 0:
439             break;
440           default:
441             assert (0);
442           }
443     }
444 }
445   
446 /* Write table T to THIS output driver. */
447 static void
448 output_tab_table (struct outp_driver *this, struct tab_table *t)
449 {
450   struct html_driver_ext *x = this->ext;
451   
452   if (t->nr == 1 && t->nc == 1)
453     {
454       fputs ("<P>", x->file.file);
455       if (!ls_empty_p (t->cc))
456         escape_string (x->file.file, ls_value (t->cc), ls_length (t->cc));
457       fputs ("</P>\n", x->file.file);
458       
459       return;
460     }
461
462   fputs ("<TABLE BORDER=1>\n", x->file.file);
463   
464   if (!ls_empty_p (&t->title))
465     {
466       fprintf (x->file.file, "  <TR>\n    <TH COLSPAN=%d>", t->nc);
467       escape_string (x->file.file, ls_value (&t->title),
468                      ls_length (&t->title));
469       fputs ("</TH>\n  </TR>\n", x->file.file);
470     }
471   
472   {
473     int r;
474     unsigned char *ct = t->ct;
475
476     for (r = 0; r < t->nr; r++)
477       {
478         int c;
479         
480         fputs ("  <TR>\n", x->file.file);
481         for (c = 0; c < t->nc; c++, ct++)
482           {
483             struct len_string *cc;
484             int tag;
485             char header[128];
486             char *cp;
487             struct tab_joined_cell *j = NULL;
488
489             cc = t->cc + c + r * t->nc;
490             if (*ct & TAB_JOIN) 
491               {
492                 j = (struct tab_joined_cell *) ls_value (cc);
493                 cc = &j->contents;
494                 if (j->x1 != c || j->y1 != r)
495                   continue; 
496               }
497
498             if (r < t->t || r >= t->nr - t->b
499                 || c < t->l || c >= t->nc - t->r)
500               tag = 'H';
501             else
502               tag = 'D';
503             cp = stpcpy (header, "    <T");
504             *cp++ = tag;
505             
506             switch (*ct & TAB_ALIGN_MASK)
507               {
508               case TAB_RIGHT:
509                 cp = stpcpy (cp, " ALIGN=RIGHT");
510                 break;
511               case TAB_LEFT:
512                 break;
513               case TAB_CENTER:
514                 cp = stpcpy (cp, " ALIGN=CENTER");
515                 break;
516               default:
517                 assert (0);
518               }
519
520             if (*ct & TAB_JOIN)
521               {
522                 if (j->x2 - j->x1 > 1)
523                   cp = spprintf (cp, " COLSPAN=%d", j->x2 - j->x1);
524                 if (j->y2 - j->y1 > 1)
525                   cp = spprintf (cp, " ROWSPAN=%d", j->y2 - j->y1);
526
527                 cc = &j->contents;
528               }
529             
530             strcpy (cp, ">");
531             fputs (header, x->file.file);
532             
533             if ( ! (*ct & TAB_EMPTY)  ) 
534               {
535                 char *s = ls_value (cc);
536                 size_t l = ls_length (cc);
537
538                 while (l && isspace ((unsigned char) *s))
539                   {
540                     l--;
541                     s++;
542                   }
543               
544                 escape_string (x->file.file, s, l);
545               }
546
547             fprintf (x->file.file, "</T%c>\n", tag);
548           }
549         fputs ("  </TR>\n", x->file.file);
550       }
551   }
552               
553   fputs ("</TABLE>\n\n", x->file.file);
554 }
555
556 /* HTML driver class. */
557 struct outp_class html_class =
558 {
559   "html",
560   0xfaeb,
561   1,
562
563   html_open_global,
564   html_close_global,
565   NULL,
566
567   html_preopen_driver,
568   html_option,
569   html_postopen_driver,
570   html_close_driver,
571
572   html_open_page,
573   html_close_page,
574
575   html_submit,
576
577   NULL,
578   NULL,
579   NULL,
580
581   NULL,
582   NULL,
583   NULL,
584   NULL,
585
586   NULL,
587   NULL,
588   NULL,
589   NULL,
590   NULL,
591   NULL,
592   NULL,
593   NULL,
594   NULL,
595 };
596
597 #endif /* !NO_HTML */
598