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