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