Revise.
[pspp] / 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., 51 Franklin Street, Fifth Floor, Boston, MA
18    02110-1301, 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 "getl.h"
39 #include "getline.h"
40 #include "getlogin_r.h"
41 #include "output.h"
42 #include "som.h"
43 #include "tab.h"
44 #include "version.h"
45 #include "mkfile.h"
46
47 #include "gettext.h"
48 #define _(msgid) gettext (msgid)
49
50 /* Prototypes. */
51 static int postopen (struct file_ext *);
52 static int preclose (struct file_ext *);
53
54 static int
55 html_open_global (struct outp_class *this UNUSED)
56 {
57   return 1;
58 }
59
60 static int
61 html_close_global (struct outp_class *this UNUSED)
62 {
63   return 1;
64 }
65
66 static int
67 html_preopen_driver (struct outp_driver *this)
68 {
69   struct html_driver_ext *x;
70
71   assert (this->driver_open == 0);
72   msg (VM (1), _("HTML driver initializing as `%s'..."), this->name);
73
74   this->ext = x = xmalloc (sizeof *x);
75   this->res = 0;
76   this->horiz = this->vert = 0;
77   this->width = this->length = 0;
78
79   this->cp_x = this->cp_y = 0;
80
81   x->prologue_fn = NULL;
82
83   x->file.filename = NULL;
84   x->file.mode = "w";
85   x->file.file = NULL;
86   x->file.sequence_no = &x->sequence_no;
87   x->file.param = this;
88   x->file.postopen = postopen;
89   x->file.preclose = preclose;
90
91   x->sequence_no = 0;
92
93   return 1;
94 }
95
96 static int
97 html_postopen_driver (struct outp_driver *this)
98 {
99   struct html_driver_ext *x = this->ext;
100
101   assert (this->driver_open == 0);
102   if (NULL == x->file.filename)
103     x->file.filename = xstrdup ("pspp.html");
104         
105   if (x->prologue_fn == NULL)
106     x->prologue_fn = xstrdup ("html-prologue");
107
108   msg (VM (2), _("%s: Initialization complete."), this->name);
109   this->driver_open = 1;
110
111   return 1;
112 }
113
114 static int
115 html_close_driver (struct outp_driver *this)
116 {
117   struct html_driver_ext *x = this->ext;
118
119   assert (this->driver_open);
120   msg (VM (2), _("%s: Beginning closing..."), this->name);
121   fn_close_ext (&x->file);
122   free (x->prologue_fn);
123   free (x->file.filename);
124   free (x);
125   msg (VM (3), _("%s: Finished closing."), this->name);
126   this->driver_open = 0;
127   
128   return 1;
129 }
130
131
132 /* Link the image contained in FILENAME to the 
133    HTML stream in file F. */
134 static int
135 link_image (struct file_ext *f, char *filename)
136 {
137   fprintf (f->file,
138            "<IMG SRC=\"%s\"/>", filename);
139
140   if (ferror (f->file))
141     return 0;
142
143   return 1;
144 }
145
146
147 /* Generic option types. */
148 enum
149 {
150   boolean_arg = -10,
151   string_arg,
152   nonneg_int_arg
153 };
154
155 /* All the options that the HTML driver supports. */
156 static struct outp_option option_tab[] =
157 {
158   /* *INDENT-OFF* */
159   {"output-file",               1,              0},
160   {"prologue-file",             string_arg,     0},
161   {"", 0, 0},
162   /* *INDENT-ON* */
163 };
164 static struct outp_option_info option_info;
165
166 static void
167 html_option (struct outp_driver *this, const char *key, const struct string *val)
168 {
169   struct html_driver_ext *x = this->ext;
170   int cat, subcat;
171
172   cat = outp_match_keyword (key, option_tab, &option_info, &subcat);
173   switch (cat)
174     {
175     case 0:
176       msg (SE, _("Unknown configuration parameter `%s' for HTML device "
177            "driver."), key);
178       break;
179     case 1:
180       free (x->file.filename);
181       x->file.filename = xstrdup (ds_c_str (val));
182       break;
183     case string_arg:
184       {
185         char **dest;
186         switch (subcat)
187           {
188           case 0:
189             dest = &x->prologue_fn;
190             break;
191           default:
192             assert (0);
193             abort ();
194           }
195         if (*dest)
196           free (*dest);
197         *dest = xstrdup (ds_c_str (val));
198       }
199       break;
200     default:
201       assert (0);
202     }
203 }
204
205 /* Variables for the prologue. */
206 struct html_variable
207   {
208     const char *key;
209     const char *value;
210   };
211   
212 static struct html_variable *html_var_tab;
213
214 /* Searches html_var_tab for a html_variable with key KEY, and returns
215    the associated value. */
216 static const char *
217 html_get_var (const char *key)
218 {
219   struct html_variable *v;
220
221   for (v = html_var_tab; v->key; v++)
222     if (!strcmp (key, v->key))
223       return v->value;
224   return NULL;
225 }
226
227 /* Writes the HTML prologue to file F. */
228 static int
229 postopen (struct file_ext *f)
230 {
231   static struct html_variable dict[] =
232     {
233       {"generator", 0},
234       {"date", 0},
235       {"user", 0},
236       {"host", 0},
237       {"title", 0},
238       {"subtitle", 0},
239       {"source-file", 0},
240       {0, 0},
241     };
242   char login[128], host[128];
243   time_t curtime;
244   struct tm *loctime;
245
246   struct outp_driver *this = f->param;
247   struct html_driver_ext *x = this->ext;
248
249   char *prologue_fn = fn_search_path (x->prologue_fn, config_path, NULL);
250   FILE *prologue_file;
251
252   char *buf = NULL;
253   int buf_size = 0;
254
255   if (prologue_fn == NULL)
256     {
257       msg (IE, _("Cannot find HTML prologue.  The use of `-vv' "
258                  "on the command line is suggested as a debugging aid."));
259       return 0;
260     }
261
262   msg (VM (1), _("%s: %s: Opening HTML prologue..."), this->name, prologue_fn);
263   prologue_file = fopen (prologue_fn, "rb");
264   if (prologue_file == NULL)
265     {
266       fclose (prologue_file);
267       free (prologue_fn);
268       msg (IE, "%s: %s", prologue_fn, strerror (errno));
269       goto error;
270     }
271
272   dict[0].value = version;
273
274   curtime = time (NULL);
275   loctime = localtime (&curtime);
276   dict[1].value = asctime (loctime);
277   {
278     char *cp = strchr (dict[1].value, '\n');
279     if (cp)
280       *cp = 0;
281   }
282
283   if (getenv ("LOGNAME") != NULL)
284     str_copy_rpad (login, sizeof login, getenv ("LOGNAME"));
285   else if (getlogin_r (login, sizeof login))
286     strcpy (login, _("nobody"));
287   dict[2].value = login;
288
289 #ifdef HAVE_UNISTD_H
290   if (gethostname (host, 128) == -1)
291     {
292       if (errno == ENAMETOOLONG)
293         host[127] = 0;
294       else
295         strcpy (host, _("nowhere"));
296     }
297 #else
298   strcpy (host, _("nowhere"));
299 #endif
300   dict[3].value = host;
301
302   dict[4].value = outp_title ? outp_title : "";
303   dict[5].value = outp_subtitle ? outp_subtitle : "";
304
305   getl_location (&dict[6].value, NULL);
306   if (dict[6].value == NULL)
307     dict[6].value = "<stdin>";
308
309   html_var_tab = dict;
310   while (-1 != getline (&buf, &buf_size, prologue_file))
311     {
312       char *buf2;
313       int len;
314
315       if (strstr (buf, "!!!"))
316         continue;
317       
318       {
319         char *cp = strstr (buf, "!title");
320         if (cp)
321           {
322             if (outp_title == NULL)
323               continue;
324             else
325               *cp = '\0';
326           }
327       }
328       
329       {
330         char *cp = strstr (buf, "!subtitle");
331         if (cp)
332           {
333             if (outp_subtitle == NULL)
334               continue;
335             else
336               *cp = '\0';
337           }
338       }
339       
340       /* PORTME: Line terminator. */
341       buf2 = fn_interp_vars (buf, html_get_var);
342       len = strlen (buf2);
343       fwrite (buf2, len, 1, f->file);
344       if (buf2[len - 1] != '\n')
345         putc ('\n', f->file);
346       free (buf2);
347     }
348   if (ferror (f->file))
349     msg (IE, _("Reading `%s': %s."), prologue_fn, strerror (errno));
350   fclose (prologue_file);
351
352   free (prologue_fn);
353   free (buf);
354
355   if (ferror (f->file))
356     goto error;
357
358   msg (VM (2), _("%s: HTML prologue read successfully."), this->name);
359   return 1;
360
361 error:
362   msg (VM (1), _("%s: Error reading HTML prologue."), this->name);
363   return 0;
364 }
365
366 /* Writes the HTML epilogue to file F. */
367 static int
368 preclose (struct file_ext *f)
369 {
370   fprintf (f->file,
371            "</BODY>\n"
372            "</HTML>\n"
373            "<!-- end of file -->\n");
374
375   if (ferror (f->file))
376     return 0;
377   return 1;
378 }
379
380 static int
381 html_open_page (struct outp_driver *this)
382 {
383   struct html_driver_ext *x = this->ext;
384
385   assert (this->driver_open && this->page_open == 0);
386   x->sequence_no++;
387   if (!fn_open_ext (&x->file))
388     {
389       if (errno)
390         msg (ME, _("HTML output driver: %s: %s"), x->file.filename,
391              strerror (errno));
392       return 0;
393     }
394
395   if (!ferror (x->file.file))
396     this->page_open = 1;
397   return !ferror (x->file.file);
398 }
399
400 static int
401 html_close_page (struct outp_driver *this)
402 {
403   struct html_driver_ext *x = this->ext;
404
405   assert (this->driver_open && this->page_open);
406   this->page_open = 0;
407   return !ferror (x->file.file);
408 }
409
410 static void output_tab_table (struct outp_driver *, struct tab_table *);
411
412 static void
413 html_submit (struct outp_driver *this, struct som_entity *s)
414 {
415   extern struct som_table_class tab_table_class;
416   struct html_driver_ext *x = this->ext;
417   
418   assert (this->driver_open && this->page_open);
419   if (x->sequence_no == 0 && !html_open_page (this))
420     {
421       msg (ME, _("Cannot open first page on HTML device %s."), this->name);
422       return;
423     }
424
425   assert ( s->class == &tab_table_class ) ;
426
427   switch (s->type) 
428     {
429     case SOM_TABLE:
430       output_tab_table ( this, (struct tab_table *) s->ext);
431       break;
432     case SOM_CHART:
433       link_image( &x->file, ((struct chart *)s->ext)->filename);
434       break;
435     default:
436       assert(0);
437       break;
438     }
439
440 }
441
442 /* Write string S of length LEN to file F, escaping characters as
443    necessary for HTML. */
444 static void
445 escape_string (FILE *f, char *s, int len)
446 {
447   char *ep = &s[len];
448   char *bp, *cp;
449
450   for (bp = cp = s; bp < ep; bp = cp)
451     {
452       while (cp < ep && *cp != '&' && *cp != '<' && *cp != '>' && *cp)
453         cp++;
454       if (cp > bp)
455         fwrite (bp, 1, cp - bp, f);
456       if (cp < ep)
457         switch (*cp++)
458           {
459           case '&':
460             fputs ("&amp;", f);
461             break;
462           case '<':
463             fputs ("&lt;", f);
464             break;
465           case '>':
466             fputs ("&gt;", f);
467             break;
468           case 0:
469             break;
470           default:
471             assert (0);
472           }
473     }
474 }
475   
476 /* Write table T to THIS output driver. */
477 static void
478 output_tab_table (struct outp_driver *this, struct tab_table *t)
479 {
480   struct html_driver_ext *x = this->ext;
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_c_str (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_c_str (&t->title),
498                      ls_length (&t->title));
499       fputs ("</TH>\n  </TR>\n", x->file.file);
500     }
501   
502   {
503     int r;
504     unsigned char *ct = t->ct;
505
506     for (r = 0; r < t->nr; r++)
507       {
508         int c;
509         
510         fputs ("  <TR>\n", x->file.file);
511         for (c = 0; c < t->nc; c++, ct++)
512           {
513             struct fixed_string *cc;
514             int tag;
515             char header[128];
516             char *cp;
517             struct tab_joined_cell *j = NULL;
518
519             cc = t->cc + c + r * t->nc;
520             if (*ct & TAB_JOIN) 
521               {
522                 j = (struct tab_joined_cell *) ls_c_str (cc);
523                 cc = &j->contents;
524                 if (j->x1 != c || j->y1 != r)
525                   continue; 
526               }
527
528             if (r < t->t || r >= t->nr - t->b
529                 || c < t->l || c >= t->nc - t->r)
530               tag = 'H';
531             else
532               tag = 'D';
533             cp = stpcpy (header, "    <T");
534             *cp++ = tag;
535             
536             switch (*ct & TAB_ALIGN_MASK)
537               {
538               case TAB_RIGHT:
539                 cp = stpcpy (cp, " ALIGN=RIGHT");
540                 break;
541               case TAB_LEFT:
542                 break;
543               case TAB_CENTER:
544                 cp = stpcpy (cp, " ALIGN=CENTER");
545                 break;
546               default:
547                 assert (0);
548               }
549
550             if (*ct & TAB_JOIN)
551               {
552                 if (j->x2 - j->x1 > 1)
553                   cp = spprintf (cp, " COLSPAN=%d", j->x2 - j->x1);
554                 if (j->y2 - j->y1 > 1)
555                   cp = spprintf (cp, " ROWSPAN=%d", j->y2 - j->y1);
556
557                 cc = &j->contents;
558               }
559             
560             strcpy (cp, ">");
561             fputs (header, x->file.file);
562             
563             if ( ! (*ct & TAB_EMPTY)  ) 
564               {
565                 char *s = ls_c_str (cc);
566                 size_t l = ls_length (cc);
567
568                 while (l && isspace ((unsigned char) *s))
569                   {
570                     l--;
571                     s++;
572                   }
573               
574                 escape_string (x->file.file, s, l);
575               }
576
577             fprintf (x->file.file, "</T%c>\n", tag);
578           }
579         fputs ("  </TR>\n", x->file.file);
580       }
581   }
582               
583   fputs ("</TABLE>\n\n", x->file.file);
584 }
585
586 static void
587 html_initialise_chart(struct outp_driver *d UNUSED, struct chart *ch)
588 {
589
590   FILE  *fp;
591
592   make_unique_file_stream(&fp, &ch->filename);
593
594 #ifdef NO_CHARTS
595   ch->lp = 0;
596 #else
597   ch->pl_params = pl_newplparams();
598   ch->lp = pl_newpl_r ("png", 0, fp, stderr, ch->pl_params);
599 #endif
600
601 }
602
603 static void 
604 html_finalise_chart(struct outp_driver *d UNUSED, struct chart *ch)
605 {
606   free(ch->filename);
607 }
608
609
610
611 /* HTML driver class. */
612 struct outp_class html_class =
613 {
614   "html",
615   0xfaeb,
616   1,
617
618   html_open_global,
619   html_close_global,
620   NULL,
621
622   html_preopen_driver,
623   html_option,
624   html_postopen_driver,
625   html_close_driver,
626
627   html_open_page,
628   html_close_page,
629
630   html_submit,
631
632   NULL,
633   NULL,
634   NULL,
635
636   NULL,
637   NULL,
638   NULL,
639   NULL,
640
641   NULL,
642   NULL,
643   NULL,
644   NULL,
645   NULL,
646   NULL,
647   NULL,
648   NULL,
649   NULL,
650
651   html_initialise_chart,
652   html_finalise_chart
653
654 };
655
656 #endif /* !NO_HTML */
657