Update build system to Autoconf 2.58, Automake 1.7, gettext 0.12.1.
[pspp-builds.git] / src / pfm-write.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 #include <config.h>
21 #include <assert.h>
22 #include <ctype.h>
23 #include <errno.h>
24 #include <float.h>
25 #include <math.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <time.h>
29 #include "alloc.h"
30 #include "avl.h"
31 #include "error.h"
32 #include "file-handle.h"
33 #include "gmp.h"
34 #include "magic.h"
35 #include "pfm.h"
36 #include "str.h"
37 #include "var.h"
38 #include "version.h"
39
40 #undef DEBUGGING
41 /*#define DEBUGGING 1 */
42 #include "debug-print.h"
43
44 /* pfm writer file_handle extension. */
45 struct pfm_fhuser_ext
46   {
47     FILE *file;                 /* Actual file. */
48
49     int lc;                     /* Number of characters on this line so far. */
50
51     int nvars;                  /* Number of variables. */
52     int *vars;                  /* Variable widths. */
53   };
54
55 static struct fh_ext_class pfm_w_class;
56
57 static int bufwrite (struct file_handle *h, const void *buf, size_t nbytes);
58 static int write_header (struct file_handle *h);
59 static int write_version_data (struct file_handle *h);
60 static int write_variables (struct file_handle *h, struct dictionary *d);
61 static int write_value_labels (struct file_handle *h, struct dictionary *d);
62
63 /* Writes the dictionary DICT to portable file HANDLE.  Returns
64    nonzero only if successful. */
65 int
66 pfm_write_dictionary (struct file_handle *handle, struct dictionary *dict)
67 {
68   struct pfm_fhuser_ext *ext;
69   
70   if (handle->class != NULL)
71     {
72       msg (ME, _("Cannot write file %s as portable file: already opened "
73                  "for %s."),
74            fh_handle_name (handle), handle->class->name);
75       return 0;
76     }
77
78   msg (VM (1), _("%s: Opening portable-file handle %s for writing."),
79        fh_handle_filename (handle), fh_handle_name (handle));
80   
81   /* Open the physical disk file. */
82   handle->class = &pfm_w_class;
83   handle->ext = ext = xmalloc (sizeof (struct pfm_fhuser_ext));
84   ext->file = fopen (handle->norm_fn, "wb");
85   ext->lc = 0;
86   if (ext->file == NULL)
87     {
88       msg (ME, _("An error occurred while opening \"%s\" for writing "
89            "as a portable file: %s."), handle->fn, strerror (errno));
90       err_cond_fail ();
91       free (ext);
92       return 0;
93     }
94   
95   {
96     int i;
97
98     ext->nvars = dict->nvar;
99     ext->vars = xmalloc (sizeof *ext->vars * dict->nvar);
100     for (i = 0; i < dict->nvar; i++)
101       ext->vars[i] = dict->var[i]->width;
102   }
103
104   /* Write the file header. */
105   if (!write_header (handle))
106     goto lossage;
107
108   /* Write version data. */
109   if (!write_version_data (handle))
110     goto lossage;
111
112   /* Write variables. */
113   if (!write_variables (handle, dict))
114     goto lossage;
115
116   /* Write value labels. */
117   if (!write_value_labels (handle, dict))
118     goto lossage;
119
120   /* Write beginning of data marker. */
121   if (!bufwrite (handle, "F", 1))
122     goto lossage;
123
124   msg (VM (2), _("Wrote portable-file header successfully."));
125
126   return 1;
127
128 lossage:
129   msg (VM (1), _("Error writing portable-file header."));
130   fclose (ext->file);
131   free (ext->vars);
132   handle->class = NULL;
133   handle->ext = NULL;
134   return 0;
135 }
136 \f  
137 /* Write NBYTES starting at BUF to the portable file represented by
138    H.  Break lines properly every 80 characters.  */
139 static int
140 bufwrite (struct file_handle *h, const void *buf, size_t nbytes)
141 {
142   struct pfm_fhuser_ext *ext = h->ext;
143
144   assert (buf != NULL);
145   while (nbytes + ext->lc >= 80)
146     {
147       size_t n = 80 - ext->lc;
148       
149       if (n && 1 != fwrite (buf, n, 1, ext->file))
150         goto lossage;
151       
152       /* PORTME: line ends. */
153       if (1 != fwrite ("\r\n", 2, 1, ext->file))
154         goto lossage;
155
156       nbytes -= n;
157       *((char **) &buf) += n;
158       ext->lc = 0;
159     }
160
161   if (nbytes && 1 != fwrite (buf, nbytes, 1, ext->file))
162     goto lossage;
163   ext->lc += nbytes;
164   
165   return 1;
166
167  lossage:
168   abort ();
169   msg (ME, _("%s: Writing portable file: %s."), h->fn, strerror (errno));
170   return 0;
171 }
172
173 /* Write D to the portable file as a floating-point field, and return
174    success. */
175 static int
176 write_float (struct file_handle *h, double d)
177 {
178   int neg = 0;
179   char *mantissa;
180   int mantissa_len;
181   mp_exp_t exponent;
182   char *buf, *cp;
183   int success;
184
185   if (d < 0.)
186     {
187       d = -d;
188       neg = 1;
189     }
190   
191   if (d == fabs (SYSMIS) || d == HUGE_VAL)
192     return bufwrite (h, "*.", 2);
193   
194   /* Use GNU libgmp2 to convert D into base-30. */
195   {
196     mpf_t f;
197     
198     mpf_init_set_d (f, d);
199     mantissa = mpf_get_str (NULL, &exponent, 30, 0, f);
200     mpf_clear (f);
201
202     for (cp = mantissa; *cp; cp++)
203       *cp = toupper (*cp);
204   }
205   
206   /* Choose standard or scientific notation. */
207   mantissa_len = (int) strlen (mantissa);
208   cp = buf = local_alloc (mantissa_len + 32);
209   if (neg)
210     *cp++ = '-';
211   if (mantissa_len == 0)
212     *cp++ = '0';
213   else if (exponent < -4 || exponent > (mp_exp_t) mantissa_len)
214     {
215       /* Scientific notation. */
216       *cp++ = mantissa[0];
217       *cp++ = '.';
218       cp = stpcpy (cp, &mantissa[1]);
219       cp = spprintf (cp, "%+ld", (long) (exponent - 1));
220     }
221   else if (exponent <= 0)
222     {
223       /* Standard notation, D <= 1. */
224       *cp++ = '.';
225       memset (cp, '0', -exponent);
226       cp += -exponent;
227       cp = stpcpy (cp, mantissa);
228     }
229   else 
230     {
231       /* Standard notation, D > 1. */
232       memcpy (cp, mantissa, exponent);
233       cp += exponent;
234       *cp++ = '.';
235       cp = stpcpy (cp, &mantissa[exponent]);
236     }
237   *cp++ = '/';
238   
239   success = bufwrite (h, buf, cp - buf);
240   local_free (buf);
241   free (mantissa);
242   return success;
243 }
244
245 /* Write N to the portable file as an integer field, and return success. */
246 static int
247 write_int (struct file_handle *h, int n)
248 {
249   char buf[64];
250   char *bp = &buf[64];
251   int neg = 0;
252
253   *--bp = '/';
254   
255   if (n < 0)
256     {
257       n = -n;
258       neg = 1;
259     }
260   
261   do
262     {
263       int r = n % 30;
264
265       /* PORTME: character codes. */
266       if (r < 10)
267         *--bp = r + '0';
268       else
269         *--bp = r - 10 + 'A';
270
271       n /= 30;
272     }
273   while (n > 0);
274
275   if (neg)
276     *--bp = '-';
277
278   return bufwrite (h, bp, &buf[64] - bp);
279 }
280
281 /* Write S to the portable file as a string field. */
282 static int
283 write_string (struct file_handle *h, const char *s)
284 {
285   size_t n = strlen (s);
286   return write_int (h, (int) n) && bufwrite (h, s, n);
287 }
288 \f
289 /* Write file header. */
290 static int
291 write_header (struct file_handle *h)
292 {
293   /* PORTME. */
294   {
295     int i;
296
297     for (i = 0; i < 5; i++)
298       if (!bufwrite (h, "ASCII SPSS PORT FILE                    ", 40))
299         return 0;
300   }
301   
302   {
303     /* PORTME: Translation table from SPSS character code to this
304        computer's native character code (which is probably ASCII). */
305     static const unsigned char spss2ascii[256] =
306       {
307         "0000000000000000000000000000000000000000000000000000000000000000"
308         "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ."
309         "<(+|&[]!$*);^-/|,%_>?`:$@'=\"000000~-0000123456789000-()0{}\\00000"
310         "0000000000000000000000000000000000000000000000000000000000000000"
311       };
312
313     if (!bufwrite (h, spss2ascii, 256))
314       return 0;
315   }
316
317   if (!bufwrite (h, "SPSSPORT", 8))
318     return 0;
319
320   return 1;
321 }
322
323 /* Writes version, date, and identification records. */
324 static int
325 write_version_data (struct file_handle *h)
326 {
327   if (!bufwrite (h, "A", 1))
328     return 0;
329   
330   {
331     char date_str[9];
332     char time_str[7];
333     time_t t;
334     struct tm tm;
335     struct tm *tmp;
336
337     if ((time_t) -1 == time (&t))
338       {
339         tm.tm_sec = tm.tm_min = tm.tm_hour = tm.tm_mon = tm.tm_year = 0;
340         tm.tm_mday = 1;
341         tmp = &tm;
342       }
343     else 
344       tmp = localtime (&t);
345     
346     sprintf (date_str, "%04d%02d%02d",
347              tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday);
348     sprintf (time_str, "%02d%02d%02d", tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
349     if (!write_string (h, date_str) || !write_string (h, time_str))
350       return 0;
351   }
352
353   /* Product identification. */
354   if (!bufwrite (h, "1", 1) || !write_string (h, version))
355     return 0;
356
357   /* Subproduct identification. */
358   if (!bufwrite (h, "3", 1) || !write_string (h, host_system))
359     return 0;
360
361   return 1;
362 }
363
364 /* Write format F to file H, and return success. */
365 static int
366 write_format (struct file_handle *h, struct fmt_spec *f)
367 {
368   return (write_int (h, formats[f->type].spss)
369           && write_int (h, f->w)
370           && write_int (h, f->d));
371 }
372
373 /* Write value V for variable VV to file H, and return success. */
374 static int
375 write_value (struct file_handle *h, union value *v, struct variable *vv)
376 {
377   if (vv->type == NUMERIC)
378     return write_float (h, v->f);
379   else
380     return write_int (h, vv->width) && bufwrite (h, v->s, vv->width);
381 }
382
383 /* Write variable records, and return success. */
384 static int
385 write_variables (struct file_handle *h, struct dictionary *dict)
386 {
387   int i;
388   
389   if (!bufwrite (h, "4", 1) || !write_int (h, dict->nvar)
390       || !write_int (h, 161))
391     return 0;
392
393   for (i = 0; i < dict->nvar; i++)
394     {
395       static const char *miss_types[MISSING_COUNT] =
396         {
397           "", "8", "88", "888", "B ", "9", "A", "B 8", "98", "A8",
398         };
399
400       const char *m;
401       int j;
402
403       struct variable *v = dict->var[i];
404       
405       if (!bufwrite (h, "7", 1) || !write_int (h, v->width)
406           || !write_string (h, v->name)
407           || !write_format (h, &v->print) || !write_format (h, &v->write))
408         return 0;
409
410       for (m = miss_types[v->miss_type], j = 0; j < (int) strlen (m); j++)
411         if ((m[j] != ' ' && !bufwrite (h, &m[j], 1))
412             || !write_value (h, &v->missing[j], v))
413           return 0;
414
415       if (v->label && (!bufwrite (h, "C", 1) || !write_string (h, v->label)))
416         return 0;
417     }
418
419   return 1;
420 }
421
422 /* Write value labels to disk.  FIXME: Inefficient. */
423 static int
424 write_value_labels (struct file_handle *h, struct dictionary *dict)
425 {
426   int i;
427
428   for (i = 0; i < dict->nvar; i++)
429     {
430       avl_traverser iter;
431       struct variable *v = dict->var[i];
432       struct value_label *vl;
433
434       if (v->val_lab == NULL)
435         continue;
436
437       if (!bufwrite (h, "D", 1)
438           || !write_int (h, 1)
439           || !write_string (h, v->name)
440           || !write_int (h, avl_count (v->val_lab)))
441         return 0;
442
443       avl_traverser_init (iter);
444       while (NULL != (vl = avl_traverse (v->val_lab, &iter)))
445         if (!write_value (h, &vl->v, v)
446             || !write_string (h, vl->s))
447           return 0;
448     }
449
450   return 1;
451 }
452
453 /* Writes case ELEM to the portable file represented by H.  Returns
454    success. */
455 int 
456 pfm_write_case (struct file_handle *h, const union value *elem)
457 {
458   struct pfm_fhuser_ext *ext = h->ext;
459   
460   int i;
461   
462   for (i = 0; i < ext->nvars; i++)
463     {
464       const int width = ext->vars[i];
465       
466       if (width == 0)
467         {
468           if (!write_float (h, elem[i].f))
469             return 0;
470         }
471       else
472         {
473           if (!write_int (h, width) || !bufwrite (h, elem->c, width))
474             return 0;
475         }
476     }
477
478   return 1;
479 }
480
481 /* Closes a portable file after we're done with it. */
482 static void
483 pfm_close (struct file_handle *h)
484 {
485   struct pfm_fhuser_ext *ext = h->ext;
486   
487   {
488     char buf[80];
489     
490     int n = 80 - ext->lc;
491     if (n == 0)
492       n = 80;
493
494     memset (buf, 'Z', n);
495     bufwrite (h, buf, n);
496   }
497
498   if (EOF == fclose (ext->file))
499     msg (ME, _("%s: Closing portable file: %s."), h->fn, strerror (errno));
500
501   free (ext->vars);
502   free (ext);
503 }
504
505 static struct fh_ext_class pfm_w_class =
506 {
507   6,
508   N_("writing as a portable file"),
509   pfm_close,
510 };