work on SAVE DATA COLLECTION
[pspp] / src / data / mdd-writer.c
diff --git a/src/data/mdd-writer.c b/src/data/mdd-writer.c
new file mode 100644 (file)
index 0000000..97efe22
--- /dev/null
@@ -0,0 +1,245 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2018 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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "data/mdd-writer.h"
+
+#include <errno.h>
+#include <libxml/xmlwriter.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "data/dictionary.h"
+#include "data/file-handle-def.h"
+#include "data/make-file.h"
+#include "data/short-names.h"
+#include "data/value-labels.h"
+#include "data/variable.h"
+#include "libpspp/message.h"
+#include "libpspp/misc.h"
+
+#include "gl/ftoastr.h"
+#include "gl/xalloc.h"
+#include "gl/xmemdup0.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) (msgid)
+
+#define _xml(X) (CHAR_CAST (const xmlChar *, (X)))
+
+/* Metadata file writer. */
+struct mdd_writer
+  {
+    struct file_handle *fh;     /* File handle. */
+    struct fh_lock *lock;       /* Mutual exclusion for file. */
+    FILE *file;                        /* File stream. */
+    struct replace_file *rf;    /* Ticket for replacing output file. */
+
+    xmlTextWriter *writer;
+  };
+
+/* Returns true if an I/O error has occurred on WRITER, false otherwise. */
+static bool
+mdd_write_error (const struct mdd_writer *writer)
+{
+  return ferror (writer->file);
+}
+
+static bool
+mdd_close (struct mdd_writer *w)
+{
+  if (!w)
+    return true;
+
+  if (w->writer)
+    xmlFreeTextWriter (w->writer);
+
+  bool ok = true;
+  if (w->file)
+    {
+      fflush (w->file);
+
+      ok = !mdd_write_error (w);
+      if (fclose (w->file) == EOF)
+        ok = false;
+
+      if (!ok)
+        msg (ME, _("An I/O error occurred writing metadata file `%s'."),
+             fh_get_file_name (w->fh));
+
+      if (ok ? !replace_file_commit (w->rf) : !replace_file_abort (w->rf))
+        ok = false;
+    }
+
+  fh_unlock (w->lock);
+  fh_unref (w->fh);
+
+  free (w);
+
+  return ok;
+}
+
+bool
+mdd_write (struct file_handle *fh, struct dictionary *dict,
+           const char *sav_name)
+{
+  struct mdd_writer *w = xzalloc (sizeof *w);
+
+  /* Open file handle as an exclusive writer. */
+  /* TRANSLATORS: this fragment will be interpolated into
+     messages in fh_lock() that identify types of files. */
+  w->lock = fh_lock (fh, FH_REF_FILE, N_("metadata file"), FH_ACC_WRITE, true);
+  if (w->lock == NULL)
+    goto error;
+
+  /* Create the file on disk. */
+  w->rf = replace_file_start (fh, "wb", 0444, &w->file);
+  if (w->rf == NULL)
+    {
+      msg (ME, _("Error opening `%s' for writing as a metadata file: %s."),
+           fh_get_file_name (fh), strerror (errno));
+      goto error;
+    }
+
+  w->writer = xmlNewTextWriter (xmlOutputBufferCreateFile (w->file, NULL));
+  if (!w->writer)
+    {
+      msg (ME, _("Internal error creating xmlTextWriter."));
+      goto error;
+    }
+
+  xmlTextWriterStartDocument (w->writer, NULL, "UTF-8", NULL);
+
+  /* <?xml-stylesheet type="text/xsl" href="mdd.xslt"?> */
+  xmlTextWriterStartPI (w->writer, _xml ("xml-stylesheet"));
+  xmlTextWriterWriteString (w->writer,
+                            _xml ("type=\"text/xsl\" href=\"mdd.xslt\""));
+  xmlTextWriterEndPI (w->writer);
+
+  xmlTextWriterStartElement (w->writer, _xml ("xml"));
+
+  /* <mdm:metadata xmlns:mdm="http://www.spss.com/mr/dm/metadatamodel/Arc
+     3/2000-02-04" mdm_createversion="7.0.0.0.331"
+     mdm_lastversion="7.0.0.0.331" id="c4c181c1-0d7c-42e3-abcd-f08296d1dfdc"
+     data_version="9" data_sub_version="1" systemvariable="0"
+     dbfiltervalidation="-1"> */
+  xmlTextWriterStartElementNS (
+    w->writer, _xml ("mdm"), _xml ("xml"),
+    _xml ("http://www.spss.com/mr/dm/metadatamodel/Arc 3/2000-02-04"));
+  static const struct pair
+    {
+      const char *key, *value;
+    }
+  pairs[] =
+    {
+      { "mdm_createversion", "7.0.0.0.331" },
+      { "mdm_lastversion", "7.0.0.0.331" },
+      { "id", "c4c181c1-0d7c-42e3-abcd-f08296d1dfdc" },
+      { "data_version", "9" },
+      { "data_sub_version", "1" },
+      { "systemvariable", "0" },
+      { "dbfiltervalidation", "-1" },
+    };
+  const int n_pairs = sizeof pairs / sizeof *pairs;
+  for (const struct pair *p = pairs; p < &pairs[n_pairs]; p++)
+    xmlTextWriterWriteAttribute (w->writer, _xml (p->key), _xml (p->value));
+  xmlTextWriterEndElement (w->writer);
+
+  /* <atoms> */
+  xmlTextWriterStartElement (w->writer, _xml ("atoms"));
+  xmlTextWriterEndElement (w->writer);
+
+  /* <datasources> */
+  xmlTextWriterStartElement (w->writer, _xml ("datasources"));
+  xmlTextWriterWriteAttribute (w->writer, _xml ("default"), _xml ("mrSavDsc"));
+
+  /* <connection> */
+  xmlTextWriterStartElement (w->writer, _xml ("connection"));
+  xmlTextWriterWriteAttribute (w->writer, _xml ("name"), _xml ("mrSavDsc"));
+  xmlTextWriterWriteAttribute (w->writer, _xml ("dblocation"),
+                               _xml (sav_name));
+  xmlTextWriterWriteAttribute (w->writer,
+                               _xml ("cdscname"), _xml ("mrSavDsc"));
+  xmlTextWriterWriteAttribute (w->writer, _xml ("project"), _xml ("126"));
+
+  size_t n_vars = dict_get_var_cnt (dict);
+  short_names_assign (dict);
+  for (size_t i = 0; i < n_vars; i++)
+    {
+      const struct variable *var = dict_get_var (dict, i);
+      xmlTextWriterStartElement (w->writer, _xml ("var"));
+
+      /* XXX Should convert short name to all-lowercase below.  */
+      xmlTextWriterWriteAttribute (w->writer, _xml ("fullname"),
+                                   _xml (var_get_short_name (var, 0)));
+      xmlTextWriterWriteAttribute (w->writer, _xml ("aliasname"),
+                                 _xml (var_get_name (var)));
+
+      const struct val_labs *val_labs = var_get_value_labels (var);
+      size_t n_vls = val_labs_count (val_labs);
+      if (n_vls)
+        {
+          const struct val_lab **vls = val_labs_sorted (val_labs);
+
+          xmlTextWriterStartElement (w->writer, _xml ("nativevalues"));
+          int width = var_get_width (var);
+          for (size_t j = 0; j < n_vls; j++)
+            {
+              const struct val_lab *vl = vls[j];
+              xmlTextWriterStartElement (w->writer, _xml ("nativevalue"));
+              /* XXX Should convert to lowercase, change non-id characters to
+                 _, prefix with _ if starts with non-letter */
+              xmlTextWriterWriteAttribute (w->writer, _xml ("fullname"),
+                                           _xml (val_lab_get_label (vl)));
+
+              /* XXX below would better use syntax_gen_value(). */
+              const union value *value = val_lab_get_value (vl);
+              if (width)
+                {
+                  char *s = xmemdup0 (value_str (value, width), width);
+                  xmlTextWriterWriteAttribute (w->writer, _xml ("value"),
+                                               _xml (s));
+                  free (s);
+                }
+              else
+                {
+                  char s[DBL_BUFSIZE_BOUND];
+
+                  c_dtoastr (s, sizeof s, 0, 0, value->f);
+                  xmlTextWriterWriteAttribute (w->writer, _xml ("value"),
+                                               _xml (s));
+                }
+              xmlTextWriterEndElement (w->writer);
+            }
+          xmlTextWriterEndElement (w->writer);
+
+          free (vls);
+        }
+
+      xmlTextWriterEndElement (w->writer);
+    }
+
+  xmlTextWriterEndElement (w->writer); /* </xml> */
+
+  xmlTextWriterEndDocument (w->writer);
+
+error:
+  mdd_close (w);
+  return NULL;
+}