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 enum mv_class miss = var_is_value_missing (var, value);
410 write_attr (writer, "missing", miss == MV_USER ? "user" : "system");
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> */
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> */
435 xmlTextWriterEndElement (writer);
437 write_empty_element (writer, "deleted");
438 xmlTextWriterEndElement (writer); /* </categories> */
443 xmlTextWriterEndElement (writer);
447 mdd_write (struct file_handle *fh, struct dictionary *dict,
448 const char *sav_name)
450 struct mdd_writer *w = XZALLOC (struct mdd_writer);
451 size_t n_vars = dict_get_n_vars (dict);
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);
460 /* Create the file on disk. */
461 w->rf = replace_file_start (fh, "wb", 0444, &w->file);
464 msg (ME, _("Error opening `%s' for writing as a metadata file: %s."),
465 fh_get_file_name (fh), strerror (errno));
469 w->writer = xmlNewTextWriter (xmlOutputBufferCreateFile (w->file, NULL));
472 msg (ME, _("Internal error creating xmlTextWriter. "
473 "Please report this to %s."), PACKAGE_BUGREPORT);
477 xmlTextWriterStartDocument (w->writer, NULL, "UTF-8", NULL);
479 /* The MDD file contents seem to roughly correspond to the object model
481 https://support.unicomsi.com/manuals/intelligence/75/DDL/MDM/docjet/metadata/chm/contentsf.html. */
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);
489 xmlTextWriterStartElement (w->writer, _xml ("xml"));
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
501 const char *key, *value;
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" },
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));
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
522 xmlTextWriterEndElement (w->writer);
525 xmlTextWriterStartElement (w->writer, _xml ("datasources"));
526 xmlTextWriterWriteAttribute (w->writer, _xml ("default"), _xml ("mrSavDsc"));
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");
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;
539 short_names_assign (dict);
540 for (size_t i = 0; i < var_count; i++)
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)
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);
550 for (size_t subvar_idx = 0; subvar_idx < mrset->n_vars; subvar_idx++)
552 xmlTextWriterStartElement(w->writer, _xml ("subalias"));
553 xmlTextWriterWriteFormatAttribute (w->writer, _xml ("index"),
555 write_attr (w->writer, "name",
556 var_get_name (mrset->vars[subvar_idx]));
558 xmlTextWriterEndElement (w->writer); /* subalias */
563 const struct variable *var = var_or_mrset.variable;
565 char *short_name = xstrdup (var_get_short_name (var, 0));
566 for (char *p = short_name; *p; p++)
568 write_attr (w->writer, "fullname", short_name);
571 write_attr (w->writer, "aliasname", var_get_name (var));
573 const struct val_labs *val_labs = var_get_value_labels (var);
574 size_t n_vls = val_labs_count (val_labs);
577 const struct val_lab **vls = val_labs_sorted (val_labs);
579 xmlTextWriterStartElement (w->writer, _xml ("nativevalues"));
580 int width = var_get_width (var);
581 for (size_t j = 0; j < n_vls; j++)
583 const struct val_lab *vl = vls[j];
584 xmlTextWriterStartElement (w->writer, _xml ("nativevalue"));
586 char *fullname = name_to_id (val_lab_get_label (vl));
587 write_attr (w->writer, "fullname", fullname);
590 write_value_label_value (w->writer, vl, width);
591 xmlTextWriterEndElement (w->writer);
593 xmlTextWriterEndElement (w->writer);
599 xmlTextWriterEndElement (w->writer); /* var */
602 xmlTextWriterEndElement (w->writer); /* connection */
603 xmlTextWriterEndElement (w->writer); /* datasources */
605 /* If the dictionary has a label, record it here */
606 const char *file_label = dict_get_label (dict);
607 if (file_label != NULL)
609 xmlTextWriterStartElement (w->writer, _xml ("labels"));
610 write_attr (w->writer, "context", "LABEL");
611 xmlTextWriterStartElement (w->writer, _xml ("text"));
613 write_attr (w->writer, "context", "ANALYSIS");
614 write_xml_lang (w->writer);
615 xmlTextWriterWriteString (w->writer, _xml (file_label));
618 xmlTextWriterEndElement (w->writer);
621 xmlTextWriterEndElement (w->writer);
624 /* We reserve ids 1...N_VARS for variables and then start other ids after
626 int id = dict_get_n_vars (dict) + 1;
629 xmlTextWriterStartElement (w->writer, _xml ("definition"));
630 for (size_t i = 0; i < var_count; i++)
632 xmlTextWriterWriteFormatAttribute (w->writer, _xml ("id"), "%zu", i + 1);
633 const struct var_or_mrset var_or_mrset = var_or_mrset_array[i];
635 if (var_or_mrset.is_mrset)
637 const struct mrset *mrset = var_or_mrset.mrset;
638 xmlTextWriterStartElement (w->writer, _xml ("variable"));
639 write_attr (w->writer, "name", mrset->name);
641 /* Use the type of the first subvariable as the type of the MRSET? */
642 write_attr (w->writer, "type", "3");
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");
651 xmlTextWriterEndElement (w->writer);
653 xmlTextWriterEndElement (w->writer);
656 xmlTextWriterStartElement (w->writer, _xml ("labels"));
657 write_attr (w->writer, "context", "LABEL");
658 xmlTextWriterStartElement (w->writer, _xml ("text"));
660 write_attr (w->writer, "context", "ANALYSIS");
661 write_xml_lang (w->writer);
662 xmlTextWriterWriteString (w->writer, _xml (mrset->label));
665 xmlTextWriterEndElement (w->writer);
668 xmlTextWriterEndElement (w->writer);
670 xmlTextWriterStartElement (w->writer, _xml ("categories"));
671 write_attr (w->writer, "global-name-space", "-1");
672 write_empty_element (w->writer, "deleted");
674 /* Individual categories */
676 for (size_t var_idx = 0; var_idx < mrset->n_vars; ++var_idx)
678 const struct variable *subvar = mrset->vars[var_idx];
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);
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");
696 xmlTextWriterEndElement (w->writer);
698 xmlTextWriterEndElement (w->writer);
700 xmlTextWriterStartElement (w->writer, _xml ("labels"));
701 write_attr (w->writer, "context", "LABEL");
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)));
708 xmlTextWriterEndElement (w->writer);
712 xmlTextWriterEndElement (w->writer);
714 xmlTextWriterEndElement (w->writer);
718 xmlTextWriterEndElement (w->writer);
720 xmlTextWriterEndElement (w->writer);
724 const struct variable *var = var_or_mrset.variable;
725 write_variable_section(w->writer, var, id++);
728 xmlTextWriterEndElement (w->writer); /* </definition> */
730 write_empty_element (w->writer, "system");
731 write_empty_element (w->writer, "systemrouting");
732 write_empty_element (w->writer, "mappings");
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++)
741 const struct variable *var = dict_get_var (dict, i);
742 xmlTextWriterStartElement (w->writer, _xml ("variable"));
743 xmlTextWriterWriteFormatAttribute (w->writer, _xml ("id"),
745 write_attr (w->writer, "name", var_get_name (var));
746 xmlTextWriterWriteFormatAttribute (w->writer, _xml ("ref"),
748 xmlTextWriterEndElement (w->writer);
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);
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);
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);
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);
796 /* <routingcontexts/> */
797 write_empty_element (w->writer, "routingcontexts");
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);
807 write_empty_element (w->writer, "versionlist");
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++)
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);
819 const struct val_lab **vls = val_labs_sorted (val_labs);
821 for (size_t j = 0; j < n_vls; j++)
823 const struct val_lab *vl = vls[j];
825 char *label = name_to_id (val_lab_get_label (vl));
826 if (string_set_insert_nocopy (&categories, label))
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);
840 string_set_destroy (&categories);
841 xmlTextWriterEndElement (w->writer);
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");
850 if (time (&t) == (time_t) -1)
851 write_attr (w->writer, "date", "01/01/1970 00:00:00 AM");
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,
860 hour ? hour : 12, tm->tm_min,
862 tm->tm_hour < 12 ? "AM" : "PM");
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);
873 xmlTextWriterEndElement (w->writer);
875 xmlTextWriterEndDocument (w->writer);
877 free(var_or_mrset_array);