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/make-file.h"
31 #include "data/short-names.h"
32 #include "data/value-labels.h"
33 #include "data/variable.h"
34 #include "libpspp/message.h"
35 #include "libpspp/misc.h"
36 #include "libpspp/string-set.h"
37 #include "libpspp/version.h"
39 #include "gl/c-ctype.h"
40 #include "gl/ftoastr.h"
41 #include "gl/xalloc.h"
42 #include "gl/xmemdup0.h"
45 #define _(msgid) gettext (msgid)
46 #define N_(msgid) (msgid)
48 #define _xml(X) CHAR_CAST (const xmlChar *, X)
50 /* Metadata file writer. */
53 struct file_handle *fh; /* File handle. */
54 struct fh_lock *lock; /* Mutual exclusion for file. */
55 FILE *file; /* File stream. */
56 struct replace_file *rf; /* Ticket for replacing output file. */
58 xmlTextWriter *writer;
61 /* Returns true if an I/O error has occurred on WRITER, false otherwise. */
63 mdd_write_error (const struct mdd_writer *writer)
65 return ferror (writer->file);
69 mdd_close (struct mdd_writer *w)
75 xmlFreeTextWriter (w->writer);
82 ok = !mdd_write_error (w);
83 if (fclose (w->file) == EOF)
87 msg (ME, _("An I/O error occurred writing metadata file `%s'."),
88 fh_get_file_name (w->fh));
90 if (ok ? !replace_file_commit (w->rf) : !replace_file_abort (w->rf))
103 write_empty_element (xmlTextWriter *writer, const char *name)
105 xmlTextWriterStartElement (writer, _xml (name));
106 xmlTextWriterEndElement (writer);
110 write_attr (xmlTextWriter *writer, const char *key, const char *value)
112 xmlTextWriterWriteAttribute (writer, _xml (key), _xml (value));
116 write_global_name_space (xmlTextWriter *writer)
118 write_attr (writer, "global-name-space", "-1");
122 write_xml_lang (xmlTextWriter *writer)
124 /* XXX should write real language */
125 xmlTextWriterWriteAttributeNS (writer, _xml ("xml"), _xml ("lang"), NULL,
130 write_value_label_value (xmlTextWriter *writer, const struct val_lab *vl,
133 /* XXX below would better use syntax_gen_value(). */
134 const union value *value = val_lab_get_value (vl);
137 char *s = xmemdup0 (value_str (value, width), width);
138 xmlTextWriterWriteAttribute (writer, _xml ("value"), _xml (s));
143 char s[DBL_BUFSIZE_BOUND];
145 c_dtoastr (s, sizeof s, 0, 0, value->f);
146 xmlTextWriterWriteAttribute (writer, _xml ("value"), _xml (s));
151 write_context (xmlTextWriter *writer, const char *name,
152 const char *alternative)
154 xmlTextWriterStartElement (writer, _xml ("context"));
155 write_attr (writer, "name", name);
158 xmlTextWriterStartElement (writer, _xml ("alternatives"));
159 xmlTextWriterStartElement (writer, _xml ("alternative"));
160 write_attr (writer, "name", alternative);
161 xmlTextWriterEndElement (writer);
162 write_empty_element (writer, "deleted");
163 xmlTextWriterEndElement (writer);
165 xmlTextWriterEndElement (writer);
169 name_to_id (const char *name)
171 char *id = xmalloc (strlen (name) + 2);
173 for (const char *s = name; *s; s++)
176 *d++ = c_tolower (*s);
177 else if (c_isdigit (*s))
185 if (d == id || d[-1] != '_')
189 if (d > id && d[-1] == '_')
197 mdd_write (struct file_handle *fh, struct dictionary *dict,
198 const char *sav_name)
200 struct mdd_writer *w = xzalloc (sizeof *w);
202 /* Open file handle as an exclusive writer. */
203 /* TRANSLATORS: this fragment will be interpolated into
204 messages in fh_lock() that identify types of files. */
205 w->lock = fh_lock (fh, FH_REF_FILE, N_("metadata file"), FH_ACC_WRITE, true);
209 /* Create the file on disk. */
210 w->rf = replace_file_start (fh, "wb", 0444, &w->file);
213 msg (ME, _("Error opening `%s' for writing as a metadata file: %s."),
214 fh_get_file_name (fh), strerror (errno));
218 w->writer = xmlNewTextWriter (xmlOutputBufferCreateFile (w->file, NULL));
221 msg (ME, _("Internal error creating xmlTextWriter."));
225 xmlTextWriterStartDocument (w->writer, NULL, "UTF-8", NULL);
227 /* The MDD file contents seem to roughly correspond to the object model
229 https://support.unicomsi.com/manuals/intelligence/75/DDL/MDM/docjet/metadata/chm/contentsf.html. */
231 /* <?xml-stylesheet type="text/xsl" href="mdd.xslt"?> */
232 xmlTextWriterStartPI (w->writer, _xml ("xml-stylesheet"));
233 xmlTextWriterWriteString (w->writer,
234 _xml ("type=\"text/xsl\" href=\"mdd.xslt\""));
235 xmlTextWriterEndPI (w->writer);
237 xmlTextWriterStartElement (w->writer, _xml ("xml"));
239 /* <mdm:metadata xmlns:mdm="http://www.spss.com/mr/dm/metadatamodel/Arc
240 3/2000-02-04" mdm_createversion="7.0.0.0.331"
241 mdm_lastversion="7.0.0.0.331" id="c4c181c1-0d7c-42e3-abcd-f08296d1dfdc"
242 data_version="9" data_sub_version="1" systemvariable="0"
243 dbfiltervalidation="-1"> */
244 xmlTextWriterStartElementNS (
245 w->writer, _xml ("mdm"), _xml ("metadata"),
246 _xml ("http://www.spss.com/mr/dm/metadatamodel/Arc%203/2000-02-04"));
247 static const struct pair
249 const char *key, *value;
253 { "mdm_createversion", "7.0.0.0.331" },
254 { "mdm_lastversion", "7.0.0.0.331" },
255 { "id", "c4c181c1-0d7c-42e3-abcd-f08296d1dfdc" },
256 { "data_version", "9" },
257 { "data_sub_version", "1" },
258 { "systemvariable", "0" },
259 { "dbfiltervalidation", "-1" },
261 const int n_pairs = sizeof pairs / sizeof *pairs;
262 for (const struct pair *p = pairs; p < &pairs[n_pairs]; p++)
263 xmlTextWriterWriteAttribute (w->writer, _xml (p->key), _xml (p->value));
266 xmlTextWriterStartElement (w->writer, _xml ("atoms"));
267 /* XXX Real files contain a list of languages and a few other random strings
268 here in <atom name="..."/> elements. It's really not clear what they're
270 xmlTextWriterEndElement (w->writer);
273 xmlTextWriterStartElement (w->writer, _xml ("datasources"));
274 xmlTextWriterWriteAttribute (w->writer, _xml ("default"), _xml ("mrSavDsc"));
277 xmlTextWriterStartElement (w->writer, _xml ("connection"));
278 write_attr (w->writer, "name", "mrSavDsc");
279 write_attr (w->writer, "dblocation", sav_name);
280 write_attr (w->writer, "cdscname", "mrSavDsc");
281 write_attr (w->writer, "project", "126");
283 size_t n_vars = dict_get_var_cnt (dict);
284 short_names_assign (dict);
285 for (size_t i = 0; i < n_vars; i++)
287 const struct variable *var = dict_get_var (dict, i);
288 xmlTextWriterStartElement (w->writer, _xml ("var"));
290 char *short_name = xstrdup (var_get_short_name (var, 0));
291 for (char *p = short_name; *p; p++)
293 write_attr (w->writer, "fullname", short_name);
296 write_attr (w->writer, "aliasname", var_get_name (var));
298 const struct val_labs *val_labs = var_get_value_labels (var);
299 size_t n_vls = val_labs_count (val_labs);
302 const struct val_lab **vls = val_labs_sorted (val_labs);
304 xmlTextWriterStartElement (w->writer, _xml ("nativevalues"));
305 int width = var_get_width (var);
306 for (size_t j = 0; j < n_vls; j++)
308 const struct val_lab *vl = vls[j];
309 xmlTextWriterStartElement (w->writer, _xml ("nativevalue"));
311 char *fullname = name_to_id (val_lab_get_label (vl));
312 write_attr (w->writer, "fullname", fullname);
315 write_value_label_value (w->writer, vl, width);
316 xmlTextWriterEndElement (w->writer);
318 xmlTextWriterEndElement (w->writer);
323 xmlTextWriterEndElement (w->writer);
325 xmlTextWriterEndElement (w->writer);
326 xmlTextWriterEndElement (w->writer);
328 /* We reserve ids 1...N_VARS for variables and then start other ids after
330 int id = dict_get_var_cnt (dict) + 1;
333 xmlTextWriterStartElement (w->writer, _xml ("definition"));
334 for (size_t i = 0; i < n_vars; i++)
336 const struct variable *var = dict_get_var (dict, i);
337 xmlTextWriterStartElement (w->writer, _xml ("variable"));
338 xmlTextWriterWriteFormatAttribute (w->writer, _xml ("id"), "%zu", i + 1);
339 write_attr (w->writer, "name", var_get_name (var));
341 bool is_string = var_get_type (var) == VAL_STRING;
342 int type = is_string ? 2 : 3;
343 xmlTextWriterWriteFormatAttribute (w->writer, _xml ("type"), "%d", type);
345 int max = is_string ? var_get_width (var) : 1;
346 xmlTextWriterWriteFormatAttribute (w->writer, _xml ("max"), "%d", max);
348 write_attr (w->writer, "maxtype", "3");
350 const char *label = var_get_label (var);
353 xmlTextWriterStartElement (w->writer, _xml ("labels"));
354 write_attr (w->writer, "context", "LABEL");
356 xmlTextWriterStartElement (w->writer, _xml ("text"));
357 write_attr (w->writer, "context", "ANALYSIS");
358 write_xml_lang (w->writer);
359 xmlTextWriterWriteString (w->writer, _xml (label));
360 xmlTextWriterEndElement (w->writer);
362 xmlTextWriterEndElement (w->writer);
365 const struct val_labs *val_labs = var_get_value_labels (var);
366 size_t n_vls = val_labs_count (val_labs);
369 const struct val_lab **vls = val_labs_sorted (val_labs);
372 xmlTextWriterStartElement (w->writer, _xml ("categories"));
373 write_global_name_space (w->writer);
374 int width = var_get_width (var);
375 for (size_t j = 0; j < n_vls; j++)
377 const struct val_lab *vl = vls[j];
380 xmlTextWriterStartElement (w->writer, _xml ("category"));
381 xmlTextWriterWriteFormatAttribute (w->writer, _xml ("id"),
384 char *name = name_to_id (val_lab_get_label (vl));
385 write_attr (w->writer, "name", name);
389 xmlTextWriterStartElement (w->writer, _xml ("properties"));
390 xmlTextWriterStartElement (w->writer, _xml ("property"));
391 write_attr (w->writer, "name", "Value");
392 write_value_label_value (w->writer, vl, width);
393 write_attr (w->writer, "type", "5");
394 write_attr (w->writer, "context", "Analysis");
395 xmlTextWriterEndElement (w->writer);
396 xmlTextWriterEndElement (w->writer);
399 xmlTextWriterStartElement (w->writer, _xml ("labels"));
400 write_attr (w->writer, "context", "LABEL");
401 xmlTextWriterStartElement (w->writer, _xml ("text"));
402 write_attr (w->writer, "context", "ANALYSIS");
403 write_xml_lang (w->writer);
404 xmlTextWriterWriteString (w->writer,
405 _xml (val_lab_get_label (vl)));
406 xmlTextWriterEndElement (w->writer);
407 xmlTextWriterEndElement (w->writer);
411 xmlTextWriterEndElement (w->writer);
413 write_empty_element (w->writer, "deleted");
414 xmlTextWriterEndElement (w->writer);
419 xmlTextWriterEndElement (w->writer);
421 xmlTextWriterEndElement (w->writer);
423 write_empty_element (w->writer, "system");
424 write_empty_element (w->writer, "systemrouting");
425 write_empty_element (w->writer, "mappings");
428 xmlTextWriterStartElement (w->writer, _xml ("design"));
429 xmlTextWriterStartElement (w->writer, _xml ("fields"));
430 write_attr (w->writer, "name", "@fields");
431 write_global_name_space (w->writer);
432 for (size_t i = 0; i < n_vars; i++)
434 const struct variable *var = dict_get_var (dict, i);
435 xmlTextWriterStartElement (w->writer, _xml ("variable"));
436 xmlTextWriterWriteFormatAttribute (w->writer, _xml ("id"),
438 write_attr (w->writer, "name", var_get_name (var));
439 xmlTextWriterWriteFormatAttribute (w->writer, _xml ("ref"),
441 xmlTextWriterEndElement (w->writer);
443 write_empty_element (w->writer, "deleted");
444 xmlTextWriterEndElement (w->writer);
445 xmlTextWriterStartElement (w->writer, _xml ("types"));
446 write_attr (w->writer, "name", "@types");
447 write_global_name_space (w->writer);
448 write_empty_element (w->writer, "deleted");
449 xmlTextWriterEndElement (w->writer);
450 xmlTextWriterStartElement (w->writer, _xml ("pages"));
451 write_attr (w->writer, "name", "@pages");
452 write_global_name_space (w->writer);
453 write_empty_element (w->writer, "deleted");
454 xmlTextWriterEndElement (w->writer);
455 xmlTextWriterStartElement (w->writer, _xml ("routings"));
456 xmlTextWriterStartElement (w->writer, _xml ("scripts"));
457 write_empty_element (w->writer, "deleted");
458 xmlTextWriterEndElement (w->writer);
459 xmlTextWriterEndElement (w->writer);
460 xmlTextWriterEndElement (w->writer);
463 /* XXX should use the real language */
464 xmlTextWriterStartElement (w->writer, _xml ("languages"));
465 write_attr (w->writer, "base", "EN-US");
466 xmlTextWriterStartElement (w->writer, _xml ("language"));
467 write_attr (w->writer, "name", "EN-US");
468 write_attr (w->writer, "id", "0409");
469 xmlTextWriterEndElement (w->writer);
470 write_empty_element (w->writer, "deleted");
471 xmlTextWriterEndElement (w->writer);
474 xmlTextWriterStartElement (w->writer, _xml ("contexts"));
475 write_attr (w->writer, "base", "Analysis");
476 write_context (w->writer, "ANALYSIS", "QUESTION");
477 write_context (w->writer, "QUESTION", "ANALYSIS");
478 write_context (w->writer, "WEBAPP", NULL);
479 write_empty_element (w->writer, "deleted");
480 xmlTextWriterEndElement (w->writer);
483 xmlTextWriterStartElement (w->writer, _xml ("labeltypes"));
484 write_attr (w->writer, "base", "label");
485 write_context (w->writer, "LABEL", NULL);
486 write_empty_element (w->writer, "deleted");
487 xmlTextWriterEndElement (w->writer);
489 /* <routingcontexts/> */
490 write_empty_element (w->writer, "routingcontexts");
493 xmlTextWriterStartElement (w->writer, _xml ("scripttypes"));
494 write_attr (w->writer, "base", "mrScriptBasic");
495 write_context (w->writer, "MRSCRIPTBASIC", NULL);
496 write_empty_element (w->writer, "deleted");
497 xmlTextWriterEndElement (w->writer);
500 write_empty_element (w->writer, "versionlist");
503 xmlTextWriterStartElement (w->writer, _xml ("categorymap"));
504 struct string_set categories = STRING_SET_INITIALIZER (categories);
505 for (size_t i = 0; i < n_vars; i++)
507 const struct variable *var = dict_get_var (dict, i);
508 const struct val_labs *val_labs = var_get_value_labels (var);
509 size_t n_vls = val_labs_count (val_labs);
512 const struct val_lab **vls = val_labs_sorted (val_labs);
514 for (size_t j = 0; j < n_vls; j++)
516 const struct val_lab *vl = vls[j];
518 char *label = name_to_id (val_lab_get_label (vl));
519 if (string_set_insert_nocopy (&categories, label))
521 xmlTextWriterStartElement (w->writer, _xml ("categoryid"));
522 write_attr (w->writer, "name", label);
523 xmlTextWriterWriteFormatAttribute (
524 w->writer, _xml ("value"),
525 "%zu", string_set_count (&categories));
526 xmlTextWriterEndElement (w->writer);
533 string_set_destroy (&categories);
534 xmlTextWriterEndElement (w->writer);
537 xmlTextWriterStartElement (w->writer, _xml ("savelogs"));
538 xmlTextWriterStartElement (w->writer, _xml ("savelog"));
539 write_attr (w->writer, "fileversion", "7.0.0.0.331");
540 write_attr (w->writer, "versionset", "");
541 write_attr (w->writer, "username", "Administrator");
543 if (time (&t) == (time_t) -1)
544 write_attr (w->writer, "date", "01/01/1970 00:00:00 AM");
547 struct tm *tm = localtime (&t);
548 int hour = tm->tm_hour % 12;
549 xmlTextWriterWriteFormatAttribute (w->writer, _xml ("date"),
550 "%02d/%02d/%04d %02d:%02d:%02d %s",
551 tm->tm_mon + 1, tm->tm_mday,
553 hour ? hour : 12, tm->tm_min,
555 tm->tm_hour < 12 ? "AM" : "PM");
557 xmlTextWriterStartElement (w->writer, _xml ("user"));
558 write_attr (w->writer, "name", "pspp");
559 write_attr (w->writer, "fileversion", version);
560 write_attr (w->writer, "comment", "Written by GNU PSPP");
561 xmlTextWriterEndElement (w->writer);
562 xmlTextWriterEndElement (w->writer);
563 xmlTextWriterEndElement (w->writer);
566 xmlTextWriterEndElement (w->writer);
568 xmlTextWriterEndDocument (w->writer);