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