lexer: Reimplement for better testability and internationalization.
[pspp-builds.git] / src / data / sys-file-writer.c
index 3fed2e05445135ec2ebcc300ce23c4855d0f41df..b1cb7c22c0dcd5e7a1343041b66d75fcac28cd36 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2006, 2007, 2009 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2006, 2007, 2009, 2010, 2011 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -16,8 +16,8 @@
 
 #include <config.h>
 
-#include "sys-file-writer.h"
-#include "sys-file-private.h"
+#include "data/sys-file-writer.h"
+#include "data/sys-file-private.h"
 
 #include <ctype.h>
 #include <errno.h>
 #include <sys/stat.h>
 #include <time.h>
 
-#include <libpspp/float-format.h>
-#include <libpspp/integer-format.h>
-#include <libpspp/message.h>
-#include <libpspp/misc.h>
-#include <libpspp/str.h>
-#include <libpspp/i18n.h>
-#include <libpspp/version.h>
-
-#include <data/attributes.h>
-#include <data/case.h>
-#include <data/casewriter-provider.h>
-#include <data/casewriter.h>
-#include <data/dictionary.h>
-#include <data/file-handle-def.h>
-#include <data/file-name.h>
-#include <data/format.h>
-#include <data/make-file.h>
-#include <data/missing-values.h>
-#include <data/settings.h>
-#include <data/short-names.h>
-#include <data/value-labels.h>
-#include <data/variable.h>
-
-#include "minmax.h"
-#include "unlocked-io.h"
-#include "xalloc.h"
+#include "data/attributes.h"
+#include "data/case.h"
+#include "data/casewriter-provider.h"
+#include "data/casewriter.h"
+#include "data/dictionary.h"
+#include "data/file-handle-def.h"
+#include "data/file-name.h"
+#include "data/format.h"
+#include "data/make-file.h"
+#include "data/missing-values.h"
+#include "data/mrset.h"
+#include "data/settings.h"
+#include "data/short-names.h"
+#include "data/value-labels.h"
+#include "data/variable.h"
+#include "libpspp/float-format.h"
+#include "libpspp/i18n.h"
+#include "libpspp/integer-format.h"
+#include "libpspp/message.h"
+#include "libpspp/misc.h"
+#include "libpspp/str.h"
+#include "libpspp/string-array.h"
+#include "libpspp/version.h"
+
+#include "gl/xmemdup0.h"
+#include "gl/minmax.h"
+#include "gl/unlocked-io.h"
+#include "gl/xalloc.h"
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
@@ -95,9 +97,9 @@ struct sfm_writer
 static const struct casewriter_class sys_file_casewriter_class;
 
 static void write_header (struct sfm_writer *, const struct dictionary *);
-static void write_variable (struct sfm_writer *, const struct variable *, const struct dictionary *);
-static void write_value_labels (struct sfm_writer *,
-                                struct variable *, int idx, const struct dictionary *);
+static void write_variable (struct sfm_writer *, const struct variable *);
+static void write_value_labels (struct sfm_writer *, struct variable *,
+                                int idx);
 static void write_integer_info_record (struct sfm_writer *);
 static void write_float_info_record (struct sfm_writer *);
 
@@ -113,6 +115,9 @@ static void write_vls_length_table (struct sfm_writer *w,
 static void write_long_string_value_labels (struct sfm_writer *,
                                             const struct dictionary *);
 
+static void write_mrsets (struct sfm_writer *, const struct dictionary *,
+                          bool pre_v14);
+
 static void write_variable_display_parameters (struct sfm_writer *w,
                                                const struct dictionary *dict);
 
@@ -140,8 +145,8 @@ static void put_cmp_opcode (struct sfm_writer *, uint8_t);
 static void put_cmp_number (struct sfm_writer *, double);
 static void put_cmp_string (struct sfm_writer *, const void *, size_t);
 
-bool write_error (const struct sfm_writer *);
-bool close_writer (struct sfm_writer *);
+static bool write_error (const struct sfm_writer *);
+static bool close_writer (struct sfm_writer *);
 
 /* Returns default options for writing a system file. */
 struct sfm_write_options
@@ -155,8 +160,7 @@ sfm_writer_default_options (void)
 }
 
 /* Opens the system file designated by file handle FH for writing
-   cases from dictionary D according to the given OPTS.  If
-   COMPRESS is nonzero, the system file will be compressed.
+   cases from dictionary D according to the given OPTS.
 
    No reference to D is retained, so it may be modified or
    destroyed at will after this function returns.  D is not
@@ -205,14 +209,14 @@ sfm_open_writer (struct file_handle *fh, struct dictionary *d,
     goto error;
 
   /* Create the file on disk. */
