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