Adopt use of gnulib for portability.
[pspp-builds.git] / src / devind.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 /* Device-independent output format.  Eventually I intend for all
21    PSPP output to work this way, but adding it as an available
22    format is a first step.
23
24    Each line in the output is a command.  The first character on
25    the line is the command name, and the rest of the line is the
26    command arguments.  The commands are described below as Perl
27    regular expressions:
28
29    #.*          comment
30
31    s            starts a new table
32    S[rc]\d+     table size in rows or columns (optional)
33    H[lrtb]\d+   number of left/right/top/bottom header rows/columns
34    B(\d+)-(\d+)/(\d+)
35                 allow column breaks every \3 rows from \1 to \2 exclusive
36    T.*          table title
37    C.*          table caption (not yet supported)
38    t(\d+)(-\d+)?,(\d+)(-\d+)?[wn][hb][lcr][tmb]:.*
39                 text for cells in rows (\1-\2) inclusive and
40                 columns (\3-\4) inclusive,
41                 wrappable/nonwrappable, header/body,
42                 left/center/right justified, top/middle/bottom
43                 justified
44    l[hv][sdtn](\d+),(\d+)-(\d+)
45                 horiz/vert line in single/double/thick/none
46                 style, running across columns/rows \2 to \3
47                 inclusive at offset \1 from top/left side of
48                 table
49    b[sdtno]{4}(\d+)-(\d+),(\d+)-(\d+)
50                 box across columns \1 to \2 inclusive and rows \3
51                 to \4 inclusive with
52                 single/double/thick/none/omit style for horiz &
53                 vert frame and horiz & vert interior lines
54    f(\d+),(\d+):.*
55                 add footnote for cell \1, \2
56    e            end table
57
58    v(\d(.\d+)+) insert \1 lines of blank space
59
60    p:.*         plain text
61    m[ewmlu]:(.*),(\d+),((\d+)(-\d+)?)?:(.*)
62                 error/warning/message/listing/user class message
63                 for file \1, line \2, columns \4 to \5, actual
64                 message \6
65
66    q            end of file
67
68    Text tokens are free-form, except that they are terminated by
69    commas and new-lines.  The following escapes are allowed:
70
71    \\n          line break
72    \\c          comma
73    \\s          non-breaking space
74    \\[0-7]{3}   octal escape
75    \\B          toggle subscript
76    \\P          toggle superscript
77    \\e          toggle emphasis
78    \\E          toggle strong emphasis
79    \\v          toggle variable name font
80    \\F          toggle file name font
81    \\p          toggle fixed-pitch text font (default: proportional)
82    \\n\((\d+)?(\.\d+)?(-?\d+(\.\d+)?+(e-?\d+))?\)
83                 number \3 (sysmis if not provided) in \1.\2 format
84    \\f\(([A-Z]*(\d+)?(\.\d+)?)(-?\d+(\.\d+)?+(e-?\d+))?\)
85                 number \1 in \4 format
86
87 */
88
89 #include <config.h>
90 #include "devind.h"
91 #include "error.h"
92 #include <errno.h>
93 #include <stdlib.h>
94 #include <ctype.h>
95 #include <time.h>
96
97 #if HAVE_UNISTD_H
98 #include <unistd.h>
99 #endif
100
101 #include "alloc.h"
102 #include "error.h"
103 #include "filename.h"
104 #include "getl.h"
105 #include "output.h"
106 #include "som.h"
107 #include "tab.h"
108 #include "version.h"
109
110 #include "gettext.h"
111 #define _(msgid) gettext (msgid)
112
113 /* Device-independent output driver extension record. */
114 struct devind_driver_ext
115   {
116     /* Internal state. */
117     struct file_ext file;       /* Output file. */
118     int sequence_no;            /* Sequence number. */
119   };
120
121 static int
122 devind_open_global (struct outp_class *this UNUSED)
123 {
124   return 1;
125 }
126
127 static int
128 devind_close_global (struct outp_class *this UNUSED)
129 {
130   return 1;
131 }
132
133 static int
134 devind_preopen_driver (struct outp_driver *this)
135 {
136   struct devind_driver_ext *x;
137
138   assert (this->driver_open == 0);
139   msg (VM (1), _("DEVIND driver initializing as `%s'..."), this->name);
140
141   this->ext = x = xmalloc (sizeof *x);
142   this->res = 0;
143   this->horiz = this->vert = 0;
144   this->width = this->length = 0;
145
146   this->cp_x = this->cp_y = 0;
147
148   x->file.filename = NULL;
149   x->file.mode = "w";
150   x->file.file = NULL;
151   x->file.sequence_no = &x->sequence_no;
152   x->file.param = this;
153   x->file.postopen = NULL;
154   x->file.preclose = NULL;
155
156   x->sequence_no = 0;
157
158   return 1;
159 }
160
161 static int
162 devind_postopen_driver (struct outp_driver *this)
163 {
164   struct devind_driver_ext *x = this->ext;
165
166   assert (this->driver_open == 0);
167   if (NULL == x->file.filename)
168     x->file.filename = xstrdup ("pspp.devind");
169         
170   msg (VM (2), _("%s: Initialization complete."), this->name);
171   this->driver_open = 1;
172
173   return 1;
174 }
175
176 static int
177 devind_close_driver (struct outp_driver *this)
178 {
179   struct devind_driver_ext *x = this->ext;
180
181   assert (this->driver_open);
182   msg (VM (2), _("%s: Beginning closing..."), this->name);
183   fputs ("q\n", x->file.file);
184   fn_close_ext (&x->file);
185   free (x->file.filename);
186   free (x);
187   msg (VM (3), _("%s: Finished closing."), this->name);
188   this->driver_open = 0;
189   
190   return 1;
191 }
192
193 /* Generic option types. */
194 enum
195 {
196   boolean_arg = -10,
197   string_arg,
198   nonneg_int_arg
199 };
200
201 /* All the options that the DEVIND driver supports. */
202 static struct outp_option option_tab[] =
203 {
204   /* *INDENT-OFF* */
205   {"output-file",               1,              0},
206   {"", 0, 0},
207   /* *INDENT-ON* */
208 };
209 static struct outp_option_info option_info;
210
211 static void
212 devind_option (struct outp_driver *this, const char *key, const struct string *val)
213 {
214   struct devind_driver_ext *x = this->ext;
215   int cat, subcat;
216
217   cat = outp_match_keyword (key, option_tab, &option_info, &subcat);
218   switch (cat)
219     {
220     case 0:
221       msg (SE, _("Unknown configuration parameter `%s' for DEVIND device "
222            "driver."), key);
223       break;
224     case 1:
225       free (x->file.filename);
226       x->file.filename = xstrdup (ds_c_str (val));
227       break;
228     default:
229       assert (0);
230     }
231 }
232
233 static int
234 devind_open_page (struct outp_driver *this)
235 {
236   struct devind_driver_ext *x = this->ext;
237
238   assert (this->driver_open && this->page_open == 0);
239   x->sequence_no++;
240   if (!fn_open_ext (&x->file))
241     {
242       if (errno)
243         msg (ME, _("DEVIND output driver: %s: %s"), x->file.filename,
244              strerror (errno));
245       return 0;
246     }
247
248   if (!ferror (x->file.file))
249     this->page_open = 1;
250   return !ferror (x->file.file);
251 }
252
253 static int
254 devind_close_page (struct outp_driver *this)
255 {
256   struct devind_driver_ext *x = this->ext;
257
258   assert (this->driver_open && this->page_open);
259   this->page_open = 0;
260   return !ferror (x->file.file);
261 }
262
263 static void output_tab_table (struct outp_driver *, struct tab_table *);
264
265 static void
266 devind_submit (struct outp_driver *this, struct som_entity *s)
267 {
268   extern struct som_table_class tab_table_class;
269   struct devind_driver_ext *x = this->ext;
270   
271   assert (this->driver_open && this->page_open);
272   if (x->sequence_no == 0 && !devind_open_page (this))
273     {
274       msg (ME, _("Cannot open first page on DEVIND device %s."), this->name);
275       return;
276     }
277
278   assert (s->class == &tab_table_class);
279
280   if ( s->type == SOM_TABLE ) 
281     output_tab_table (this, s->ext);
282 }
283
284 /* Write string S of length LEN to file F, escaping characters as
285    necessary for DEVIND. */
286 static void
287 escape_string (FILE *f, char *s, int len)
288 {
289   char *ep = &s[len];
290   char *bp, *cp;
291
292   putc (':', f);
293
294   for (bp = cp = s; bp < ep; bp = cp)
295     {
296       while (cp < ep && *cp != ',' && *cp != '\n' && *cp)
297         cp++;
298       if (cp > bp)
299         fwrite (bp, 1, cp - bp, f);
300       if (cp < ep)
301         switch (*cp++)
302           {
303           case ',':
304             fputs ("\\c", f);
305             break;
306           case '\n':
307             fputs ("\\n", f);
308             break;
309           case 0:
310             break;
311           default:
312             assert (0);
313           }
314     }
315 }
316   
317 /* Write table T to THIS output driver. */
318 static void
319 output_tab_table (struct outp_driver *this, struct tab_table *t)
320 {
321   struct devind_driver_ext *x = this->ext;
322   
323   if (t->nr == 1 && t->nc == 1)
324     {
325       fputs ("p:", x->file.file);
326       escape_string (x->file.file, ls_c_str (t->cc), ls_length (t->cc));
327       putc ('\n', x->file.file);
328       
329       return;
330     }
331
332   /* Start table. */
333   fprintf (x->file.file, "s\n");
334
335   /* Table size. */
336   fprintf (x->file.file, "Sr%d\n", t->nr);
337   fprintf (x->file.file, "Sc%d\n", t->nc);
338
339   /* Table headers. */
340   if (t->l != 0)
341     fprintf (x->file.file, "Hl%d\n", t->l);
342   if (t->r != 0)
343     fprintf (x->file.file, "Hr%d\n", t->r);
344   if (t->t != 0)
345     fprintf (x->file.file, "Ht%d\n", t->t);
346   if (t->b != 0)
347     fprintf (x->file.file, "Hb%d\n", t->b);
348
349   /* Title. */
350   if (!ls_empty_p (&t->title))
351     {
352       putc ('T', x->file.file);
353       escape_string (x->file.file, ls_c_str (&t->title),
354                      ls_length (&t->title));
355       putc ('\n', x->file.file);
356     }
357
358   /* Column breaks. */
359   if (t->col_style == TAB_COL_DOWN) 
360     fprintf (x->file.file, "B%d-%d/%d\n", t->t, t->nr - t->b, t->col_group);
361
362   /* Table text. */
363   {
364     int r;
365     unsigned char *ct = t->ct;
366
367     for (r = 0; r < t->nr; r++)
368       {
369         int c;
370         
371         for (c = 0; c < t->nc; c++, ct++)
372           {
373             struct fixed_string *cc;
374             struct tab_joined_cell *j;
375
376             if (*ct == TAB_EMPTY)
377               continue;
378             
379             cc = t->cc + c + r * t->nc;
380             if (*ct & TAB_JOIN) 
381               {
382                 j = (struct tab_joined_cell *) ls_c_str (cc);
383                 cc = &j->contents;
384                 if (c != j->x1 || r != j->y1)
385                   continue;
386               }
387             else
388               j = NULL;
389
390             putc ('t', x->file.file);
391             if (j == NULL) 
392               fprintf (x->file.file, "%d,%d", r, c);
393             else
394               fprintf (x->file.file, "%d-%d,%d-%d",
395                        j->y1, j->y2, j->x1, j->x2);
396             putc ((*ct & TAT_NOWRAP) ? 'n' : 'w', x->file.file);
397             putc ((*ct & TAT_TITLE) ? 'h' : 'b', x->file.file);
398             if ((*ct & TAB_ALIGN_MASK) == TAB_RIGHT)
399               putc ('r', x->file.file);
400             else if ((*ct & TAB_ALIGN_MASK) == TAB_LEFT)
401               putc ('l', x->file.file);
402             else
403               putc ('c', x->file.file);
404             putc ('t', x->file.file);
405             escape_string (x->file.file, ls_c_str (cc), ls_length (cc));
406             putc ('\n', x->file.file);
407           }
408       }
409   }
410
411   /* Horizontal lines. */
412   {
413     int r, c;
414
415     for (r = 0; r <= t->nr; r++)
416       for (c = 0; c < t->nc; c++) 
417         {
418           int rule = t->rh[c + r * t->nc];
419           if (rule != 0)
420             fprintf (x->file.file, "lh%c%d,%d-%d\n", "nsdt"[rule], r, c, c);
421         }
422   }
423
424   /* Vertical lines. */
425   {
426     int r, c;
427
428     for (r = 0; r < t->nr; r++)
429       for (c = 0; c <= t->nc; c++) 
430         {
431           int rule = t->rv[c + r * (t->nc + 1)];
432           if (rule != 0)
433             fprintf (x->file.file, "lv%c%d,%d-%d\n", "nsdt"[rule], c, r, r);
434         }
435   }
436
437   /* End of table. */
438   fputs ("e\n", x->file.file);
439 }
440
441 /* DEVIND driver class. */
442 struct outp_class devind_class =
443 {
444   "devind",
445   0xb1e7,
446   1,
447
448   devind_open_global,
449   devind_close_global,
450   NULL,
451
452   devind_preopen_driver,
453   devind_option,
454   devind_postopen_driver,
455   devind_close_driver,
456
457   devind_open_page,
458   devind_close_page,
459
460   devind_submit,
461
462   NULL,
463   NULL,
464   NULL,
465
466   NULL,
467   NULL,
468   NULL,
469   NULL,
470
471   NULL,
472   NULL,
473   NULL,
474   NULL,
475   NULL,
476   NULL,
477   NULL,
478   NULL,
479   NULL,
480
481   NULL,
482   NULL,
483 };