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