checkin of 0.3.0
[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 #if __CHECKER__
178     case 42000:
179       assert (0);
180 #endif
181     default:
182       assert (0);
183     }
184 }
185
186 /* Variables for the prologue. */
187 struct html_variable
188   {
189     const char *key;
190     const char *value;
191   };
192   
193 static struct html_variable *html_var_tab;
194
195 /* Searches html_var_tab for a html_variable with key KEY, and returns
196    the associated value. */
197 static const char *
198 html_get_var (const char *key)
199 {
200   struct html_variable *v;
201
202   for (v = html_var_tab; v->key; v++)
203     if (!strcmp (key, v->key))
204       return v->value;
205   return NULL;
206 }
207
208 /* Writes the HTML prologue to file F. */
209 static int
210 postopen (struct file_ext *f)
211 {
212   static struct html_variable dict[] =
213     {
214       {"generator", 0},
215       {"date", 0},
216       {"user", 0},
217       {"host", 0},
218       {"title", 0},
219       {"subtitle", 0},
220       {"source-file", 0},
221       {0, 0},
222     };
223 #if HAVE_UNISTD_H
224   char host[128];
225 #endif
226   time_t curtime;
227   struct tm *loctime;
228
229   struct outp_driver *this = f->param;
230   struct html_driver_ext *x = this->ext;
231
232   char *prologue_fn = fn_search_path (x->prologue_fn, config_path, NULL);
233   FILE *prologue_file;
234
235   char *buf = NULL;
236   int buf_size = 0;
237
238   if (prologue_fn == NULL)
239     {
240       msg (IE, _("Cannot find HTML prologue.  The use of `-vv' "
241                  "on the command line is suggested as a debugging aid."));
242       return 0;
243     }
244
245   msg (VM (1), _("%s: %s: Opening HTML prologue..."), this->name, prologue_fn);
246   prologue_file = fopen (prologue_fn, "rb");
247   if (prologue_file == NULL)
248     {
249       fclose (prologue_file);
250       free (prologue_fn);
251       msg (IE, "%s: %s", prologue_fn, strerror (errno));
252       goto error;
253     }
254
255   dict[0].value = version;
256
257   curtime = time (NULL);
258   loctime = localtime (&curtime);
259   dict[1].value = asctime (loctime);
260   {
261     char *cp = strchr (dict[1].value, '\n');
262     if (cp)
263       *cp = 0;
264   }
265
266   /* PORTME: Determine username, net address. */
267 #if HAVE_UNISTD_H
268   dict[2].value = getenv ("LOGNAME");
269   if (!dict[2].value)
270     dict[2].value = getlogin ();
271   if (!dict[2].value)
272     dict[2].value = _("nobody");
273
274   if (gethostname (host, 128) == -1)
275     {
276       if (errno == ENAMETOOLONG)
277         host[127] = 0;
278       else
279         strcpy (host, _("nowhere"));
280     }
281   dict[3].value = host;
282 #else /* !HAVE_UNISTD_H */
283   dict[2].value = _("nobody");
284   dict[3].value = _("nowhere");
285 #endif /* !HAVE_UNISTD_H */
286
287   dict[4].value = outp_title ? outp_title : "";
288   dict[5].value = outp_subtitle ? outp_subtitle : "";
289
290   getl_location (&dict[6].value, NULL);
291   if (dict[6].value == NULL)
292     dict[6].value = "<stdin>";
293
294   html_var_tab = dict;
295   while (-1 != getline (&buf, &buf_size, prologue_file))
296     {
297       char *buf2;
298       int len;
299
300       if (strstr (buf, "!!!"))
301         continue;
302       
303       {
304         char *cp = strstr (buf, "!title");
305         if (cp)
306           {
307             if (outp_title == NULL)
308               continue;
309             else
310               *cp = '\0';
311           }
312       }
313       
314       {
315         char *cp = strstr (buf, "!subtitle");
316         if (cp)
317           {
318             if (outp_subtitle == NULL)
319               continue;
320             else
321               *cp = '\0';
322           }
323       }
324       
325       /* PORTME: Line terminator. */
326       buf2 = fn_interp_vars (buf, html_get_var);
327       len = strlen (buf2);
328       fwrite (buf2, len, 1, f->file);
329       if (buf2[len - 1] != '\n')
330         putc ('\n', f->file);
331       free (buf2);
332     }
333   if (ferror (f->file))
334     msg (IE, _("Reading `%s': %s."), prologue_fn, strerror (errno));
335   fclose (prologue_file);
336
337   free (prologue_fn);
338   free (buf);
339
340   if (ferror (f->file))
341     goto error;
342
343   msg (VM (2), _("%s: HTML prologue read successfully."), this->name);
344   return 1;
345
346 error:
347   msg (VM (1), _("%s: Error reading HTML prologue."), this->name);
348   return 0;
349 }
350
351 /* Writes the HTML epilogue to file F. */
352 static int
353 preclose (struct file_ext *f)
354 {
355   fprintf (f->file,
356            "</BODY>\n"
357            "</HTML>\n"
358            "<!-- end of file -->\n");
359
360   if (ferror (f->file))
361     return 0;
362   return 1;
363 }
364
365 int
366 html_open_page (struct outp_driver *this)
367 {
368   struct html_driver_ext *x = this->ext;
369
370   assert (this->driver_open && this->page_open == 0);
371   x->sequence_no++;
372   if (!fn_open_ext (&x->file))
373     {
374       if (errno)
375         msg (ME, _("HTML output driver: %s: %s"), x->file.filename,
376              strerror (errno));
377       return 0;
378     }
379
380   if (!ferror (x->file.file))
381     this->page_open = 1;
382   return !ferror (x->file.file);
383 }
384
385 int
386 html_close_page (struct outp_driver *this)
387 {
388   struct html_driver_ext *x = this->ext;
389
390   assert (this->driver_open && this->page_open);
391   this->page_open = 0;
392   return !ferror (x->file.file);
393 }
394
395 static void output_tab_table (struct outp_driver *, struct tab_table *);
396
397 void
398 html_submit (struct outp_driver *this, struct som_table *s)
399 {
400   extern struct som_table_class tab_table_class;
401   struct html_driver_ext *x = this->ext;
402   
403   assert (this->driver_open && this->page_open);
404   if (x->sequence_no == 0 && !html_open_page (this))
405     {
406       msg (ME, _("Cannot open first page on HTML device %s."), this->name);
407       return;
408     }
409
410   if (s->class == &tab_table_class)
411     output_tab_table (this, (struct tab_table *) s->ext);
412   else
413     assert (0);
414 }
415
416 /* Emit HTML to FILE to change from *OLD_ATTR attributes to NEW_ATTR.
417    Sets *OLD_ATTR to NEW_ATTR when done. */
418 static void
419 change_attributes (FILE *f, int *old_attr, int new_attr)
420 {
421   if (*old_attr == new_attr)
422     return;
423
424   if (*old_attr & OUTP_F_B)
425     fputs ("</B>", f);
426   if (*old_attr & OUTP_F_I)
427     fputs ("</I>", f);
428   if (new_attr & OUTP_F_I)
429     fputs ("<I>", f);
430   if (new_attr & OUTP_F_B)
431     fputs ("<B>", f);
432
433   *old_attr = new_attr;
434 }
435
436 /* Write string S of length LEN to file F, escaping characters as
437    necessary for HTML. */
438 static void
439 escape_string (FILE *f, char *s, int len)
440 {
441   char *ep = &s[len];
442   char *bp, *cp;
443   int attr = 0;
444
445   for (bp = cp = s; bp < ep; bp = cp)
446     {
447       while (cp < ep && *cp != '&' && *cp != '<' && *cp != '>' && *cp)
448         cp++;
449       if (cp > bp)
450         fwrite (bp, 1, cp - bp, f);
451       if (cp < ep)
452         switch (*cp++)
453           {
454           case '&':
455             fputs ("&amp;", f);
456             break;
457           case '<':
458             fputs ("&lt;", f);
459             break;
460           case '>':
461             fputs ("&gt;", f);
462             break;
463           case 0:
464             break;
465           default:
466             assert (0);
467           }
468     }
469
470   if (attr)
471     change_attributes (f, &attr, 0);
472 }
473   
474 /* Write table T to THIS output driver. */
475 static void
476 output_tab_table (struct outp_driver *this, struct tab_table *t)
477 {
478   struct html_driver_ext *x = this->ext;
479   
480   tab_hit++;
481
482   if (t->nr == 1 && t->nc == 1)
483     {
484       fputs ("<P>", x->file.file);
485       if (!ls_empty_p (t->cc))
486         escape_string (x->file.file, ls_value (t->cc), ls_length (t->cc));
487       fputs ("</P>\n", x->file.file);
488       
489       return;
490     }
491
492   fputs ("<TABLE BORDER=1>\n", x->file.file);
493   
494   if (!ls_empty_p (&t->title))
495     {
496       fprintf (x->file.file, "  <TR>\n    <TH COLSPAN=%d>", t->nc);
497       escape_string (x->file.file, ls_value (&t->title),
498                      ls_length (&t->title));
499       fputs ("</TH>\n  </TR>\n", x->file.file);
500     }
501   
502   {
503     int r;
504     struct len_string *cc = t->cc;
505     unsigned char *ct = t->ct;
506
507     for (r = 0; r < t->nr; r++)
508       {
509         int c;
510         
511         fputs ("  <TR>\n", x->file.file);
512         for (c = 0; c < t->nc; c++, cc++, ct++)
513           {
514             int tag;
515             char header[128];
516             char *cp;
517
518             if ((*ct & TAB_JOIN)
519                 && ((struct tab_joined_cell *) ls_value (cc))->hit == tab_hit)
520               continue;
521
522             if (r < t->t || r >= t->nr - t->b
523                 || c < t->l || c >= t->nc - t->r)
524               tag = 'H';
525             else
526               tag = 'D';
527             cp = stpcpy (header, "    <T");
528             *cp++ = tag;
529             
530             switch (*ct & TAB_ALIGN_MASK)
531               {
532               case TAB_RIGHT:
533                 cp = stpcpy (cp, " ALIGN=RIGHT");
534                 break;
535               case TAB_LEFT:
536                 break;
537               case TAB_CENTER:
538                 cp = stpcpy (cp, " ALIGN=CENTER");
539                 break;
540               default:
541                 assert (0);
542               }
543
544             if (*ct & TAB_JOIN)
545               {
546                 struct tab_joined_cell *j =
547                   (struct tab_joined_cell *) ls_value (cc);
548                 j->hit = tab_hit;
549                 
550                 if (j->x2 - j->x1 > 1)
551                   cp = spprintf (cp, " COLSPAN=%d", j->x2 - j->x1);
552                 if (j->y2 - j->y1 > 1)
553                   cp = spprintf (cp, " ROWSPAN=%d", j->y2 - j->y1);
554               }
555             
556             strcpy (cp, ">");
557             fputs (header, x->file.file);
558             
559             {
560               char *s = ls_value (cc);
561               size_t l = ls_length (cc);
562
563               while (l && isspace ((unsigned char) *s))
564                 {
565                   l--;
566                   s++;
567                 }
568               
569               escape_string (x->file.file, s, l);
570             }
571
572             fprintf (x->file.file, "</T%c>\n", tag);
573           }
574         fputs ("  </TR>\n", x->file.file);
575       }
576   }
577               
578   fputs ("</TABLE>\n\n", x->file.file);
579 }
580
581 /* HTML driver class. */
582 struct outp_class html_class =
583 {
584   "html",
585   0xfaeb,
586   1,
587
588   html_open_global,
589   html_close_global,
590   NULL,
591
592   html_preopen_driver,
593   html_option,
594   html_postopen_driver,
595   html_close_driver,
596
597   html_open_page,
598   html_close_page,
599
600   html_submit,
601
602   NULL,
603   NULL,
604   NULL,
605
606   NULL,
607   NULL,
608   NULL,
609   NULL,
610
611   NULL,
612   NULL,
613   NULL,
614   NULL,
615   NULL,
616   NULL,
617   NULL,
618   NULL,
619   NULL,
620 };
621
622 #endif /* !NO_HTML */
623