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