42d818b519f908004e0eb876cdfc089ffd53a67b
[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 <assert.h>
25 #include <errno.h>
26 #include <stdlib.h>
27 #include <ctype.h>
28 #include <time.h>
29
30 #if HAVE_UNISTD_H
31 #include <unistd.h>
32 #endif
33
34 #include "alloc.h"
35 #include "error.h"
36 #include "filename.h"
37 #include "getline.h"
38 #include "htmlP.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 int
49 html_open_global (struct outp_class *this unused)
50 {
51   return 1;
52 }
53
54 int
55 html_close_global (struct outp_class *this unused)
56 {
57   return 1;
58 }
59
60 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 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 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 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 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 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 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 /* Emit HTML to FILE to change from *OLD_ATTR attributes to NEW_ATTR.
413    Sets *OLD_ATTR to NEW_ATTR when done. */
414 static void
415 change_attributes (FILE *f, int *old_attr, int new_attr)
416 {
417   if (*old_attr == new_attr)
418     return;
419
420   if (*old_attr & OUTP_F_B)
421     fputs ("</B>", f);
422   if (*old_attr & OUTP_F_I)
423     fputs ("</I>", f);
424   if (new_attr & OUTP_F_I)
425     fputs ("<I>", f);
426   if (new_attr & OUTP_F_B)
427     fputs ("<B>", f);
428
429   *old_attr = new_attr;
430 }
431
432 /* Write string S of length LEN to file F, escaping characters as
433    necessary for HTML. */
434 static void
435 escape_string (FILE *f, char *s, int len)
436 {
437   char *ep = &s[len];
438   char *bp, *cp;
439   int attr = 0;
440
441   for (bp = cp = s; bp < ep; bp = cp)
442     {
443       while (cp < ep && *cp != '&' && *cp != '<' && *cp != '>' && *cp)
444         cp++;
445       if (cp > bp)
446         fwrite (bp, 1, cp - bp, f);
447       if (cp < ep)
448         switch (*cp++)
449           {
450           case '&':
451             fputs ("&amp;", f);
452             break;
453           case '<':
454             fputs ("&lt;", f);
455             break;
456           case '>':
457             fputs ("&gt;", f);
458             break;
459           case 0:
460             break;
461           default:
462             assert (0);
463           }
464     }
465
466   if (attr)
467     change_attributes (f, &attr, 0);
468 }
469   
470 /* Write table T to THIS output driver. */
471 static void
472 output_tab_table (struct outp_driver *this, struct tab_table *t)
473 {
474   struct html_driver_ext *x = this->ext;
475   
476   tab_hit++;
477
478   if (t->nr == 1 && t->nc == 1)
479     {
480       fputs ("<P>", x->file.file);
481       if (!ls_empty_p (t->cc))
482         escape_string (x->file.file, ls_value (t->cc), ls_length (t->cc));
483       fputs ("</P>\n", x->file.file);
484       
485       return;
486     }
487
488   fputs ("<TABLE BORDER=1>\n", x->file.file);
489   
490   if (!ls_empty_p (&t->title))
491     {
492       fprintf (x->file.file, "  <TR>\n    <TH COLSPAN=%d>", t->nc);
493       escape_string (x->file.file, ls_value (&t->title),
494                      ls_length (&t->title));
495       fputs ("</TH>\n  </TR>\n", x->file.file);
496     }
497   
498   {
499     int r;
500     struct len_string *cc = t->cc;
501     unsigned char *ct = t->ct;
502
503     for (r = 0; r < t->nr; r++)
504       {
505         int c;
506         
507         fputs ("  <TR>\n", x->file.file);
508         for (c = 0; c < t->nc; c++, cc++, ct++)
509           {
510             int tag;
511             char header[128];
512             char *cp;
513
514             if ((*ct & TAB_JOIN)
515                 && ((struct tab_joined_cell *) ls_value (cc))->hit == tab_hit)
516               continue;
517
518             if (r < t->t || r >= t->nr - t->b
519                 || c < t->l || c >= t->nc - t->r)
520               tag = 'H';
521             else
522               tag = 'D';
523             cp = stpcpy (header, "    <T");
524             *cp++ = tag;
525             
526             switch (*ct & TAB_ALIGN_MASK)
527               {
528               case TAB_RIGHT:
529                 cp = stpcpy (cp, " ALIGN=RIGHT");
530                 break;
531               case TAB_LEFT:
532                 break;
533               case TAB_CENTER:
534                 cp = stpcpy (cp, " ALIGN=CENTER");
535                 break;
536               default:
537                 assert (0);
538               }
539
540             if (*ct & TAB_JOIN)
541               {
542                 struct tab_joined_cell *j =
543                   (struct tab_joined_cell *) ls_value (cc);
544                 j->hit = tab_hit;
545                 
546                 if (j->x2 - j->x1 > 1)
547                   cp = spprintf (cp, " COLSPAN=%d", j->x2 - j->x1);
548                 if (j->y2 - j->y1 > 1)
549                   cp = spprintf (cp, " ROWSPAN=%d", j->y2 - j->y1);
550               }
551             
552             strcpy (cp, ">");
553             fputs (header, x->file.file);
554             
555             if ( ! (*ct & TAB_EMPTY)  ) 
556               {
557                 char *s = ls_value (cc);
558                 size_t l = ls_length (cc);
559
560                 while (l && isspace ((unsigned char) *s))
561                   {
562                     l--;
563                     s++;
564                   }
565               
566                 escape_string (x->file.file, s, l);
567               }
568
569             fprintf (x->file.file, "</T%c>\n", tag);
570           }
571         fputs ("  </TR>\n", x->file.file);
572       }
573   }
574               
575   fputs ("</TABLE>\n\n", x->file.file);
576 }
577
578 /* HTML driver class. */
579 struct outp_class html_class =
580 {
581   "html",
582   0xfaeb,
583   1,
584
585   html_open_global,
586   html_close_global,
587   NULL,
588
589   html_preopen_driver,
590   html_option,
591   html_postopen_driver,
592   html_close_driver,
593
594   html_open_page,
595   html_close_page,
596
597   html_submit,
598
599   NULL,
600   NULL,
601   NULL,
602
603   NULL,
604   NULL,
605   NULL,
606   NULL,
607
608   NULL,
609   NULL,
610   NULL,
611   NULL,
612   NULL,
613   NULL,
614   NULL,
615   NULL,
616   NULL,
617 };
618
619 #endif /* !NO_HTML */
620