037905ea08355c164ccfa634e35a39dc456b2948
[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 "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 "getline.h"
39 #include "output.h"
40 #include "som.h"
41 #include "tab.h"
42 #include "version.h"
43 #include "mkfile.h"
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       {"source-file", 0},
235       {0, 0},
236     };
237 #if HAVE_UNISTD_H
238   char host[128];
239 #endif
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   int 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   /* PORTME: Determine username, net address. */
281 #if HAVE_UNISTD_H
282   dict[2].value = getenv ("LOGNAME");
283   if (!dict[2].value)
284     dict[2].value = getlogin ();
285   if (!dict[2].value)
286     dict[2].value = _("nobody");
287
288   if (gethostname (host, 128) == -1)
289     {
290       if (errno == ENAMETOOLONG)
291         host[127] = 0;
292       else
293         strcpy (host, _("nowhere"));
294     }
295   dict[3].value = host;
296 #else /* !HAVE_UNISTD_H */
297   dict[2].value = _("nobody");
298   dict[3].value = _("nowhere");
299 #endif /* !HAVE_UNISTD_H */
300
301   dict[4].value = outp_title ? outp_title : "";
302   dict[5].value = outp_subtitle ? outp_subtitle : "";
303
304   getl_location (&dict[6].value, NULL);
305   if (dict[6].value == NULL)
306     dict[6].value = "<stdin>";
307
308   html_var_tab = dict;
309   while (-1 != getline (&buf, &buf_size, prologue_file))
310     {
311       char *buf2;
312       int len;
313
314       if (strstr (buf, "!!!"))
315         continue;
316       
317       {
318         char *cp = strstr (buf, "!title");
319         if (cp)
320           {
321             if (outp_title == NULL)
322               continue;
323             else
324               *cp = '\0';
325           }
326       }
327       
328       {
329         char *cp = strstr (buf, "!subtitle");
330         if (cp)
331           {
332             if (outp_subtitle == NULL)
333               continue;
334             else
335               *cp = '\0';
336           }
337       }
338       
339       /* PORTME: Line terminator. */
340       buf2 = fn_interp_vars (buf, html_get_var);
341       len = strlen (buf2);
342       fwrite (buf2, len, 1, f->file);
343       if (buf2[len - 1] != '\n')
344         putc ('\n', f->file);
345       free (buf2);
346     }
347   if (ferror (f->file))
348     msg (IE, _("Reading `%s': %s."), prologue_fn, strerror (errno));
349   fclose (prologue_file);
350
351   free (prologue_fn);
352   free (buf);
353
354   if (ferror (f->file))
355     goto error;
356
357   msg (VM (2), _("%s: HTML prologue read successfully."), this->name);
358   return 1;
359
360 error:
361   msg (VM (1), _("%s: Error reading HTML prologue."), this->name);
362   return 0;
363 }
364
365 /* Writes the HTML epilogue to file F. */
366 static int
367 preclose (struct file_ext *f)
368 {
369   fprintf (f->file,
370            "</BODY>\n"
371            "</HTML>\n"
372            "<!-- end of file -->\n");
373
374   if (ferror (f->file))
375     return 0;
376   return 1;
377 }
378
379 static int
380 html_open_page (struct outp_driver *this)
381 {
382   struct html_driver_ext *x = this->ext;
383
384   assert (this->driver_open && this->page_open == 0);
385   x->sequence_no++;
386   if (!fn_open_ext (&x->file))
387     {
388       if (errno)
389         msg (ME, _("HTML output driver: %s: %s"), x->file.filename,
390              strerror (errno));
391       return 0;
392     }
393
394   if (!ferror (x->file.file))
395     this->page_open = 1;
396   return !ferror (x->file.file);
397 }
398
399 static int
400 html_close_page (struct outp_driver *this)
401 {
402   struct html_driver_ext *x = this->ext;
403
404   assert (this->driver_open && this->page_open);
405   this->page_open = 0;
406   return !ferror (x->file.file);
407 }
408
409 static void output_tab_table (struct outp_driver *, struct tab_table *);
410
411 static void
412 html_submit (struct outp_driver *this, struct som_entity *s)
413 {
414   extern struct som_table_class tab_table_class;
415   struct html_driver_ext *x = this->ext;
416   
417   assert (this->driver_open && this->page_open);
418   if (x->sequence_no == 0 && !html_open_page (this))
419     {
420       msg (ME, _("Cannot open first page on HTML device %s."), this->name);
421       return;
422     }
423
424   assert ( s->class == &tab_table_class ) ;
425
426   switch (s->type) 
427     {
428     case SOM_TABLE:
429       output_tab_table ( this, (struct tab_table *) s->ext);
430       break;
431     case SOM_CHART:
432       link_image( &x->file, ((struct chart *)s->ext)->filename);
433       break;
434     default:
435       assert(0);
436       break;
437     }
438
439 }
440
441 /* Write string S of length LEN to file F, escaping characters as
442    necessary for HTML. */
443 static void
444 escape_string (FILE *f, char *s, int len)
445 {
446   char *ep = &s[len];
447   char *bp, *cp;
448
449   for (bp = cp = s; bp < ep; bp = cp)
450     {
451       while (cp < ep && *cp != '&' && *cp != '<' && *cp != '>' && *cp)
452         cp++;
453       if (cp > bp)
454         fwrite (bp, 1, cp - bp, f);
455       if (cp < ep)
456         switch (*cp++)
457           {
458           case '&':
459             fputs ("&amp;", f);
460             break;
461           case '<':
462             fputs ("&lt;", f);
463             break;
464           case '>':
465             fputs ("&gt;", f);
466             break;
467           case 0:
468             break;
469           default:
470             assert (0);
471           }
472     }
473 }
474   
475 /* Write table T to THIS output driver. */
476 static void
477 output_tab_table (struct outp_driver *this, struct tab_table *t)
478 {
479   struct html_driver_ext *x = this->ext;
480   
481   if (t->nr == 1 && t->nc == 1)
482     {
483       fputs ("<P>", x->file.file);
484       if (!ls_empty_p (t->cc))
485         escape_string (x->file.file, ls_c_str (t->cc), ls_length (t->cc));
486       fputs ("</P>\n", x->file.file);
487       
488       return;
489     }
490
491   fputs ("<TABLE BORDER=1>\n", x->file.file);
492   
493   if (!ls_empty_p (&t->title))
494     {
495       fprintf (x->file.file, "  <TR>\n    <TH COLSPAN=%d>", t->nc);
496       escape_string (x->file.file, ls_c_str (&t->title),
497                      ls_length (&t->title));
498       fputs ("</TH>\n  </TR>\n", x->file.file);
499     }
500   
501   {
502     int r;
503     unsigned char *ct = t->ct;
504
505     for (r = 0; r < t->nr; r++)
506       {
507         int c;
508         
509         fputs ("  <TR>\n", x->file.file);
510         for (c = 0; c < t->nc; c++, ct++)
511           {
512             struct len_string *cc;
513             int tag;
514             char header[128];
515             char *cp;
516             struct tab_joined_cell *j = NULL;
517
518             cc = t->cc + c + r * t->nc;
519             if (*ct & TAB_JOIN) 
520               {
521                 j = (struct tab_joined_cell *) ls_c_str (cc);
522                 cc = &j->contents;
523                 if (j->x1 != c || j->y1 != r)
524                   continue; 
525               }
526
527             if (r < t->t || r >= t->nr - t->b
528                 || c < t->l || c >= t->nc - t->r)
529               tag = 'H';
530             else
531               tag = 'D';
532             cp = stpcpy (header, "    <T");
533             *cp++ = tag;
534             
535             switch (*ct & TAB_ALIGN_MASK)
536               {
537               case TAB_RIGHT:
538                 cp = stpcpy (cp, " ALIGN=RIGHT");
539                 break;
540               case TAB_LEFT:
541                 break;
542               case TAB_CENTER:
543                 cp = stpcpy (cp, " ALIGN=CENTER");
544                 break;
545               default:
546                 assert (0);
547               }
548
549             if (*ct & TAB_JOIN)
550               {
551                 if (j->x2 - j->x1 > 1)
552                   cp = spprintf (cp, " COLSPAN=%d", j->x2 - j->x1);
553                 if (j->y2 - j->y1 > 1)
554                   cp = spprintf (cp, " ROWSPAN=%d", j->y2 - j->y1);
555
556                 cc = &j->contents;
557               }
558             
559             strcpy (cp, ">");
560             fputs (header, x->file.file);
561             
562             if ( ! (*ct & TAB_EMPTY)  ) 
563               {
564                 char *s = ls_c_str (cc);
565                 size_t l = ls_length (cc);
566
567                 while (l && isspace ((unsigned char) *s))
568                   {
569                     l--;
570                     s++;
571                   }
572               
573                 escape_string (x->file.file, s, l);
574               }
575
576             fprintf (x->file.file, "</T%c>\n", tag);
577           }
578         fputs ("  </TR>\n", x->file.file);
579       }
580   }
581               
582   fputs ("</TABLE>\n\n", x->file.file);
583 }
584
585
586 void html_initialise_chart(struct outp_class *c, struct chart *ch);
587 void html_finalise_chart(struct outp_class *c, struct chart *ch);
588
589
590 void
591 html_initialise_chart(struct outp_class *c UNUSED, struct chart *ch)
592 {
593
594   FILE  *fp;
595
596   make_unique_file_stream(&fp, &ch->filename);
597
598 #ifdef NO_CHARTS
599   ch->lp = 0;
600 #else
601   ch->pl_params = pl_newplparams();
602   ch->lp = pl_newpl_r ("png", 0, fp, stderr, ch->pl_params);
603 #endif
604
605 }
606
607 void 
608 html_finalise_chart(struct outp_class *c UNUSED, struct chart *ch)
609 {
610   free(ch->filename);
611 }
612
613
614
615 /* HTML driver class. */
616 struct outp_class html_class =
617 {
618   "html",
619   0xfaeb,
620   1,
621
622   html_open_global,
623   html_close_global,
624   NULL,
625
626   html_preopen_driver,
627   html_option,
628   html_postopen_driver,
629   html_close_driver,
630
631   html_open_page,
632   html_close_page,
633
634   html_submit,
635
636   NULL,
637   NULL,
638   NULL,
639
640   NULL,
641   NULL,
642   NULL,
643   NULL,
644
645   NULL,
646   NULL,
647   NULL,
648   NULL,
649   NULL,
650   NULL,
651   NULL,
652   NULL,
653   NULL,
654
655   html_initialise_chart,
656   html_finalise_chart
657
658 };
659
660 #endif /* !NO_HTML */
661