1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2018 Free Software Foundation, Inc.
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.
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.
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/>. */
19 #include "data/mdd-writer.h"
22 #include <libxml/xmlwriter.h>
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"
43 #include "gl/c-ctype.h"
44 #include "gl/ftoastr.h"
45 #include "gl/xalloc.h"
46 #include "gl/xmemdup0.h"
49 #define _(msgid) gettext (msgid)
50 #define N_(msgid) (msgid)
52 #define _xml(X) CHAR_CAST (const xmlChar *, X)
58 VAL_CATEGORICAL_TYPE = 3,
59 VAL_DATETIME_TYPE = 5,
63 /* Get the numeric type of the variable. */
64 static enum val_numeric_type
65 var_get_numeric_type_ (const struct variable *var)
67 const struct fmt_spec *print = var_get_print_format (var);
68 if (var_get_type (var) == VAL_STRING)
69 return VAL_STRING_TYPE;
71 if (var_has_value_labels (var))
72 return VAL_CATEGORICAL_TYPE;
75 return VAL_DECIMAL_TYPE;
77 if (print->type == FMT_DATETIME)
78 return VAL_DATETIME_TYPE;
80 if (print->type == FMT_F)
81 return VAL_INTEGER_TYPE;
83 return VAL_CATEGORICAL_TYPE;
86 /* Metadata file writer. */
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. */
94 xmlTextWriter *writer;
97 /* Either a variable or a multi-response set. */
100 /* If true, the union contains a multi-response set. Otherwise, it
101 contains a variable. */
105 const struct mrset *mrset;
106 const struct variable *variable;
110 struct all_dict_variables
112 struct var_or_mrset *vars;
116 struct all_dict_variables
117 all_variables (struct dictionary *dict);
119 /* Extract all variables in a dictionary, both normal and multi.
121 Excludes variables which are subvariables of an MRSET. */
122 struct all_dict_variables
123 all_variables (struct dictionary *dict)
125 size_t n_vars = dict_get_n_vars (dict);
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)
131 const struct variable *var = dict_get_var (dict, i);
132 string_set_insert (&var_names, var_get_name (var));
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++)
139 const struct mrset *mrset = dict_get_mrset (dict, set_idx);
140 for (size_t i = 0; i < mrset->n_vars; ++i)
142 const struct variable *var = mrset->vars[i];
143 string_set_delete (&var_names, var_get_name (var));
147 /* Add the number of remaining variables to the number of MRSets. */
148 size_t var_count = n_sets + string_set_count (&var_names);
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);
154 /* Fill the array. */
155 struct string_set added_mrsets = STRING_SET_INITIALIZER (added_mrsets);
158 for (size_t i = 0; i < n_vars; ++i)
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++)
164 const struct mrset *mrset = dict_get_mrset (dict, set_idx);
165 for (size_t i = 0; i < mrset->n_vars; ++i)
167 const struct variable *subvar = mrset->vars[i];
168 if (!strcmp (var_get_name (var), var_get_name (subvar)))
170 /* Then this variable is a member of this MRSet. */
171 found_in_mrset = true;
173 /* Check if this MRSet has already been added and add
175 if (!string_set_contains (&added_mrsets, mrset->name))
177 string_set_insert (&added_mrsets, mrset->name);
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;
189 /* If the variable wasn't found to be a member of any MRSets, record it as
190 a normal variable. */
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;
201 /* Ensure that we filled up the array. */
202 assert (var_idx == var_count);
205 string_set_destroy (&added_mrsets);
206 string_set_destroy (&var_names);
208 struct all_dict_variables result;
209 result.vars = var_or_mrset_array;
210 result.count = var_count;
215 /* Returns true if an I/O error has occurred on WRITER, false otherwise. */
217 mdd_write_error (const struct mdd_writer *writer)
219 return ferror (writer->file);
223 mdd_close (struct mdd_writer *w)
229 xmlFreeTextWriter (w->writer);
236 ok = !mdd_write_error (w);
237 if (fclose (w->file) == EOF)
241 msg (ME, _("An I/O error occurred writing metadata file `%s'."),
242 fh_get_file_name (w->fh));
244 if (ok ? !replace_file_commit (w->rf) : !replace_file_abort (w->rf))
257 write_empty_element (xmlTextWriter *writer, const char *name)
259 xmlTextWriterStartElement (writer, _xml (name));
260 xmlTextWriterEndElement (writer);
264 write_attr (xmlTextWriter *writer, const char *key, const char *value)
266 xmlTextWriterWriteAttribute (writer, _xml (key), _xml (value));
270 write_global_name_space (xmlTextWriter *writer)
272 write_attr (writer, "global-name-space", "-1");
276 write_xml_lang (xmlTextWriter *writer)
278 /* XXX should write real language */
279 xmlTextWriterWriteAttributeNS (writer, _xml ("xml"), _xml ("lang"), NULL,
284 write_value_label_value (xmlTextWriter *writer, const struct val_lab *vl,
287 /* XXX below would better use syntax_gen_value(). */
288 const union value *value = val_lab_get_value (vl);
291 char *s = xmemdup0 (value->s, width);
292 xmlTextWriterWriteAttribute (writer, _xml ("value"), _xml (s));
297 char s[DBL_BUFSIZE_BOUND];
299 c_dtoastr (s, sizeof s, 0, 0, value->f);
300 xmlTextWriterWriteAttribute (writer, _xml ("value"), _xml (s));
305 write_context (xmlTextWriter *writer, const char *name,
306 const char *alternative)
308 xmlTextWriterStartElement (writer, _xml ("context"));
309 write_attr (writer, "name", name);
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);
319 xmlTextWriterEndElement (writer);
323 name_to_id (const char *name)
325 char *id = xmalloc (strlen (name) + 2);
327 for (const char *s = name; *s; s++)
330 *d++ = c_tolower (*s);
331 else if (c_isdigit (*s))
339 if (d == id || d[-1] != '_')
343 if (d > id && d[-1] == '_')
351 write_variable_section (xmlTextWriter *writer, const struct variable *var, int id)
353 xmlTextWriterStartElement (writer, _xml ("variable"));
354 write_attr (writer, "name", var_get_name (var));
356 bool is_string = var_get_type (var) == VAL_STRING;
358 int type = var_get_numeric_type_ (var);
359 xmlTextWriterWriteFormatAttribute (writer, _xml ("type"), "%d", type);
361 int max = is_string ? var_get_width (var) : 1;
362 xmlTextWriterWriteFormatAttribute (writer, _xml ("max"), "%d", max);
364 write_attr (writer, "maxtype", "3");
366 const char *label = var_get_label (var);
369 xmlTextWriterStartElement (writer, _xml ("labels"));
370 write_attr (writer, "context", "LABEL");
372 xmlTextWriterStartElement (writer, _xml ("text"));
373 write_attr (writer, "context", "ANALYSIS");
374 write_xml_lang (writer);
375 xmlTextWriterWriteString (writer, _xml (label));
376 xmlTextWriterEndElement (writer);
378 xmlTextWriterEndElement (writer);
381 const struct val_labs *val_labs = var_get_value_labels (var);
382 size_t n_vls = val_labs_count (val_labs);
385 const struct val_lab **vls = val_labs_sorted (val_labs);
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++)
393 const struct val_lab *vl = vls[j];
394 const union value *value = val_lab_get_value (vl);
397 xmlTextWriterStartElement (writer, _xml ("category"));
398 xmlTextWriterWriteFormatAttribute (writer, _xml ("id"), "_%d", id);
400 char *name = name_to_id (val_lab_get_label (vl));
401 write_attr (writer, "name", name);
404 /* If the value here is missing, annotate it.
406 XXX only checking "user" here because not sure of correct other
408 if (var_is_value_missing (var, value, MV_USER))
409 write_attr (writer, "missing", "user");
410 else if (var_is_value_missing (var, value, MV_SYSTEM))
411 write_attr (writer, "missing", "system");
414 xmlTextWriterStartElement (writer, _xml ("properties"));
415 xmlTextWriterStartElement (writer, _xml ("property"));
416 write_attr (writer, "name", "Value");
417 write_value_label_value (writer, vl, width);
418 write_attr (writer, "type", "5");
419 write_attr (writer, "context", "Analysis");
420 xmlTextWriterEndElement (writer); /* </property> */
421 xmlTextWriterEndElement (writer); /* </properties> */
424 xmlTextWriterStartElement (writer, _xml ("labels"));
425 write_attr (writer, "context", "LABEL");
426 xmlTextWriterStartElement (writer, _xml ("text"));
427 write_attr (writer, "context", "ANALYSIS");
428 write_xml_lang (writer);
429 xmlTextWriterWriteString (writer,
430 _xml (val_lab_get_label (vl)));
431 xmlTextWriterEndElement (writer); /* </text> */
432 xmlTextWriterEndElement (writer); /* </labels> */
436 xmlTextWriterEndElement (writer);
438 write_empty_element (writer, "deleted");
439 xmlTextWriterEndElement (writer); /* </categories> */
444 xmlTextWriterEndElement (writer);
448 mdd_write (struct file_handle *fh, struct dictionary *dict,
449 const char *sav_name)
451 struct mdd_writer *w = XZALLOC (struct mdd_writer);
452 size_t n_vars = dict_get_n_vars (dict);
454 /* Open file handle as an exclusive writer. */
455 /* TRANSLATORS: this fragment will be interpolated into
456 messages in fh_lock() that identify types of files. */
457 w->lock = fh_lock (fh, FH_REF_FILE, N_("metadata file"), FH_ACC_WRITE, true);
461 /* Create the file on disk. */
462 w->rf = replace_file_start (fh, "wb", 0444, &w->file);
465 msg (ME, _("Error opening `%s' for writing as a metadata file: %s."),
466 fh_get_file_name (fh), strerror (errno));
470 w->writer = xmlNewTextWriter (xmlOutputBufferCreateFile (w->file, NULL));
473 msg (ME, _("Internal error creating xmlTextWriter. "
474 "Please report this to %s."), PACKAGE_BUGREPORT);
478 xmlTextWriterStartDocument (w->writer, NULL, "UTF-8", NULL);
480 /* The MDD file contents seem to roughly correspond to the object model
482 https://support.unicomsi.com/manuals/intelligence/75/DDL/MDM/docjet/metadata/chm/contentsf.html. */
484 /* <?xml-stylesheet type="text/xsl" href="mdd.xslt"?> */
485 xmlTextWriterStartPI (w->writer, _xml ("xml-stylesheet"));
486 xmlTextWriterWriteString (w->writer,
487 _xml ("type=\"text/xsl\" href=\"mdd.xslt\""));
488 xmlTextWriterEndPI (w->writer);
490 xmlTextWriterStartElement (w->writer, _xml ("xml"));
492 /* <mdm:metadata xmlns:mdm="http://www.spss.com/mr/dm/metadatamodel/Arc
493 3/2000-02-04" mdm_createversion="7.0.0.0.331"
494 mdm_lastversion="7.0.0.0.331" id="c4c181c1-0d7c-42e3-abcd-f08296d1dfdc"
495 data_version="9" data_sub_version="1" systemvariable="0"
496 dbfiltervalidation="-1"> */
497 xmlTextWriterStartElementNS (
498 w->writer, _xml ("mdm"), _xml ("metadata"),
499 _xml ("http://www.spss.com/mr/dm/metadatamodel/Arc%203/2000-02-04"));
500 static const struct pair
502 const char *key, *value;
506 { "mdm_createversion", "7.0.0.0.331" },
507 { "mdm_lastversion", "7.0.0.0.331" },
508 { "id", "c4c181c1-0d7c-42e3-abcd-f08296d1dfdc" },
509 { "data_version", "9" },
510 { "data_sub_version", "1" },
511 { "systemvariable", "0" },
512 { "dbfiltervalidation", "-1" },
514 const int n_pairs = sizeof pairs / sizeof *pairs;
515 for (const struct pair *p = pairs; p < &pairs[n_pairs]; p++)
516 xmlTextWriterWriteAttribute (w->writer, _xml (p->key), _xml (p->value));
519 xmlTextWriterStartElement (w->writer, _xml ("atoms"));
520 /* XXX Real files contain a list of languages and a few other random strings
521 here in <atom name="..."/> elements. It's really not clear what they're
523 xmlTextWriterEndElement (w->writer);
526 xmlTextWriterStartElement (w->writer, _xml ("datasources"));
527 xmlTextWriterWriteAttribute (w->writer, _xml ("default"), _xml ("mrSavDsc"));
530 xmlTextWriterStartElement (w->writer, _xml ("connection"));
531 write_attr (w->writer, "name", "mrSavDsc");
532 write_attr (w->writer, "dblocation", sav_name);
533 write_attr (w->writer, "cdscname", "mrSavDsc");
534 write_attr (w->writer, "project", "126");
536 struct all_dict_variables allvars = all_variables (dict);
537 struct var_or_mrset *var_or_mrset_array = allvars.vars;
538 size_t var_count = allvars.count;
540 short_names_assign (dict);
541 for (size_t i = 0; i < var_count; i++)
543 const struct var_or_mrset var_or_mrset = var_or_mrset_array[i];
544 xmlTextWriterStartElement (w->writer, _xml ("var"));
545 if (var_or_mrset.is_mrset)
547 const struct mrset *mrset = var_or_mrset.mrset;
548 write_attr (w->writer, "fullname", mrset->name + 1);
549 write_attr (w->writer, "aliasname", mrset->name);
551 for (size_t subvar_idx = 0; subvar_idx < mrset->n_vars; subvar_idx++)
553 xmlTextWriterStartElement(w->writer, _xml ("subalias"));
554 xmlTextWriterWriteFormatAttribute (w->writer, _xml ("index"),
556 write_attr (w->writer, "name",
557 var_get_name (mrset->vars[subvar_idx]));
559 xmlTextWriterEndElement (w->writer); /* subalias */
564 const struct variable *var = var_or_mrset.variable;
566 char *short_name = xstrdup (var_get_short_name (var, 0));
567 for (char *p = short_name; *p; p++)
569 write_attr (w->writer, "fullname", short_name);
572 write_attr (w->writer, "aliasname", var_get_name (var));
574 const struct val_labs *val_labs = var_get_value_labels (var);
575 size_t n_vls = val_labs_count (val_labs);
578 const struct val_lab **vls = val_labs_sorted (val_labs);
580 xmlTextWriterStartElement (w->writer, _xml ("nativevalues"));
581 int width = var_get_width (var);
582 for (size_t j = 0; j < n_vls; j++)
584 const struct val_lab *vl = vls[j];
585 xmlTextWriterStartElement (w->writer, _xml ("nativevalue"));
587 char *fullname = name_to_id (val_lab_get_label (vl));
588 write_attr (w->writer, "fullname", fullname);
591 write_value_label_value (w->writer, vl, width);
592 xmlTextWriterEndElement (w->writer);
594 xmlTextWriterEndElement (w->writer);
600 xmlTextWriterEndElement (w->writer); /* var */
603 xmlTextWriterEndElement (w->writer); /* connection */
604 xmlTextWriterEndElement (w->writer); /* datasources */
606 /* If the dictionary has a label, record it here */
607 const char *file_label = dict_get_label (dict);
608 if (file_label != NULL)
610 xmlTextWriterStartElement (w->writer, _xml ("labels"));
611 write_attr (w->writer, "context", "LABEL");
612 xmlTextWriterStartElement (w->writer, _xml ("text"));
614 write_attr (w->writer, "context", "ANALYSIS");
615 write_xml_lang (w->writer);
616 xmlTextWriterWriteString (w->writer, _xml (file_label));
619 xmlTextWriterEndElement (w->writer);
622 xmlTextWriterEndElement (w->writer);
625 /* We reserve ids 1...N_VARS for variables and then start other ids after
627 int id = dict_get_n_vars (dict) + 1;
630 xmlTextWriterStartElement (w->writer, _xml ("definition"));
631 for (size_t i = 0; i < var_count; i++)
633 xmlTextWriterWriteFormatAttribute (w->writer, _xml ("id"), "%zu", i + 1);
634 const struct var_or_mrset var_or_mrset = var_or_mrset_array[i];
636 if (var_or_mrset.is_mrset)
638 const struct mrset *mrset = var_or_mrset.mrset;
639 xmlTextWriterStartElement (w->writer, _xml ("variable"));
640 write_attr (w->writer, "name", mrset->name);
642 /* Use the type of the first subvariable as the type of the MRSET? */
643 write_attr (w->writer, "type", "3");
645 xmlTextWriterStartElement (w->writer, _xml ("properties"));
646 xmlTextWriterStartElement (w->writer, _xml ("property"));
647 write_attr (w->writer, "name", "QvLabel");
648 write_attr (w->writer, "value", mrset->name);
649 write_attr (w->writer, "type", "8");
650 write_attr (w->writer, "context", "Analysis");
652 xmlTextWriterEndElement (w->writer);
654 xmlTextWriterEndElement (w->writer);
657 xmlTextWriterStartElement (w->writer, _xml ("labels"));
658 write_attr (w->writer, "context", "LABEL");
659 xmlTextWriterStartElement (w->writer, _xml ("text"));
661 write_attr (w->writer, "context", "ANALYSIS");
662 write_xml_lang (w->writer);
663 xmlTextWriterWriteString (w->writer, _xml (mrset->label));
666 xmlTextWriterEndElement (w->writer);
669 xmlTextWriterEndElement (w->writer);
671 xmlTextWriterStartElement (w->writer, _xml ("categories"));
672 write_attr (w->writer, "global-name-space", "-1");
673 write_empty_element (w->writer, "deleted");
675 /* Individual categories */
677 for (size_t var_idx = 0; var_idx < mrset->n_vars; ++var_idx)
679 const struct variable *subvar = mrset->vars[var_idx];
681 xmlTextWriterStartElement (w->writer, _xml ("category"));
682 write_attr (w->writer, "context", "LABEL");
683 char *name_without_spaces = strdup (var_get_name (subvar));
684 for (size_t i = 0; name_without_spaces[i]; ++i)
685 if (name_without_spaces[i] == ' ') name_without_spaces[i] = '_';
686 write_attr (w->writer, "name", name_without_spaces);
687 free (name_without_spaces);
689 xmlTextWriterStartElement (w->writer, _xml ("properties"));
690 xmlTextWriterStartElement (w->writer, _xml ("property"));
691 write_attr (w->writer, "name", "QvBasicNum");
692 xmlTextWriterWriteFormatAttribute
693 (w->writer, _xml ("value"), "%d", value);
694 write_attr (w->writer, "type", "3");
695 write_attr (w->writer, "context", "Analysis");
697 xmlTextWriterEndElement (w->writer);
699 xmlTextWriterEndElement (w->writer);
701 xmlTextWriterStartElement (w->writer, _xml ("labels"));
702 write_attr (w->writer, "context", "LABEL");
704 xmlTextWriterStartElement (w->writer, _xml ("text"));
705 write_attr (w->writer, "context", "ANALYSIS");
706 write_xml_lang (w->writer);
707 xmlTextWriterWriteString (w->writer, _xml (var_get_label (subvar)));
709 xmlTextWriterEndElement (w->writer);
713 xmlTextWriterEndElement (w->writer);
715 xmlTextWriterEndElement (w->writer);
719 xmlTextWriterEndElement (w->writer);
721 xmlTextWriterEndElement (w->writer);
725 const struct variable *var = var_or_mrset.variable;
726 write_variable_section(w->writer, var, id++);
729 xmlTextWriterEndElement (w->writer); /* </definition> */
731 write_empty_element (w->writer, "system");
732 write_empty_element (w->writer, "systemrouting");
733 write_empty_element (w->writer, "mappings");
736 xmlTextWriterStartElement (w->writer, _xml ("design"));
737 xmlTextWriterStartElement (w->writer, _xml ("fields"));
738 write_attr (w->writer, "name", "@fields");
739 write_global_name_space (w->writer);
740 for (size_t i = 0; i < n_vars; i++)
742 const struct variable *var = dict_get_var (dict, i);
743 xmlTextWriterStartElement (w->writer, _xml ("variable"));
744 xmlTextWriterWriteFormatAttribute (w->writer, _xml ("id"),
746 write_attr (w->writer, "name", var_get_name (var));
747 xmlTextWriterWriteFormatAttribute (w->writer, _xml ("ref"),
749 xmlTextWriterEndElement (w->writer);
751 write_empty_element (w->writer, "deleted");
752 xmlTextWriterEndElement (w->writer);
753 xmlTextWriterStartElement (w->writer, _xml ("types"));
754 write_attr (w->writer, "name", "@types");
755 write_global_name_space (w->writer);
756 write_empty_element (w->writer, "deleted");
757 xmlTextWriterEndElement (w->writer);
758 xmlTextWriterStartElement (w->writer, _xml ("pages"));
759 write_attr (w->writer, "name", "@pages");
760 write_global_name_space (w->writer);
761 write_empty_element (w->writer, "deleted");
762 xmlTextWriterEndElement (w->writer);
763 xmlTextWriterStartElement (w->writer, _xml ("routings"));
764 xmlTextWriterStartElement (w->writer, _xml ("scripts"));
765 write_empty_element (w->writer, "deleted");
766 xmlTextWriterEndElement (w->writer);
767 xmlTextWriterEndElement (w->writer);
768 xmlTextWriterEndElement (w->writer);
771 /* XXX should use the real language */
772 xmlTextWriterStartElement (w->writer, _xml ("languages"));
773 write_attr (w->writer, "base", "EN-US");
774 xmlTextWriterStartElement (w->writer, _xml ("language"));
775 write_attr (w->writer, "name", "EN-US");
776 write_attr (w->writer, "id", "0409");
777 xmlTextWriterEndElement (w->writer);
778 write_empty_element (w->writer, "deleted");
779 xmlTextWriterEndElement (w->writer);
782 xmlTextWriterStartElement (w->writer, _xml ("contexts"));
783 write_attr (w->writer, "base", "Analysis");
784 write_context (w->writer, "ANALYSIS", "QUESTION");
785 write_context (w->writer, "QUESTION", "ANALYSIS");
786 write_context (w->writer, "WEBAPP", NULL);
787 write_empty_element (w->writer, "deleted");
788 xmlTextWriterEndElement (w->writer);
791 xmlTextWriterStartElement (w->writer, _xml ("labeltypes"));
792 write_attr (w->writer, "base", "label");
793 write_context (w->writer, "LABEL", NULL);
794 write_empty_element (w->writer, "deleted");
795 xmlTextWriterEndElement (w->writer);
797 /* <routingcontexts/> */
798 write_empty_element (w->writer, "routingcontexts");
801 xmlTextWriterStartElement (w->writer, _xml ("scripttypes"));
802 write_attr (w->writer, "base", "mrScriptBasic");
803 write_context (w->writer, "MRSCRIPTBASIC", NULL);
804 write_empty_element (w->writer, "deleted");
805 xmlTextWriterEndElement (w->writer);
808 write_empty_element (w->writer, "versionlist");
811 xmlTextWriterStartElement (w->writer, _xml ("categorymap"));
812 struct string_set categories = STRING_SET_INITIALIZER (categories);
813 for (size_t i = 0; i < n_vars; i++)
815 const struct variable *var = dict_get_var (dict, i);
816 const struct val_labs *val_labs = var_get_value_labels (var);
817 size_t n_vls = val_labs_count (val_labs);
820 const struct val_lab **vls = val_labs_sorted (val_labs);
822 for (size_t j = 0; j < n_vls; j++)
824 const struct val_lab *vl = vls[j];
826 char *label = name_to_id (val_lab_get_label (vl));
827 if (string_set_insert_nocopy (&categories, label))
829 xmlTextWriterStartElement (w->writer, _xml ("categoryid"));
830 write_attr (w->writer, "name", label);
831 xmlTextWriterWriteFormatAttribute (
832 w->writer, _xml ("value"),
833 "%zu", string_set_count (&categories));
834 xmlTextWriterEndElement (w->writer);
841 string_set_destroy (&categories);
842 xmlTextWriterEndElement (w->writer);
845 xmlTextWriterStartElement (w->writer, _xml ("savelogs"));
846 xmlTextWriterStartElement (w->writer, _xml ("savelog"));
847 write_attr (w->writer, "fileversion", "7.0.0.0.331");
848 write_attr (w->writer, "versionset", "");
849 write_attr (w->writer, "username", "Administrator");
851 if (time (&t) == (time_t) -1)
852 write_attr (w->writer, "date", "01/01/1970 00:00:00 AM");
855 struct tm *tm = localtime (&t);
856 int hour = tm->tm_hour % 12;
857 xmlTextWriterWriteFormatAttribute (w->writer, _xml ("date"),
858 "%02d/%02d/%04d %02d:%02d:%02d %s",
859 tm->tm_mon + 1, tm->tm_mday,
861 hour ? hour : 12, tm->tm_min,
863 tm->tm_hour < 12 ? "AM" : "PM");
865 xmlTextWriterStartElement (w->writer, _xml ("user"));
866 write_attr (w->writer, "name", "pspp");
867 write_attr (w->writer, "fileversion", version);
868 write_attr (w->writer, "comment", "Written by GNU PSPP");
869 xmlTextWriterEndElement (w->writer);
870 xmlTextWriterEndElement (w->writer);
871 xmlTextWriterEndElement (w->writer);
874 xmlTextWriterEndElement (w->writer);
876 xmlTextWriterEndDocument (w->writer);
878 free(var_or_mrset_array);