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