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