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