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 "output/spv/spvxml-helpers.h"
25 #include "libpspp/cast.h"
26 #include "libpspp/compiler.h"
27 #include "libpspp/hash-functions.h"
28 #include "libpspp/str.h"
29 #include "output/options.h"
30 #include "output/table.h"
32 #include "gl/xvasprintf.h"
34 char * WARN_UNUSED_RESULT
35 spvxml_context_finish (struct spvxml_context *ctx, struct spvxml_node *root)
38 root->class_->spvxml_node_collect_ids (ctx, root);
40 root->class_->spvxml_node_resolve_refs (ctx, root);
42 hmap_destroy (&ctx->id_map);
48 spvxml_node_context_uninit (struct spvxml_node_context *nctx)
50 for (struct spvxml_attribute *a = nctx->attrs;
51 a < &nctx->attrs[nctx->n_attrs]; a++)
56 xml_element_type_to_string (xmlElementType type)
60 case XML_ELEMENT_NODE: return "element";
61 case XML_ATTRIBUTE_NODE: return "attribute";
62 case XML_TEXT_NODE: return "text";
63 case XML_CDATA_SECTION_NODE: return "CDATA section";
64 case XML_ENTITY_REF_NODE: return "entity reference";
65 case XML_ENTITY_NODE: return "entity";
66 case XML_PI_NODE: return "PI";
67 case XML_COMMENT_NODE: return "comment";
68 case XML_DOCUMENT_NODE: return "document";
69 case XML_DOCUMENT_TYPE_NODE: return "document type";
70 case XML_DOCUMENT_FRAG_NODE: return "document fragment";
71 case XML_NOTATION_NODE: return "notation";
72 case XML_HTML_DOCUMENT_NODE: return "HTML document";
73 case XML_DTD_NODE: return "DTD";
74 case XML_ELEMENT_DECL: return "element declaration";
75 case XML_ATTRIBUTE_DECL: return "attribute declaration";
76 case XML_ENTITY_DECL: return "entity declaration";
77 case XML_NAMESPACE_DECL: return "namespace declaration";
78 case XML_XINCLUDE_START: return "XInclude start";
79 case XML_XINCLUDE_END: return "XInclude end";
81 #ifndef XML_DOCB_DOCUMENT_NODE
82 /* libxml2 removed this value from the enum and redefined it as a macro
83 for backward compatibility. When it's not in the enum, it's best not
84 to have a 'case' for it because that provokes a compiler warning. */
85 case XML_DOCB_DOCUMENT_NODE: return "docb document";
87 default: return "<error>";
92 spvxml_format_node_path (const xmlNode *node, struct string *s)
94 enum { MAX_STACK = 32 };
95 const xmlNode *stack[MAX_STACK];
98 while (node != NULL && node->type != XML_DOCUMENT_NODE && n < MAX_STACK)
107 ds_put_byte (s, '/');
109 ds_put_cstr (s, CHAR_CAST (char *, node->name));
110 if (node->type == XML_ELEMENT_NODE)
116 for (const xmlNode *sibling = node->parent->children;
117 sibling; sibling = sibling->next)
121 else if (sibling->type == XML_ELEMENT_NODE
122 && !strcmp (CHAR_CAST (char *, sibling->name),
123 CHAR_CAST (char *, node->name)))
127 ds_put_format (s, "[%zu]", index);
131 ds_put_format (s, "(%s)", xml_element_type_to_string (node->type));
135 static struct spvxml_node *
136 spvxml_node_find (struct spvxml_context *ctx, const char *name,
139 struct spvxml_node *node;
140 HMAP_FOR_EACH_WITH_HASH (node, struct spvxml_node, id_node, hash,
142 if (!strcmp (node->id, name))
149 spvxml_node_collect_id (struct spvxml_context *ctx, struct spvxml_node *node)
154 unsigned int hash = hash_string (node->id, 0);
155 struct spvxml_node *other = spvxml_node_find (ctx, node->id, hash);
160 struct string node_path = DS_EMPTY_INITIALIZER;
161 spvxml_format_node_path (node->raw, &node_path);
163 struct string other_path = DS_EMPTY_INITIALIZER;
164 spvxml_format_node_path (other->raw, &other_path);
166 ctx->error = xasprintf ("Nodes %s and %s both have ID \"%s\".",
167 ds_cstr (&node_path),
168 ds_cstr (&other_path), node->id);
170 ds_destroy (&node_path);
171 ds_destroy (&other_path);
177 hmap_insert (&ctx->id_map, &node->id_node, hash);
181 spvxml_node_resolve_ref (struct spvxml_context *ctx,
182 const xmlNode *src, const char *attr_name,
183 const struct spvxml_node_class *const *classes,
186 char *dst_id = CHAR_CAST (
187 char *, xmlGetProp (CONST_CAST (xmlNode *, src),
188 CHAR_CAST (xmlChar *, attr_name)));
192 struct spvxml_node *dst = spvxml_node_find (ctx, dst_id,
193 hash_string (dst_id, 0));
196 struct string node_path = DS_EMPTY_INITIALIZER;
197 spvxml_format_node_path (src, &node_path);
199 ctx->error = xasprintf (
200 "%s: Attribute %s has unknown target ID \"%s\".",
201 ds_cstr (&node_path), attr_name, dst_id);
203 ds_destroy (&node_path);
213 for (size_t i = 0; i < n; i++)
214 if (classes[i] == dst->class_)
222 struct string s = DS_EMPTY_INITIALIZER;
223 spvxml_format_node_path (src, &s);
225 ds_put_format (&s, ": Attribute \"%s\" should refer to a \"%s\"",
226 attr_name, classes[0]->name);
228 ds_put_format (&s, " or \"%s\"", classes[1]->name);
231 for (size_t i = 1; i < n - 1; i++)
232 ds_put_format (&s, ", \"%s\"", classes[i]->name);
233 ds_put_format (&s, ", or \"%s\"", classes[n - 1]->name);
235 ds_put_format (&s, " element, but its target ID \"%s\" "
236 "actually refers to a \"%s\" element.",
237 dst_id, dst->class_->name);
239 ctx->error = ds_steal_cstr (&s);
246 void PRINTF_FORMAT (2, 3)
247 spvxml_attr_error (struct spvxml_node_context *nctx, const char *format, ...)
252 struct string s = DS_EMPTY_INITIALIZER;
253 ds_put_cstr (&s, "error parsing attributes of ");
254 spvxml_format_node_path (nctx->parent, &s);
257 va_start (args, format);
258 ds_put_cstr (&s, ": ");
259 ds_put_vformat (&s, format, args);
262 nctx->up->error = ds_steal_cstr (&s);
265 /* xmlGetPropNodeValueInternal() is from tree.c in libxml2 2.9.4+dfsg1, which
266 is covered by the following copyright and license:
268 Except where otherwise noted in the source code (e.g. the files hash.c,
269 list.c and the trio files, which are covered by a similar licence but with
270 different Copyright notices) all the files are:
272 Copyright (C) 1998-2012 Daniel Veillard. All Rights Reserved.
274 Permission is hereby granted, free of charge, to any person obtaining a copy
275 of this software and associated documentation files (the "Software"), to
276 deal in the Software without restriction, including without limitation the
277 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
278 sell copies of the Software, and to permit persons to whom the Software is
279 fur- nished to do so, subject to the following conditions:
281 The above copyright notice and this permission notice shall be included in
282 all copies or substantial portions of the Software.
284 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
285 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
286 FIT- NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
287 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
288 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
289 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
293 xmlGetPropNodeValueInternal(const xmlAttr *prop)
297 if (prop->type == XML_ATTRIBUTE_NODE) {
299 * Note that we return at least the empty string.
300 * TODO: Do we really always want that?
302 if (prop->children != NULL) {
303 if ((prop->children->next == NULL) &&
304 ((prop->children->type == XML_TEXT_NODE) ||
305 (prop->children->type == XML_CDATA_SECTION_NODE)))
308 * Optimization for the common case: only 1 text node.
310 return(xmlStrdup(prop->children->content));
314 ret = xmlNodeListGetString(prop->doc, prop->children, 1);
319 return(xmlStrdup((xmlChar *)""));
320 } else if (prop->type == XML_ATTRIBUTE_DECL) {
321 return(xmlStrdup(((xmlAttributePtr)prop)->defaultValue));
326 static struct spvxml_attribute *
327 find_attribute (struct spvxml_node_context *nctx, const char *name)
329 /* XXX This is linear search but we could use binary search. */
330 for (struct spvxml_attribute *a = nctx->attrs;
331 a < &nctx->attrs[nctx->n_attrs]; a++)
332 if (!strcmp (a->name, name))
339 format_attribute (struct string *s, const xmlAttr *attr)
341 const char *name = CHAR_CAST (char *, attr->name);
342 char *value = CHAR_CAST (char *, xmlGetPropNodeValueInternal (attr));
343 ds_put_format (s, "%s=\"%s\"", name, value);
348 spvxml_parse_attributes (struct spvxml_node_context *nctx)
350 for (const xmlAttr *node = nctx->parent->properties; node; node = node->next)
352 const char *node_name = CHAR_CAST (char *, node->name);
353 struct spvxml_attribute *a = find_attribute (nctx, node_name);
356 if (!strcmp (node_name, "id"))
359 struct string unexpected = DS_EMPTY_INITIALIZER;
360 format_attribute (&unexpected, node);
363 for (node = node->next; node; node = node->next)
365 node_name = CHAR_CAST (char *, node->name);
366 if (!find_attribute (nctx, node_name)
367 && strcmp (node_name, "id"))
369 ds_put_byte (&unexpected, ' ');
370 format_attribute (&unexpected, node);
375 spvxml_attr_error (nctx, "Node has unexpected attribute%s: %s",
376 n > 1 ? "s" : "", ds_cstr (&unexpected));
377 ds_destroy (&unexpected);
382 spvxml_attr_error (nctx, "Duplicate attribute \"%s\".", a->name);
385 a->value = CHAR_CAST (char *, xmlGetPropNodeValueInternal (node));
388 for (struct spvxml_attribute *a = nctx->attrs;
389 a < &nctx->attrs[nctx->n_attrs]; a++)
391 if (a->required && !a->value)
392 spvxml_attr_error (nctx, "Missing required attribute \"%s\".",
399 spvxml_attr_parse_enum (struct spvxml_node_context *nctx,
400 const struct spvxml_attribute *a,
401 const struct spvxml_enum enums[])
406 for (const struct spvxml_enum *e = enums; e->name; e++)
407 if (!strcmp (a->value, e->name))
410 for (const struct spvxml_enum *e = enums; e->name; e++)
411 if (!strcmp (e->name, "OTHER"))
414 spvxml_attr_error (nctx, "Attribute %s has unexpected value \"%s\".",
420 spvxml_attr_parse_bool (struct spvxml_node_context *nctx,
421 const struct spvxml_attribute *a)
423 static const struct spvxml_enum bool_enums[] = {
429 return !a->value ? -1 : spvxml_attr_parse_enum (nctx, a, bool_enums);
433 spvxml_attr_parse_fixed (struct spvxml_node_context *nctx,
434 const struct spvxml_attribute *a,
435 const char *attr_value)
437 const struct spvxml_enum fixed_enums[] = {
438 { attr_value, true },
442 return spvxml_attr_parse_enum (nctx, a, fixed_enums);
446 spvxml_attr_parse_int (struct spvxml_node_context *nctx,
447 const struct spvxml_attribute *a)
453 int save_errno = errno;
455 long int integer = strtol (a->value, &tail, 10);
456 if (errno || *tail || integer <= INT_MIN || integer > INT_MAX)
458 spvxml_attr_error (nctx, "Attribute %s has unexpected value "
459 "\"%s\" expecting small integer.", a->name, a->value);
468 spvxml_attr_parse_color (struct spvxml_node_context *nctx,
469 const struct spvxml_attribute *a)
471 if (!a->value || !strcmp (a->value, "transparent"))
474 struct cell_color color;
475 if (parse_color__ (a->value, &color))
476 return (color.r << 16) | (color.g << 8) | color.b;
478 spvxml_attr_error (nctx, "Attribute %s has unexpected value "
479 "\"%s\" expecting #rrggbb or rrggbb or web color name.",
485 try_strtod (char *s, char **tail, double *real)
487 char *comma = strchr (s, ',');
491 int save_errno = errno;
494 *real = strtod (s, tail);
495 bool ok = errno == 0;
504 spvxml_attr_parse_real (struct spvxml_node_context *nctx,
505 const struct spvxml_attribute *a)
512 if (!try_strtod (a->value, &tail, &real) || *tail)
513 spvxml_attr_error (nctx, "Attribute %s has unexpected value "
514 "\"%s\" expecting real number.", a->name, a->value);
520 spvxml_attr_parse_dimension (struct spvxml_node_context *nctx,
521 const struct spvxml_attribute *a)
528 if (!try_strtod (a->value, &tail, &real))
531 tail += strspn (tail, " \t\r\n");
538 static const struct unit units[] = {
540 /* If you add anything to this table, update the table in
541 doc/dev/spv-file-format.texi also. */
550 /* Device-independent pixels. */
563 for (size_t i = 0; i < sizeof units / sizeof *units; i++)
564 if (!strcmp (units[i].name, tail))
565 return real / units[i].divisor;
569 spvxml_attr_error (nctx, "Attribute %s has unexpected value "
570 "\"%s\" expecting dimension.", a->name, a->value);
575 spvxml_attr_parse_ref (struct spvxml_node_context *nctx UNUSED,
576 const struct spvxml_attribute *a UNUSED)
581 void PRINTF_FORMAT (3, 4)
582 spvxml_content_error (struct spvxml_node_context *nctx, const xmlNode *node,
583 const char *format, ...)
588 struct string s = DS_EMPTY_INITIALIZER;
590 ds_put_cstr (&s, "error parsing content of ");
591 spvxml_format_node_path (nctx->parent, &s);
595 ds_put_format (&s, " at %s", xml_element_type_to_string (node->type));
597 ds_put_format (&s, " \"%s\"", node->name);
600 ds_put_format (&s, " at end of content");
603 va_start (args, format);
604 ds_put_cstr (&s, ": ");
605 ds_put_vformat (&s, format, args);
608 //puts (ds_cstr (&s));
610 nctx->up->error = ds_steal_cstr (&s);
614 spvxml_content_parse_element (struct spvxml_node_context *nctx,
616 const char *elem_name, xmlNode **outp)
618 xmlNode *node = *nodep;
621 if (node->type == XML_ELEMENT_NODE
622 && (!strcmp (CHAR_CAST (char *, node->name), elem_name)
623 || !strcmp (elem_name, "any")))
629 else if (node->type != XML_COMMENT_NODE)
635 spvxml_content_error (nctx, node, "\"%s\" element expected.", elem_name);
641 spvxml_content_parse_text (struct spvxml_node_context *nctx UNUSED, xmlNode **nodep,
644 struct string text = DS_EMPTY_INITIALIZER;
646 xmlNode *node = *nodep;
649 if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE)
651 char *segment = CHAR_CAST (char *, xmlNodeGetContent (node));
654 text.ss = ss_cstr (segment);
655 text.capacity = text.ss.length;
659 ds_put_cstr (&text, segment);
663 else if (node->type != XML_COMMENT_NODE)
670 *textp = ds_steal_cstr (&text);
676 spvxml_content_parse_end (struct spvxml_node_context *nctx, xmlNode *node)
682 else if (node->type != XML_COMMENT_NODE)
688 struct string s = DS_EMPTY_INITIALIZER;
690 for (int i = 0; i < 4 && node; i++, node = node->next)
693 ds_put_cstr (&s, ", ");
694 ds_put_cstr (&s, xml_element_type_to_string (node->type));
696 ds_put_format (&s, " \"%s\"", node->name);
699 ds_put_format (&s, ", ...");
701 spvxml_content_error (nctx, node, "Extra content found expecting end: %s",