-  mode = S_IRUSR | S_IRGRP | S_IROTH;
+  mode = 0444;
   if (opts.create_writeable)
-    mode |= S_IWUSR | S_IWGRP | S_IWOTH;
+    mode |= 0222;
   w->rf = replace_file_start (fh_get_file_name (fh), "wb", mode,
                               &w->file, NULL);
   if (w->rf == NULL)
     {
-      msg (ME, _("Error opening \"%s\" for writing as a system file: %s."),
+      msg (ME, _("Error opening `%s' for writing as a system file: %s."),
            fh_get_file_name (fh), strerror (errno));
       goto error;
     }
@@ -223,7 +227,7 @@ sfm_open_writer (struct file_handle *fh, struct dictionary *d,
   /* Write basic variable info. */
   short_names_assign (d);
   for (i = 0; i < dict_get_var_cnt (d); i++)
-    write_variable (w, dict_get_var (d, i), d);
+    write_variable (w, dict_get_var (d, i));
 
   /* Write out value labels. */
   idx = 0;
@@ -231,16 +235,18 @@ sfm_open_writer (struct file_handle *fh, struct dictionary *d,
     {
       struct variable *v = dict_get_var (d, i);
 
-      write_value_labels (w, v, idx, d);
+      write_value_labels (w, v, idx);
       idx += sfm_width_to_octs (var_get_width (v));
     }
 
-  if (dict_get_documents (d) != NULL)
+  if (dict_get_document_line_cnt (d) > 0)
     write_documents (w, d);
 
   write_integer_info_record (w);
   write_float_info_record (w);
 
+  write_mrsets (w, d, true);
+
   write_variable_display_parameters (w, d);
 
   if (opts.version >= 3)
@@ -254,6 +260,8 @@ sfm_open_writer (struct file_handle *fh, struct dictionary *d,
     write_data_file_attributes (w, d);
   write_variable_attributes (w, d);
 
+  write_mrsets (w, d, false);
+
   write_encoding_record (w, d);
 
   /* Write end-of-headers record. */
@@ -261,10 +269,7 @@ sfm_open_writer (struct file_handle *fh, struct dictionary *d,
   write_int (w, 0);
 
   if (write_error (w))
-    {
-      close_writer (w);
-      return NULL;
-    }
+    goto error;
 
   return casewriter_create (dict_get_proto (d), &sys_file_casewriter_class, w);
 
@@ -421,7 +426,7 @@ write_variable_continuation_records (struct sfm_writer *w, int width)
 /* Write the variable record(s) for variable V to system file
    W. */
 static void
-write_variable (struct sfm_writer *w, const struct variable *v, const struct dictionary *dict)
+write_variable (struct sfm_writer *w, const struct variable *v)
 {
   int width = var_get_width (v);
   int segment_cnt = sfm_width_to_segments (width);
@@ -461,12 +466,12 @@ write_variable (struct sfm_writer *w, const struct variable *v, const struct dic
   /* Value label. */
   if (var_has_label (v))
     {
-      const char *label = var_get_label (v);
-      char *l = recode_string (dict_get_encoding (dict), UTF8, label, -1);
-      size_t padded_len = ROUND_UP (MIN (strlen (l), 255), 4);
-      write_int (w, padded_len);
-      write_string (w, l, padded_len);
-      free (l);
+      char *label = recode_string (var_get_encoding (v), UTF8, var_get_label (v), -1);
+      size_t label_len = MIN (strlen (label), 255);
+      size_t padded_len = ROUND_UP (label_len, 4);
+      write_int (w, label_len);
+      write_string (w, label, padded_len);
+      free (label);
     }
 
   /* Write the missing values, if any, range first. */
@@ -508,7 +513,7 @@ write_variable (struct sfm_writer *w, const struct variable *v, const struct dic
    Value labels for long string variables are written separately,
    by write_long_string_value_labels. */
 static void
-write_value_labels (struct sfm_writer *w, struct variable *v, int idx, const struct dictionary *dict)
+write_value_labels (struct sfm_writer *w, struct variable *v, int idx)
 {
   const struct val_labs *val_labs;
   const struct val_lab **labels;
@@ -527,7 +532,7 @@ write_value_labels (struct sfm_writer *w, struct variable *v, int idx, const str
   for (i = 0; i < n_labels; i++)
     {
       const struct val_lab *vl = labels[i];
-      char *label = recode_string (dict_get_encoding (dict), UTF8, val_lab_get_label (vl), -1);
+      char *label = recode_string (var_get_encoding (v), UTF8, val_lab_get_label (vl), -1);
       uint8_t len = MIN (strlen (label), 255);
 
       write_value (w, val_lab_get_value (vl), var_get_width (v));
@@ -548,11 +553,22 @@ write_value_labels (struct sfm_writer *w, struct variable *v, int idx, const str
 static void
 write_documents (struct sfm_writer *w, const struct dictionary *d)
 {
-  size_t line_cnt = dict_get_document_line_cnt (d);
+  const struct string_array *docs = dict_get_documents (d);
+  const char *enc = dict_get_encoding (d);
+  size_t i;
 
   write_int (w, 6);             /* Record type. */
-  write_int (w, line_cnt);
-  write_bytes (w, dict_get_documents (d), line_cnt * DOC_LINE_LENGTH);
+  write_int (w, docs->n);
+  for (i = 0; i < docs->n; i++)
+    {
+      char *s = recode_string (enc, "UTF-8", docs->strings[i], -1);
+      size_t s_len = strlen (s);
+      size_t write_len = MIN (s_len, DOC_LINE_LENGTH);
+
+      write_bytes (w, s, write_len);
+      write_spaces (w, DOC_LINE_LENGTH - write_len);
+      free (s);
+    }
 }
 
 static void
@@ -568,10 +584,10 @@ put_attrset (struct string *string, const struct attrset *attrs)
       size_t j;
 
       ds_put_cstr (string, attribute_get_name (attr));
-      ds_put_char (string, '(');
+      ds_put_byte (string, '(');
       for (j = 0; j < n_values; j++) 
         ds_put_format (string, "'%s'\n", attribute_get_value (attr, j));
-      ds_put_char (string, ')');
+      ds_put_byte (string, ')');
     }
 }
 
@@ -611,7 +627,7 @@ write_variable_attributes (struct sfm_writer *w, const struct dictionary *d)
       if (attrset_count (attrs)) 
         {
           if (n_attrsets++)
-            ds_put_char (&s, '/');
+            ds_put_byte (&s, '/');
           ds_put_format (&s, "%s:", var_get_short_name (v, 0));
           put_attrset (&s, attrs);
         }
@@ -621,6 +637,64 @@ write_variable_attributes (struct sfm_writer *w, const struct dictionary *d)
   ds_destroy (&s);
 }
 
+/* Write multiple response sets.  If PRE_V14 is true, writes sets supported by
+   SPSS before release 14, otherwise writes sets supported only by later
+   versions. */
+static void
+write_mrsets (struct sfm_writer *w, const struct dictionary *dict,
+              bool pre_v14)
+{
+  struct string s = DS_EMPTY_INITIALIZER;
+  size_t n_mrsets;
+  size_t i;
+
+  n_mrsets = dict_get_n_mrsets (dict);
+  if (n_mrsets == 0)
+    return;
+
+  for (i = 0; i < n_mrsets; i++)
+    {
+      const struct mrset *mrset = dict_get_mrset (dict, i);
+      const char *label;
+      size_t j;
+
+      if ((mrset->type != MRSET_MD || mrset->cat_source != MRSET_COUNTEDVALUES)
+          != pre_v14)
+        continue;
+
+      ds_put_format (&s, "%s=", mrset->name);
+      if (mrset->type == MRSET_MD)
+        {
+          char *counted;
+
+          if (mrset->cat_source == MRSET_COUNTEDVALUES)
+            ds_put_format (&s, "E %d ", mrset->label_from_var_label ? 11 : 1);
+          else
+            ds_put_byte (&s, 'D');
+
+          if (mrset->width == 0)
+            counted = xasprintf ("%.0f", mrset->counted.f);
+          else
+            counted = xmemdup0 (value_str (&mrset->counted, mrset->width),
+                                mrset->width);
+          ds_put_format (&s, "%zu %s", strlen (counted), counted);
+          free (counted);
+        }
+      else
+        ds_put_byte (&s, 'C');
+      ds_put_byte (&s, ' ');
+
+      label = mrset->label && !mrset->label_from_var_label ? mrset->label : "";
+      ds_put_format (&s, "%zu %s", strlen (label), label);
+
+      for (j = 0; j < mrset->n_vars; j++)
+        ds_put_format (&s, " %s", var_get_short_name (mrset->vars[j], 0));
+      ds_put_byte (&s, '\n');
+    }
+  write_attribute_record (w, &s, pre_v14 ? 7 : 19);
+  ds_destroy (&s);
+}
+
 /* Write the alignment, width and scale values. */
 static void
 write_variable_display_parameters (struct sfm_writer *w,
@@ -781,7 +855,7 @@ write_longvar_table (struct sfm_writer *w, const struct dictionary *dict)
       char *longname = recode_string (dict_get_encoding (dict), UTF8, var_get_name (v), -1);
 
       if (i)
-        ds_put_char (&map, '\t');
+        ds_put_byte (&map, '\t');
       ds_put_format (&map, "%s=%s",
                      var_get_short_name (v, 0), longname);
       free (longname);
@@ -881,7 +955,7 @@ sys_file_casewriter_destroy (struct casewriter *writer, void *w_)
 }
 
 /* Returns true if an I/O error has occurred on WRITER, false otherwise. */
-bool
+static bool
 write_error (const struct sfm_writer *writer)
 {
   return ferror (writer->file);
@@ -889,7 +963,7 @@ write_error (const struct sfm_writer *writer)
 
 /* Closes a system file after we're done with it.
    Returns true if successful, false if an I/O error occurred. */
-bool
+static bool
 close_writer (struct sfm_writer *w)
 {
   bool ok;
@@ -910,7 +984,7 @@ close_writer (struct sfm_writer *w)
       /* Seek back to the beginning and update the number of cases.
          This is just a courtesy to later readers, so there's no need
          to check return values or report errors. */
-      if (ok && w->case_cnt <= INT32_MAX && !fseek (w->file, 80, SEEK_SET))
+      if (ok && w->case_cnt <= INT32_MAX && !fseeko (w->file, 80, SEEK_SET))
         {
           write_int (w, w->case_cnt);
           clearerr (w->file);
@@ -920,7 +994,7 @@ close_writer (struct sfm_writer *w)
         ok = false;
 
       if (!ok)
-        msg (ME, _("An I/O error occurred writing system file \"%s\"."),
+        msg (ME, _("An I/O error occurred writing system file `%s'."),
              fh_get_file_name (w->fh));
 
       if (ok ? !replace_file_commit (w->rf) : !replace_file_abort (w->rf))