Change how checking for missing values works.
[pspp] / src / data / mdd-writer.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2018 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "data/mdd-writer.h"
20
21 #include <errno.h>
22 #include <libxml/xmlwriter.h>
23 #include <stdbool.h>
24 #include <string.h>
25 #include <sys/stat.h>
26 #include <time.h>
27
28 #include "data/dictionary.h"
29 #include "data/file-handle-def.h"
30 #include "data/format.h"
31 #include "data/make-file.h"
32 #include "data/missing-values.h"
33 #include "data/mrset.h"
34 #include "data/short-names.h"
35 #include "data/value-labels.h"
36 #include "data/variable.h"
37 #include "libpspp/message.h"
38 #include "libpspp/misc.h"
39 #include "libpspp/string-map.h"
40 #include "libpspp/string-set.h"
41 #include "libpspp/version.h"
42
43 #include "gl/c-ctype.h"
44 #include "gl/ftoastr.h"
45 #include "gl/xalloc.h"
46 #include "gl/xmemdup0.h"
47
48 #include "gettext.h"
49 #define _(msgid) gettext (msgid)
50 #define N_(msgid) (msgid)
51
52 #define _xml(X) CHAR_CAST (const xmlChar *, X)
53
54 enum val_numeric_type
55   {
56     VAL_INTEGER_TYPE = 1,
57     VAL_STRING_TYPE = 2,
58     VAL_CATEGORICAL_TYPE = 3,
59     VAL_DATETIME_TYPE = 5,
60     VAL_DECIMAL_TYPE = 6
61   };
62
63 /* Get the numeric type of the variable. */
64 static enum val_numeric_type
65 var_get_numeric_type_ (const struct variable *var)
66 {
67   const struct fmt_spec *print = var_get_print_format (var);
68   if (var_get_type (var) == VAL_STRING)
69     return VAL_STRING_TYPE;
70
71   if (var_has_value_labels (var))
72     return VAL_CATEGORICAL_TYPE;
73
74   if (print->d > 0)
75     return VAL_DECIMAL_TYPE;
76
77   if (print->type == FMT_DATETIME)
78     return VAL_DATETIME_TYPE;
79
80   if (print->type == FMT_F)
81     return VAL_INTEGER_TYPE;
82
83   return VAL_CATEGORICAL_TYPE;
84 }
85
86 /* Metadata file writer. */
87 struct mdd_writer
88   {
89     struct file_handle *fh;     /* File handle. */
90     struct fh_lock *lock;       /* Mutual exclusion for file. */
91     FILE *file;                 /* File stream. */
92     struct replace_file *rf;    /* Ticket for replacing output file. */
93
94     xmlTextWriter *writer;
95   };
96
97 /* Either a variable or a multi-response set. */
98 struct var_or_mrset
99   {
100     /* If true, the union contains a multi-response set.  Otherwise, it
101        contains a variable. */
102     bool is_mrset;
103     union
104       {
105         const struct mrset *mrset;
106         const struct variable *variable;
107       };
108   };
109
110 struct all_dict_variables
111   {
112     struct var_or_mrset *vars;
113     size_t count;
114   };
115
116 struct all_dict_variables
117 all_variables (struct dictionary *dict);
118
119 /* Extract all variables in a dictionary, both normal and multi.
120
121    Excludes variables which are subvariables of an MRSET. */
122 struct all_dict_variables
123 all_variables (struct dictionary *dict)
124 {
125   size_t n_vars = dict_get_n_vars (dict);
126
127   /* Start with a set of all variable names. */
128   struct string_set var_names = STRING_SET_INITIALIZER (var_names);
129   for (size_t i = 0; i < n_vars; ++i)
130     {
131       const struct variable *var = dict_get_var (dict, i);
132       string_set_insert (&var_names, var_get_name (var));
133     }
134
135   /* For each MRSET M, remove all subvariable names of M from S. */
136   size_t n_sets = dict_get_n_mrsets (dict);
137   for (size_t set_idx = 0; set_idx < n_sets; set_idx++)
138     {
139       const struct mrset *mrset = dict_get_mrset (dict, set_idx);
140       for (size_t i = 0; i < mrset->n_vars; ++i)
141         {
142           const struct variable *var = mrset->vars[i];
143           string_set_delete (&var_names, var_get_name (var));
144         }
145     }
146
147   /* Add the number of remaining variables to the number of MRSets. */
148   size_t var_count = n_sets + string_set_count (&var_names);
149
150   /* Allocate an array of var_or_mrset pointers (initially null). */
151   struct var_or_mrset *var_or_mrset_array
152     = XCALLOC (var_count, struct var_or_mrset);
153
154   /* Fill the array. */
155   struct string_set added_mrsets = STRING_SET_INITIALIZER (added_mrsets);
156
157   size_t var_idx = 0;
158   for (size_t i = 0; i < n_vars; ++i)
159     {
160       const struct variable *var = dict_get_var (dict, i);
161       bool found_in_mrset = false;
162       for (size_t set_idx = 0; set_idx < n_sets; set_idx++)
163         {
164           const struct mrset *mrset = dict_get_mrset (dict, set_idx);
165           for (size_t i = 0; i < mrset->n_vars; ++i)
166             {
167               const struct variable *subvar = mrset->vars[i];
168               if (!strcmp (var_get_name (var), var_get_name (subvar)))
169                 {
170                   /* Then this variable is a member of this MRSet. */
171                   found_in_mrset = true;
172
173                   /* Check if this MRSet has already been added and add
174                      otherwise. */
175                   if (!string_set_contains (&added_mrsets, mrset->name))
176                     {
177                       string_set_insert (&added_mrsets, mrset->name);
178
179                       assert (var_idx < var_count);
180                       struct var_or_mrset *v_o_m
181                         = &var_or_mrset_array[var_idx++];
182                       v_o_m->is_mrset = true;
183                       v_o_m->mrset = mrset;
184                     }
185                 }
186             }
187         }
188
189       /* If the variable wasn't found to be a member of any MRSets, record it as
190          a normal variable. */
191       if (!found_in_mrset)
192         {
193           /* The variable is not part of a multi-response set. */
194           assert (var_idx < var_count);
195           struct var_or_mrset *v_o_m = &var_or_mrset_array[var_idx++];
196           v_o_m->is_mrset = false;
197           v_o_m->variable = var;
198         }
199     }
200
201   /* Ensure that we filled up the array. */
202   assert (var_idx == var_count);
203
204   /* Cleanup. */
205   string_set_destroy (&added_mrsets);
206   string_set_destroy (&var_names);
207
208   struct all_dict_variables result;
209   result.vars = var_or_mrset_array;
210   result.count = var_count;
211   return result;
212 }
213
214
215 /* Returns true if an I/O error has occurred on WRITER, false otherwise. */
216 static bool
217 mdd_write_error (const struct mdd_writer *writer)
218 {
219   return ferror (writer->file);
220 }
221
222 static bool
223 mdd_close (struct mdd_writer *w)
224 {
225   if (!w)
226     return true;
227
228   if (w->writer)
229     xmlFreeTextWriter (w->writer);
230
231   bool ok = true;
232   if (w->file)
233     {
234       fflush (w->file);
235
236       ok = !mdd_write_error (w);
237       if (fclose (w->file) == EOF)
238         ok = false;
239
240       if (!ok)
241         msg (ME, _("An I/O error occurred writing metadata file `%s'."),
242              fh_get_file_name (w->fh));
243
244       if (ok ? !replace_file_commit (w->rf) : !replace_file_abort (w->rf))
245         ok = false;
246     }
247
248   fh_unlock (w->lock);
249   fh_unref (w->fh);
250
251   free (w);
252
253   return ok;
254 }
255
256 static void
257 write_empty_element (xmlTextWriter *writer, const char *name)
258 {
259   xmlTextWriterStartElement (writer, _xml (name));
260   xmlTextWriterEndElement (writer);
261 }
262
263 static void
264 write_attr (xmlTextWriter *writer, const char *key, const char *value)
265 {
266   xmlTextWriterWriteAttribute (writer, _xml (key), _xml (value));
267 }
268
269 static void
270 write_global_name_space (xmlTextWriter *writer)
271 {
272   write_attr (writer, "global-name-space", "-1");
273 }
274
275 static void
276 write_xml_lang (xmlTextWriter *writer)
277 {
278   /* XXX should write real language */
279   xmlTextWriterWriteAttributeNS (writer, _xml ("xml"), _xml ("lang"), NULL,
280                                  _xml ("en-US"));
281 }
282
283 static void
284 write_value_label_value (xmlTextWriter *writer, const struct val_lab *vl,
285                          int width)
286 {
287   /* XXX below would better use syntax_gen_value(). */
288   const union value *value = val_lab_get_value (vl);
289   if (width)
290     {
291       char *s = xmemdup0 (value->s, width);
292       xmlTextWriterWriteAttribute (writer, _xml ("value"), _xml (s));
293       free (s);
294     }
295   else
296     {
297       char s[DBL_BUFSIZE_BOUND];
298
299       c_dtoastr (s, sizeof s, 0, 0, value->f);
300       xmlTextWriterWriteAttribute (writer, _xml ("value"), _xml (s));
301     }
302 }
303
304 static void
305 write_context (xmlTextWriter *writer, const char *name,
306                const char *alternative)
307 {
308   xmlTextWriterStartElement (writer, _xml ("context"));
309   write_attr (writer, "name", name);
310   if (alternative)
311     {
312       xmlTextWriterStartElement (writer, _xml ("alternatives"));
313       xmlTextWriterStartElement (writer, _xml ("alternative"));
314       write_attr (writer, "name", alternative);
315       xmlTextWriterEndElement (writer);
316       write_empty_element (writer, "deleted");
317       xmlTextWriterEndElement (writer);
318     }
319   xmlTextWriterEndElement (writer);
320 }
321
322 static char *
323 name_to_id (const char *name)
324 {
325   char *id = xmalloc (strlen (name) + 2);
326   char *d = id;
327   for (const char *s = name; *s; s++)
328     {
329       if (c_isalpha (*s))
330         *d++ = c_tolower (*s);
331       else if (c_isdigit (*s))
332         {
333           if (d == id)
334             *d++ = '_';
335           *d++ = *s;
336         }
337       else
338         {
339           if (d == id || d[-1] != '_')
340             *d++ = '_';
341         }
342     }
343   if (d > id && d[-1] == '_')
344     d--;
345   *d = '\0';
346
347   return id;
348 }
349
350 static void
351 write_variable_section (xmlTextWriter *writer, const struct variable *var, int id)
352 {
353   xmlTextWriterStartElement (writer, _xml ("variable"));
354   write_attr (writer, "name", var_get_name (var));
355
356   bool is_string = var_get_type (var) == VAL_STRING;
357
358   int type = var_get_numeric_type_ (var);
359   xmlTextWriterWriteFormatAttribute (writer, _xml ("type"), "%d", type);
360
361   int max = is_string ? var_get_width (var) : 1;
362   xmlTextWriterWriteFormatAttribute (writer, _xml ("max"), "%d", max);
363
364   write_attr (writer, "maxtype", "3");
365
366   const char *label = var_get_label (var);
367   if (label)
368     {
369       xmlTextWriterStartElement (writer, _xml ("labels"));
370       write_attr (writer, "context", "LABEL");
371
372       xmlTextWriterStartElement (writer, _xml ("text"));
373       write_attr (writer, "context", "ANALYSIS");
374       write_xml_lang (writer);
375       xmlTextWriterWriteString (writer, _xml (label));
376       xmlTextWriterEndElement (writer);
377
378       xmlTextWriterEndElement (writer);
379     }
380
381   const struct val_labs *val_labs = var_get_value_labels (var);
382   size_t n_vls = val_labs_count (val_labs);
383   if (n_vls)
384     {
385       const struct val_lab **vls = val_labs_sorted (val_labs);
386
387       /* <categories/> */
388       xmlTextWriterStartElement (writer, _xml ("categories"));
389       write_global_name_space (writer);
390       int width = var_get_width (var);
391       for (size_t j = 0; j < n_vls; j++)
392         {
393           const struct val_lab *vl = vls[j];
394           const union value *value = val_lab_get_value (vl);
395
396           /* <category> */
397           xmlTextWriterStartElement (writer, _xml ("category"));
398           xmlTextWriterWriteFormatAttribute (writer, _xml ("id"), "_%d", id);
399
400           char *name = name_to_id (val_lab_get_label (vl));
401           write_attr (writer, "name", name);
402           free (name);
403
404           /* If the value here is missing, annotate it.
405
406              XXX only checking "user" here because not sure of correct other
407              cases. */
408           enum mv_class miss = var_is_value_missing (var, value);
409           if (miss)
410             write_attr (writer, "missing", miss == MV_USER ? "user" : "system");
411
412           /* <properties/> */
413           xmlTextWriterStartElement (writer, _xml ("properties"));
414           xmlTextWriterStartElement (writer, _xml ("property"));
415           write_attr (writer, "name", "Value");
416           write_value_label_value (writer, vl, width);
417           write_attr (writer, "type", "5");
418           write_attr (writer, "context", "Analysis");
419           xmlTextWriterEndElement (writer); /* </property> */
420           xmlTextWriterEndElement (writer); /* </properties> */
421
422           /* <labels/> */
423           xmlTextWriterStartElement (writer, _xml ("labels"));
424           write_attr (writer, "context", "LABEL");
425           xmlTextWriterStartElement (writer, _xml ("text"));
426           write_attr (writer, "context", "ANALYSIS");
427           write_xml_lang (writer);
428           xmlTextWriterWriteString (writer,
429                                     _xml (val_lab_get_label (vl)));
430           xmlTextWriterEndElement (writer); /* </text> */
431           xmlTextWriterEndElement (writer); /* </labels> */
432
433
434           /* </category> */
435           xmlTextWriterEndElement (writer);
436         }
437       write_empty_element (writer, "deleted");
438       xmlTextWriterEndElement (writer); /* </categories> */
439
440       free (vls);
441     }
442   /* </variable> */
443   xmlTextWriterEndElement (writer);
444 }
445
446 bool
447 mdd_write (struct file_handle *fh, struct dictionary *dict,
448            const char *sav_name)
449 {
450   struct mdd_writer *w = XZALLOC (struct mdd_writer);
451   size_t n_vars = dict_get_n_vars (dict);
452
453   /* Open file handle as an exclusive writer. */
454   /* TRANSLATORS: this fragment will be interpolated into
455      messages in fh_lock() that identify types of files. */
456   w->lock = fh_lock (fh, FH_REF_FILE, N_("metadata file"), FH_ACC_WRITE, true);
457   if (w->lock == NULL)
458     goto error;
459
460   /* Create the file on disk. */
461   w->rf = replace_file_start (fh, "wb", 0444, &w->file);
462   if (w->rf == NULL)
463     {
464       msg (ME, _("Error opening `%s' for writing as a metadata file: %s."),
465            fh_get_file_name (fh), strerror (errno));
466       goto error;
467     }
468
469   w->writer = xmlNewTextWriter (xmlOutputBufferCreateFile (w->file, NULL));
470   if (!w->writer)
471     {
472       msg (ME, _("Internal error creating xmlTextWriter.  "
473                  "Please report this to %s."), PACKAGE_BUGREPORT);
474       goto error;
475     }
476
477   xmlTextWriterStartDocument (w->writer, NULL, "UTF-8", NULL);
478
479   /* The MDD file contents seem to roughly correspond to the object model
480      documentatation at:
481      https://support.unicomsi.com/manuals/intelligence/75/DDL/MDM/docjet/metadata/chm/contentsf.html.  */
482
483   /* <?xml-stylesheet type="text/xsl" href="mdd.xslt"?> */
484   xmlTextWriterStartPI (w->writer, _xml ("xml-stylesheet"));
485   xmlTextWriterWriteString (w->writer,
486                             _xml ("type=\"text/xsl\" href=\"mdd.xslt\""));
487   xmlTextWriterEndPI (w->writer);
488
489   xmlTextWriterStartElement (w->writer, _xml ("xml"));
490
491   /* <mdm:metadata xmlns:mdm="http://www.spss.com/mr/dm/metadatamodel/Arc
492      3/2000-02-04" mdm_createversion="7.0.0.0.331"
493      mdm_lastversion="7.0.0.0.331" id="c4c181c1-0d7c-42e3-abcd-f08296d1dfdc"
494      data_version="9" data_sub_version="1" systemvariable="0"
495      dbfiltervalidation="-1"> */
496   xmlTextWriterStartElementNS (
497     w->writer, _xml ("mdm"), _xml ("metadata"),
498     _xml ("http://www.spss.com/mr/dm/metadatamodel/Arc%203/2000-02-04"));
499   static const struct pair
500     {
501       const char *key, *value;
502     }
503   pairs[] =
504     {
505       { "mdm_createversion", "7.0.0.0.331" },
506       { "mdm_lastversion", "7.0.0.0.331" },
507       { "id", "c4c181c1-0d7c-42e3-abcd-f08296d1dfdc" },
508       { "data_version", "9" },
509       { "data_sub_version", "1" },
510       { "systemvariable", "0" },
511       { "dbfiltervalidation", "-1" },
512     };
513   const int n_pairs = sizeof pairs / sizeof *pairs;
514   for (const struct pair *p = pairs; p < &pairs[n_pairs]; p++)
515     xmlTextWriterWriteAttribute (w->writer, _xml (p->key), _xml (p->value));
516
517   /* <atoms/> */
518   xmlTextWriterStartElement (w->writer, _xml ("atoms"));
519   /* XXX Real files contain a list of languages and a few other random strings
520      here in <atom name="..."/> elements.  It's really not clear what they're
521      good for. */
522   xmlTextWriterEndElement (w->writer);
523
524   /* <datasources> */
525   xmlTextWriterStartElement (w->writer, _xml ("datasources"));
526   xmlTextWriterWriteAttribute (w->writer, _xml ("default"), _xml ("mrSavDsc"));
527
528   /* <connection/> */
529   xmlTextWriterStartElement (w->writer, _xml ("connection"));
530   write_attr (w->writer, "name", "mrSavDsc");
531   write_attr (w->writer, "dblocation", sav_name);
532   write_attr (w->writer, "cdscname", "mrSavDsc");
533   write_attr (w->writer, "project", "126");
534
535   struct all_dict_variables allvars = all_variables (dict);
536   struct var_or_mrset *var_or_mrset_array = allvars.vars;
537   size_t var_count = allvars.count;
538
539   short_names_assign (dict);
540   for (size_t i = 0; i < var_count; i++)
541     {
542       const struct var_or_mrset var_or_mrset = var_or_mrset_array[i];
543       xmlTextWriterStartElement (w->writer, _xml ("var"));
544       if (var_or_mrset.is_mrset)
545         {
546           const struct mrset *mrset = var_or_mrset.mrset;
547           write_attr (w->writer, "fullname", mrset->name + 1);
548           write_attr (w->writer, "aliasname", mrset->name);
549
550           for (size_t subvar_idx = 0; subvar_idx < mrset->n_vars; subvar_idx++)
551             {
552               xmlTextWriterStartElement(w->writer, _xml ("subalias"));
553               xmlTextWriterWriteFormatAttribute (w->writer, _xml ("index"),
554                                                  "%zu", subvar_idx);
555               write_attr (w->writer, "name",
556                           var_get_name (mrset->vars[subvar_idx]));
557
558               xmlTextWriterEndElement (w->writer); /* subalias */
559             }
560         }
561       else
562         {
563           const struct variable *var = var_or_mrset.variable;
564
565           char *short_name = xstrdup (var_get_short_name (var, 0));
566           for (char *p = short_name; *p; p++)
567             *p = c_tolower (*p);
568           write_attr (w->writer, "fullname", short_name);
569           free (short_name);
570
571           write_attr (w->writer, "aliasname", var_get_name (var));
572
573           const struct val_labs *val_labs = var_get_value_labels (var);
574           size_t n_vls = val_labs_count (val_labs);
575           if (n_vls)
576             {
577               const struct val_lab **vls = val_labs_sorted (val_labs);
578
579               xmlTextWriterStartElement (w->writer, _xml ("nativevalues"));
580               int width = var_get_width (var);
581               for (size_t j = 0; j < n_vls; j++)
582                 {
583                   const struct val_lab *vl = vls[j];
584                   xmlTextWriterStartElement (w->writer, _xml ("nativevalue"));
585
586                   char *fullname = name_to_id (val_lab_get_label (vl));
587                   write_attr (w->writer, "fullname", fullname);
588                   free (fullname);
589
590                   write_value_label_value (w->writer, vl, width);
591                   xmlTextWriterEndElement (w->writer);
592                 }
593               xmlTextWriterEndElement (w->writer);
594
595               free (vls);
596             }
597
598         }
599       xmlTextWriterEndElement (w->writer); /* var */
600     }
601
602   xmlTextWriterEndElement (w->writer); /* connection */
603   xmlTextWriterEndElement (w->writer); /* datasources */
604
605   /* If the dictionary has a label, record it here */
606   const char *file_label = dict_get_label (dict);
607   if (file_label != NULL)
608     {
609       xmlTextWriterStartElement (w->writer, _xml ("labels"));
610       write_attr (w->writer, "context", "LABEL");
611       xmlTextWriterStartElement (w->writer, _xml ("text"));
612
613       write_attr (w->writer, "context", "ANALYSIS");
614       write_xml_lang (w->writer);
615       xmlTextWriterWriteString (w->writer, _xml (file_label));
616
617       /* </text> */
618       xmlTextWriterEndElement (w->writer);
619
620       /* </labels> */
621       xmlTextWriterEndElement (w->writer);
622     }
623
624   /* We reserve ids 1...N_VARS for variables and then start other ids after
625      that. */
626   int id = dict_get_n_vars (dict) + 1;
627
628   /* <definition/> */
629   xmlTextWriterStartElement (w->writer, _xml ("definition"));
630   for (size_t i = 0; i < var_count; i++)
631     {
632       xmlTextWriterWriteFormatAttribute (w->writer, _xml ("id"), "%zu", i + 1);
633       const struct var_or_mrset var_or_mrset = var_or_mrset_array[i];
634
635       if (var_or_mrset.is_mrset)
636         {
637           const struct mrset *mrset = var_or_mrset.mrset;
638           xmlTextWriterStartElement (w->writer, _xml ("variable"));
639           write_attr (w->writer, "name", mrset->name);
640
641           /* Use the type of the first subvariable as the type of the MRSET? */
642           write_attr (w->writer, "type", "3");
643
644           xmlTextWriterStartElement (w->writer, _xml ("properties"));
645           xmlTextWriterStartElement (w->writer, _xml ("property"));
646           write_attr (w->writer, "name", "QvLabel");
647           write_attr (w->writer, "value", mrset->name);
648           write_attr (w->writer, "type", "8");
649           write_attr (w->writer, "context", "Analysis");
650           /* </property> */
651           xmlTextWriterEndElement (w->writer);
652           /* </properties> */
653           xmlTextWriterEndElement (w->writer);
654
655
656           xmlTextWriterStartElement (w->writer, _xml ("labels"));
657           write_attr (w->writer, "context", "LABEL");
658           xmlTextWriterStartElement (w->writer, _xml ("text"));
659
660           write_attr (w->writer, "context", "ANALYSIS");
661           write_xml_lang (w->writer);
662           xmlTextWriterWriteString (w->writer, _xml (mrset->label));
663
664           /* </text> */
665           xmlTextWriterEndElement (w->writer);
666
667           /* </labels> */
668           xmlTextWriterEndElement (w->writer);
669
670           xmlTextWriterStartElement (w->writer, _xml ("categories"));
671           write_attr (w->writer, "global-name-space", "-1");
672           write_empty_element (w->writer, "deleted");
673
674           /* Individual categories */
675           int value = 2;
676           for (size_t var_idx = 0; var_idx < mrset->n_vars; ++var_idx)
677             {
678               const struct variable *subvar = mrset->vars[var_idx];
679               value += 2;
680               xmlTextWriterStartElement (w->writer, _xml ("category"));
681               write_attr (w->writer, "context", "LABEL");
682               char *name_without_spaces = strdup (var_get_name (subvar));
683               for (size_t i = 0; name_without_spaces[i]; ++i)
684                 if (name_without_spaces[i] == ' ') name_without_spaces[i] = '_';
685               write_attr (w->writer, "name", name_without_spaces);
686               free (name_without_spaces);
687
688               xmlTextWriterStartElement (w->writer, _xml ("properties"));
689               xmlTextWriterStartElement (w->writer, _xml ("property"));
690               write_attr (w->writer, "name", "QvBasicNum");
691               xmlTextWriterWriteFormatAttribute
692                 (w->writer, _xml ("value"), "%d", value);
693               write_attr (w->writer, "type", "3");
694               write_attr (w->writer, "context", "Analysis");
695               /* </property> */
696               xmlTextWriterEndElement (w->writer);
697               /* </properties> */
698               xmlTextWriterEndElement (w->writer);
699
700               xmlTextWriterStartElement (w->writer, _xml ("labels"));
701               write_attr (w->writer, "context", "LABEL");
702
703               xmlTextWriterStartElement (w->writer, _xml ("text"));
704               write_attr (w->writer, "context", "ANALYSIS");
705               write_xml_lang (w->writer);
706               xmlTextWriterWriteString (w->writer, _xml (var_get_label (subvar)));
707               /* </text> */
708               xmlTextWriterEndElement (w->writer);
709
710
711               /* </labels> */
712               xmlTextWriterEndElement (w->writer);
713               /* </category> */
714               xmlTextWriterEndElement (w->writer);
715             }
716
717           /* </categories> */
718           xmlTextWriterEndElement (w->writer);
719           /* </variable> */
720           xmlTextWriterEndElement (w->writer);
721         }
722       else
723         {
724           const struct variable *var = var_or_mrset.variable;
725           write_variable_section(w->writer, var, id++);
726         }
727     }
728   xmlTextWriterEndElement (w->writer); /* </definition> */
729
730   write_empty_element (w->writer, "system");
731   write_empty_element (w->writer, "systemrouting");
732   write_empty_element (w->writer, "mappings");
733
734   /* <design/> */
735   xmlTextWriterStartElement (w->writer, _xml ("design"));
736   xmlTextWriterStartElement (w->writer, _xml ("fields"));
737   write_attr (w->writer, "name", "@fields");
738   write_global_name_space (w->writer);
739   for (size_t i = 0; i < n_vars; i++)
740     {
741       const struct variable *var = dict_get_var (dict, i);
742       xmlTextWriterStartElement (w->writer, _xml ("variable"));
743       xmlTextWriterWriteFormatAttribute (w->writer, _xml ("id"),
744                                          "_%zu", i + 1);
745       write_attr (w->writer, "name", var_get_name (var));
746       xmlTextWriterWriteFormatAttribute (w->writer, _xml ("ref"),
747                                          "%zu", i + 1);
748       xmlTextWriterEndElement (w->writer);
749     }
750   write_empty_element (w->writer, "deleted");
751   xmlTextWriterEndElement (w->writer);
752   xmlTextWriterStartElement (w->writer, _xml ("types"));
753   write_attr (w->writer, "name", "@types");
754   write_global_name_space (w->writer);
755   write_empty_element (w->writer, "deleted");
756   xmlTextWriterEndElement (w->writer);
757   xmlTextWriterStartElement (w->writer, _xml ("pages"));
758   write_attr (w->writer, "name", "@pages");
759   write_global_name_space (w->writer);
760   write_empty_element (w->writer, "deleted");
761   xmlTextWriterEndElement (w->writer);
762   xmlTextWriterStartElement (w->writer, _xml ("routings"));
763   xmlTextWriterStartElement (w->writer, _xml ("scripts"));
764   write_empty_element (w->writer, "deleted");
765   xmlTextWriterEndElement (w->writer);
766   xmlTextWriterEndElement (w->writer);
767   xmlTextWriterEndElement (w->writer);
768
769   /* <languages/> */
770   /* XXX should use the real language */
771   xmlTextWriterStartElement (w->writer, _xml ("languages"));
772   write_attr (w->writer, "base", "EN-US");
773   xmlTextWriterStartElement (w->writer, _xml ("language"));
774   write_attr (w->writer, "name", "EN-US");
775   write_attr (w->writer, "id", "0409");
776   xmlTextWriterEndElement (w->writer);
777   write_empty_element (w->writer, "deleted");
778   xmlTextWriterEndElement (w->writer);
779
780   /* <contexts/> */
781   xmlTextWriterStartElement (w->writer, _xml ("contexts"));
782   write_attr (w->writer, "base", "Analysis");
783   write_context (w->writer, "ANALYSIS", "QUESTION");
784   write_context (w->writer, "QUESTION", "ANALYSIS");
785   write_context (w->writer, "WEBAPP", NULL);
786   write_empty_element (w->writer, "deleted");
787   xmlTextWriterEndElement (w->writer);
788
789   /* <labeltypes/> */
790   xmlTextWriterStartElement (w->writer, _xml ("labeltypes"));
791   write_attr (w->writer, "base", "label");
792   write_context (w->writer, "LABEL", NULL);
793   write_empty_element (w->writer, "deleted");
794   xmlTextWriterEndElement (w->writer);
795
796   /* <routingcontexts/> */
797   write_empty_element (w->writer, "routingcontexts");
798
799   /* <scripttypes/> */
800   xmlTextWriterStartElement (w->writer, _xml ("scripttypes"));
801   write_attr (w->writer, "base", "mrScriptBasic");
802   write_context (w->writer, "MRSCRIPTBASIC", NULL);
803   write_empty_element (w->writer, "deleted");
804   xmlTextWriterEndElement (w->writer);
805
806   /* <versionlist/> */
807   write_empty_element (w->writer, "versionlist");
808
809   /* <categorymap/> */
810   xmlTextWriterStartElement (w->writer, _xml ("categorymap"));
811   struct string_set categories = STRING_SET_INITIALIZER (categories);
812   for (size_t i = 0; i < n_vars; i++)
813     {
814       const struct variable *var = dict_get_var (dict, i);
815       const struct val_labs *val_labs = var_get_value_labels (var);
816       size_t n_vls = val_labs_count (val_labs);
817       if (n_vls)
818         {
819           const struct val_lab **vls = val_labs_sorted (val_labs);
820
821           for (size_t j = 0; j < n_vls; j++)
822             {
823               const struct val_lab *vl = vls[j];
824
825               char *label = name_to_id (val_lab_get_label (vl));
826               if (string_set_insert_nocopy (&categories, label))
827                 {
828                   xmlTextWriterStartElement (w->writer, _xml ("categoryid"));
829                   write_attr (w->writer, "name", label);
830                   xmlTextWriterWriteFormatAttribute (
831                     w->writer, _xml ("value"),
832                     "%zu", string_set_count (&categories));
833                   xmlTextWriterEndElement (w->writer);
834                 }
835             }
836
837           free (vls);
838         }
839     }
840   string_set_destroy (&categories);
841   xmlTextWriterEndElement (w->writer);
842
843   /* <savelogs/> */
844   xmlTextWriterStartElement (w->writer, _xml ("savelogs"));
845   xmlTextWriterStartElement (w->writer, _xml ("savelog"));
846   write_attr (w->writer, "fileversion", "7.0.0.0.331");
847   write_attr (w->writer, "versionset", "");
848   write_attr (w->writer, "username", "Administrator");
849   time_t t;
850   if (time (&t) == (time_t) -1)
851     write_attr (w->writer, "date", "01/01/1970 00:00:00 AM");
852   else
853     {
854       struct tm *tm = localtime (&t);
855       int hour = tm->tm_hour % 12;
856       xmlTextWriterWriteFormatAttribute (w->writer, _xml ("date"),
857                                          "%02d/%02d/%04d %02d:%02d:%02d %s",
858                                          tm->tm_mon + 1, tm->tm_mday,
859                                          tm->tm_year + 1900,
860                                          hour ? hour : 12, tm->tm_min,
861                                          tm->tm_sec,
862                                          tm->tm_hour < 12 ? "AM" : "PM");
863     }
864   xmlTextWriterStartElement (w->writer, _xml ("user"));
865   write_attr (w->writer, "name", "pspp");
866   write_attr (w->writer, "fileversion", version);
867   write_attr (w->writer, "comment", "Written by GNU PSPP");
868   xmlTextWriterEndElement (w->writer);
869   xmlTextWriterEndElement (w->writer);
870   xmlTextWriterEndElement (w->writer);
871
872   /* </xml> */
873   xmlTextWriterEndElement (w->writer);
874
875   xmlTextWriterEndDocument (w->writer);
876
877   free(var_or_mrset_array);
878
879 error:
880   mdd_close (w);
881   return NULL;
882 }