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"
30 #include "gl/c-ctype.h"
31 #include "gl/xvasprintf.h"
32 #include "gl/xalloc.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";
80 case XML_DOCB_DOCUMENT_NODE: return "docb document";
81 default: return "<error>";
86 spvxml_format_node_path (const xmlNode *node, struct string *s)
88 enum { MAX_STACK = 32 };
89 const xmlNode *stack[MAX_STACK];
92 while (node != NULL && node->type != XML_DOCUMENT_NODE && n < MAX_STACK)
101 ds_put_byte (s, '/');
103 ds_put_cstr (s, CHAR_CAST (char *, node->name));
104 if (node->type == XML_ELEMENT_NODE)
110 for (const xmlNode *sibling = node->parent->children;
111 sibling; sibling = sibling->next)
115 else if (sibling->type == XML_ELEMENT_NODE
116 && !strcmp (CHAR_CAST (char *, sibling->name),
117 CHAR_CAST (char *, node->name)))
121 ds_put_format (s, "[%zu]", index);
125 ds_put_format (s, "(%s)", xml_element_type_to_string (node->type));
129 static struct spvxml_node *
130 spvxml_node_find (struct spvxml_context *ctx, const char *name,
133 struct spvxml_node *node;
134 HMAP_FOR_EACH_WITH_HASH (node, struct spvxml_node, id_node, hash,
136 if (!strcmp (node->id, name))
143 spvxml_node_collect_id (struct spvxml_context *ctx, struct spvxml_node *node)
148 unsigned int hash = hash_string (node->id, 0);
149 struct spvxml_node *other = spvxml_node_find (ctx, node->id, hash);
154 struct string node_path = DS_EMPTY_INITIALIZER;
155 spvxml_format_node_path (node->raw, &node_path);
157 struct string other_path = DS_EMPTY_INITIALIZER;
158 spvxml_format_node_path (other->raw, &other_path);
160 ctx->error = xasprintf ("Nodes %s and %s both have ID \"%s\".",
161 ds_cstr (&node_path),
162 ds_cstr (&other_path), node->id);
164 ds_destroy (&node_path);
165 ds_destroy (&other_path);
171 hmap_insert (&ctx->id_map, &node->id_node, hash);
175 spvxml_node_resolve_ref (struct spvxml_context *ctx,
176 const xmlNode *src, const char *attr_name,
177 const struct spvxml_node_class *const *classes,
180 char *dst_id = CHAR_CAST (
181 char *, xmlGetProp (CONST_CAST (xmlNode *, src),
182 CHAR_CAST (xmlChar *, attr_name)));
186 struct spvxml_node *dst = spvxml_node_find (ctx, dst_id,
187 hash_string (dst_id, 0));
190 struct string node_path = DS_EMPTY_INITIALIZER;
191 spvxml_format_node_path (src, &node_path);
193 ctx->error = xasprintf (
194 "%s: Attribute %s has unknown target ID \"%s\".",
195 ds_cstr (&node_path), attr_name, dst_id);
197 ds_destroy (&node_path);
207 for (size_t i = 0; i < n; i++)
208 if (classes[i] == dst->class_)
216 struct string s = DS_EMPTY_INITIALIZER;
217 spvxml_format_node_path (src, &s);
219 ds_put_format (&s, ": Attribute \"%s\" should refer to a \"%s\"",
220 attr_name, classes[0]->name);
222 ds_put_format (&s, " or \"%s\"", classes[1]->name);
225 for (size_t i = 1; i < n - 1; i++)
226 ds_put_format (&s, ", \"%s\"", classes[i]->name);
227 ds_put_format (&s, ", or \"%s\"", classes[n - 1]->name);
229 ds_put_format (&s, " element, but its target ID \"%s\" "
230 "actually refers to a \"%s\" element.",
231 dst_id, dst->class_->name);
233 ctx->error = ds_steal_cstr (&s);
240 void PRINTF_FORMAT (2, 3)
241 spvxml_attr_error (struct spvxml_node_context *nctx, const char *format, ...)
246 struct string s = DS_EMPTY_INITIALIZER;
247 ds_put_cstr (&s, "error parsing attributes of ");
248 spvxml_format_node_path (nctx->parent, &s);
251 va_start (args, format);
252 ds_put_cstr (&s, ": ");
253 ds_put_vformat (&s, format, args);
256 nctx->up->error = ds_steal_cstr (&s);
259 /* xmlGetPropNodeValueInternal() is from tree.c in libxml2 2.9.4+dfsg1, which
260 is covered by the following copyright and license:
262 Except where otherwise noted in the source code (e.g. the files hash.c,
263 list.c and the trio files, which are covered by a similar licence but with
264 different Copyright notices) all the files are:
266 Copyright (C) 1998-2012 Daniel Veillard. All Rights Reserved.
268 Permission is hereby granted, free of charge, to any person obtaining a copy
269 of this software and associated documentation files (the "Software"), to
270 deal in the Software without restriction, including without limitation the
271 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
272 sell copies of the Software, and to permit persons to whom the Software is
273 fur- nished to do so, subject to the following conditions:
275 The above copyright notice and this permission notice shall be included in
276 all copies or substantial portions of the Software.
278 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
279 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
280 FIT- NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
281 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
282 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
283 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
287 xmlGetPropNodeValueInternal(const xmlAttr *prop)
291 if (prop->type == XML_ATTRIBUTE_NODE) {
293 * Note that we return at least the empty string.
294 * TODO: Do we really always want that?
296 if (prop->children != NULL) {
297 if ((prop->children->next == NULL) &&
298 ((prop->children->type == XML_TEXT_NODE) ||
299 (prop->children->type == XML_CDATA_SECTION_NODE)))
302 * Optimization for the common case: only 1 text node.
304 return(xmlStrdup(prop->children->content));
308 ret = xmlNodeListGetString(prop->doc, prop->children, 1);
313 return(xmlStrdup((xmlChar *)""));
314 } else if (prop->type == XML_ATTRIBUTE_DECL) {
315 return(xmlStrdup(((xmlAttributePtr)prop)->defaultValue));
320 /* Returns true if S consists entirely of digits (and at least one digit) */
322 all_digits (const char *s)
324 for (size_t i = 0; ; i++)
325 if (!c_isdigit (s[i]))
326 return i > 0 && (s[i] == '\0' || (s[i] == '_' && s[i + 1] == '\0'));
329 static struct spvxml_attribute *
330 find_attribute (struct spvxml_node_context *nctx, const char *name)
332 /* XXX This is linear search but we could use binary search. */
333 for (struct spvxml_attribute *a = nctx->attrs;
334 a < &nctx->attrs[nctx->n_attrs]; a++)
336 if (!strcmp (a->name, name))
342 size_t a_len = strlen (a->name);
343 if ((a->name[a_len - 1] == '#'
344 && !strncmp (name, a->name, a_len - 1)
345 && all_digits (name + a_len - 1))
346 || (a->name[a_len - 1] == '%'
347 && !strncmp (name, a->name, a_len - 1)))
355 format_attribute (struct string *s, const xmlAttr *attr)
357 const char *name = CHAR_CAST (char *, attr->name);
358 char *value = CHAR_CAST (char *, xmlGetPropNodeValueInternal (attr));
359 ds_put_format (s, "%s=\"%s\"", name, value);
364 spvxml_parse_attributes (struct spvxml_node_context *nctx)
366 for (const xmlAttr *node = nctx->parent->properties; node; node = node->next)
368 const char *node_name = CHAR_CAST (char *, node->name);
369 struct spvxml_attribute *a = find_attribute (nctx, node_name);
372 if (!strcmp (node_name, "id"))
375 struct string unexpected = DS_EMPTY_INITIALIZER;
376 format_attribute (&unexpected, node);
379 for (node = node->next; node; node = node->next)
381 node_name = CHAR_CAST (char *, node->name);
382 if (!find_attribute (nctx, node_name)
383 && strcmp (node_name, "id"))
385 ds_put_byte (&unexpected, ' ');
386 format_attribute (&unexpected, node);
391 spvxml_attr_error (nctx, "Node has unexpected attribute%s: %s",
392 n > 1 ? "s" : "", ds_cstr (&unexpected));
393 ds_destroy (&unexpected);
398 if (a->name[strlen (a->name) - 1] != '#'
399 && a->name[strlen (a->name) - 1] != '%')
401 spvxml_attr_error (nctx, "Duplicate attribute \"%s\".", a->name);
406 a->value = CHAR_CAST (char *, xmlGetPropNodeValueInternal (node));
409 for (struct spvxml_attribute *a = nctx->attrs;
410 a < &nctx->attrs[nctx->n_attrs]; a++)
412 if (a->required && !a->value)
413 spvxml_attr_error (nctx, "Missing required attribute \"%s\".",
420 spvxml_attr_parse_enum (struct spvxml_node_context *nctx,
421 const struct spvxml_attribute *a,
422 const struct spvxml_enum enums[])
427 for (const struct spvxml_enum *e = enums; e->name; e++)
428 if (!strcmp (a->value, e->name))
431 for (const struct spvxml_enum *e = enums; e->name; e++)
432 if (!strcmp (e->name, "OTHER"))
435 spvxml_attr_error (nctx, "Attribute %s has unexpected value \"%s\".",
441 spvxml_attr_parse_bool (struct spvxml_node_context *nctx,
442 const struct spvxml_attribute *a)
444 static const struct spvxml_enum bool_enums[] = {
450 return !a->value ? -1 : spvxml_attr_parse_enum (nctx, a, bool_enums);
454 spvxml_attr_parse_fixed (struct spvxml_node_context *nctx,
455 const struct spvxml_attribute *a,
456 const char *attr_value)
458 const struct spvxml_enum fixed_enums[] = {
459 { attr_value, true },
463 return spvxml_attr_parse_enum (nctx, a, fixed_enums);
467 spvxml_attr_parse_int (struct spvxml_node_context *nctx,
468 const struct spvxml_attribute *a)
474 int save_errno = errno;
476 long int integer = strtol (a->value, &tail, 10);
477 if (errno || *tail || integer <= INT_MIN || integer > INT_MAX)
479 spvxml_attr_error (nctx, "Attribute %s has unexpected value "
480 "\"%s\" expecting small integer.", a->name, a->value);
489 lookup_color_name (const char *s)
493 struct hmap_node hmap_node;
498 static struct color colors[] =
500 { .name = "aliceblue", .code = 0xf0f8ff },
501 { .name = "antiquewhite", .code = 0xfaebd7 },
502 { .name = "aqua", .code = 0x00ffff },
503 { .name = "aquamarine", .code = 0x7fffd4 },
504 { .name = "azure", .code = 0xf0ffff },
505 { .name = "beige", .code = 0xf5f5dc },
506 { .name = "bisque", .code = 0xffe4c4 },
507 { .name = "black", .code = 0x000000 },
508 { .name = "blanchedalmond", .code = 0xffebcd },
509 { .name = "blue", .code = 0x0000ff },
510 { .name = "blueviolet", .code = 0x8a2be2 },
511 { .name = "brown", .code = 0xa52a2a },
512 { .name = "burlywood", .code = 0xdeb887 },
513 { .name = "cadetblue", .code = 0x5f9ea0 },
514 { .name = "chartreuse", .code = 0x7fff00 },
515 { .name = "chocolate", .code = 0xd2691e },
516 { .name = "coral", .code = 0xff7f50 },
517 { .name = "cornflowerblue", .code = 0x6495ed },
518 { .name = "cornsilk", .code = 0xfff8dc },
519 { .name = "crimson", .code = 0xdc143c },
520 { .name = "cyan", .code = 0x00ffff },
521 { .name = "darkblue", .code = 0x00008b },
522 { .name = "darkcyan", .code = 0x008b8b },
523 { .name = "darkgoldenrod", .code = 0xb8860b },
524 { .name = "darkgray", .code = 0xa9a9a9 },
525 { .name = "darkgreen", .code = 0x006400 },
526 { .name = "darkgrey", .code = 0xa9a9a9 },
527 { .name = "darkkhaki", .code = 0xbdb76b },
528 { .name = "darkmagenta", .code = 0x8b008b },
529 { .name = "darkolivegreen", .code = 0x556b2f },
530 { .name = "darkorange", .code = 0xff8c00 },
531 { .name = "darkorchid", .code = 0x9932cc },
532 { .name = "darkred", .code = 0x8b0000 },
533 { .name = "darksalmon", .code = 0xe9967a },
534 { .name = "darkseagreen", .code = 0x8fbc8f },
535 { .name = "darkslateblue", .code = 0x483d8b },
536 { .name = "darkslategray", .code = 0x2f4f4f },
537 { .name = "darkslategrey", .code = 0x2f4f4f },
538 { .name = "darkturquoise", .code = 0x00ced1 },
539 { .name = "darkviolet", .code = 0x9400d3 },
540 { .name = "deeppink", .code = 0xff1493 },
541 { .name = "deepskyblue", .code = 0x00bfff },
542 { .name = "dimgray", .code = 0x696969 },
543 { .name = "dimgrey", .code = 0x696969 },
544 { .name = "dodgerblue", .code = 0x1e90ff },
545 { .name = "firebrick", .code = 0xb22222 },
546 { .name = "floralwhite", .code = 0xfffaf0 },
547 { .name = "forestgreen", .code = 0x228b22 },
548 { .name = "fuchsia", .code = 0xff00ff },
549 { .name = "gainsboro", .code = 0xdcdcdc },
550 { .name = "ghostwhite", .code = 0xf8f8ff },
551 { .name = "gold", .code = 0xffd700 },
552 { .name = "goldenrod", .code = 0xdaa520 },
553 { .name = "gray", .code = 0x808080 },
554 { .name = "green", .code = 0x008000 },
555 { .name = "greenyellow", .code = 0xadff2f },
556 { .name = "grey", .code = 0x808080 },
557 { .name = "honeydew", .code = 0xf0fff0 },
558 { .name = "hotpink", .code = 0xff69b4 },
559 { .name = "indianred", .code = 0xcd5c5c },
560 { .name = "indigo", .code = 0x4b0082 },
561 { .name = "ivory", .code = 0xfffff0 },
562 { .name = "khaki", .code = 0xf0e68c },
563 { .name = "lavender", .code = 0xe6e6fa },
564 { .name = "lavenderblush", .code = 0xfff0f5 },
565 { .name = "lawngreen", .code = 0x7cfc00 },
566 { .name = "lemonchiffon", .code = 0xfffacd },
567 { .name = "lightblue", .code = 0xadd8e6 },
568 { .name = "lightcoral", .code = 0xf08080 },
569 { .name = "lightcyan", .code = 0xe0ffff },
570 { .name = "lightgoldenrodyellow", .code = 0xfafad2 },
571 { .name = "lightgray", .code = 0xd3d3d3 },
572 { .name = "lightgreen", .code = 0x90ee90 },
573 { .name = "lightgrey", .code = 0xd3d3d3 },
574 { .name = "lightpink", .code = 0xffb6c1 },
575 { .name = "lightsalmon", .code = 0xffa07a },
576 { .name = "lightseagreen", .code = 0x20b2aa },
577 { .name = "lightskyblue", .code = 0x87cefa },
578 { .name = "lightslategray", .code = 0x778899 },
579 { .name = "lightslategrey", .code = 0x778899 },
580 { .name = "lightsteelblue", .code = 0xb0c4de },
581 { .name = "lightyellow", .code = 0xffffe0 },
582 { .name = "lime", .code = 0x00ff00 },
583 { .name = "limegreen", .code = 0x32cd32 },
584 { .name = "linen", .code = 0xfaf0e6 },
585 { .name = "magenta", .code = 0xff00ff },
586 { .name = "maroon", .code = 0x800000 },
587 { .name = "mediumaquamarine", .code = 0x66cdaa },
588 { .name = "mediumblue", .code = 0x0000cd },
589 { .name = "mediumorchid", .code = 0xba55d3 },
590 { .name = "mediumpurple", .code = 0x9370db },
591 { .name = "mediumseagreen", .code = 0x3cb371 },
592 { .name = "mediumslateblue", .code = 0x7b68ee },
593 { .name = "mediumspringgreen", .code = 0x00fa9a },
594 { .name = "mediumturquoise", .code = 0x48d1cc },
595 { .name = "mediumvioletred", .code = 0xc71585 },
596 { .name = "midnightblue", .code = 0x191970 },
597 { .name = "mintcream", .code = 0xf5fffa },
598 { .name = "mistyrose", .code = 0xffe4e1 },
599 { .name = "moccasin", .code = 0xffe4b5 },
600 { .name = "navajowhite", .code = 0xffdead },
601 { .name = "navy", .code = 0x000080 },
602 { .name = "oldlace", .code = 0xfdf5e6 },
603 { .name = "olive", .code = 0x808000 },
604 { .name = "olivedrab", .code = 0x6b8e23 },
605 { .name = "orange", .code = 0xffa500 },
606 { .name = "orangered", .code = 0xff4500 },
607 { .name = "orchid", .code = 0xda70d6 },
608 { .name = "palegoldenrod", .code = 0xeee8aa },
609 { .name = "palegreen", .code = 0x98fb98 },
610 { .name = "paleturquoise", .code = 0xafeeee },
611 { .name = "palevioletred", .code = 0xdb7093 },
612 { .name = "papayawhip", .code = 0xffefd5 },
613 { .name = "peachpuff", .code = 0xffdab9 },
614 { .name = "peru", .code = 0xcd853f },
615 { .name = "pink", .code = 0xffc0cb },
616 { .name = "plum", .code = 0xdda0dd },
617 { .name = "powderblue", .code = 0xb0e0e6 },
618 { .name = "purple", .code = 0x800080 },
619 { .name = "red", .code = 0xff0000 },
620 { .name = "rosybrown", .code = 0xbc8f8f },
621 { .name = "royalblue", .code = 0x4169e1 },
622 { .name = "saddlebrown", .code = 0x8b4513 },
623 { .name = "salmon", .code = 0xfa8072 },
624 { .name = "sandybrown", .code = 0xf4a460 },
625 { .name = "seagreen", .code = 0x2e8b57 },
626 { .name = "seashell", .code = 0xfff5ee },
627 { .name = "sienna", .code = 0xa0522d },
628 { .name = "silver", .code = 0xc0c0c0 },
629 { .name = "skyblue", .code = 0x87ceeb },
630 { .name = "slateblue", .code = 0x6a5acd },
631 { .name = "slategray", .code = 0x708090 },
632 { .name = "slategrey", .code = 0x708090 },
633 { .name = "snow", .code = 0xfffafa },
634 { .name = "springgreen", .code = 0x00ff7f },
635 { .name = "steelblue", .code = 0x4682b4 },
636 { .name = "tan", .code = 0xd2b48c },
637 { .name = "teal", .code = 0x008080 },
638 { .name = "thistle", .code = 0xd8bfd8 },
639 { .name = "tomato", .code = 0xff6347 },
640 { .name = "turquoise", .code = 0x40e0d0 },
641 { .name = "violet", .code = 0xee82ee },
642 { .name = "wheat", .code = 0xf5deb3 },
643 { .name = "white", .code = 0xffffff },
644 { .name = "whitesmoke", .code = 0xf5f5f5 },
645 { .name = "yellow", .code = 0xffff00 },
646 { .name = "yellowgreen", .code = 0x9acd32 },
649 static struct hmap color_table = HMAP_INITIALIZER (color_table);
651 if (hmap_is_empty (&color_table))
652 for (size_t i = 0; i < sizeof colors / sizeof *colors; i++)
653 hmap_insert (&color_table, &colors[i].hmap_node,
654 hash_string (colors[i].name, 0));
656 const struct color *color;
657 HMAP_FOR_EACH_WITH_HASH (color, struct color, hmap_node,
658 hash_string (s, 0), &color_table)
659 if (!strcmp (color->name, s))
665 spvxml_attr_parse_color (struct spvxml_node_context *nctx,
666 const struct spvxml_attribute *a)
668 if (!a->value || !strcmp (a->value, "transparent"))
672 if (sscanf (a->value, "#%2x%2x%2x", &r, &g, &b) == 3
673 || sscanf (a->value, "%2x%2x%2x", &r, &g, &b) == 3)
674 return (r << 16) | (g << 8) | b;
676 int code = lookup_color_name (a->value);
680 spvxml_attr_error (nctx, "Attribute %s has unexpected value "
681 "\"%s\" expecting #rrggbb or rrggbb or web color name.",
687 try_strtod (char *s, char **tail, double *real)
689 char *comma = strchr (s, ',');
693 int save_errno = errno;
696 *real = strtod (s, tail);
697 bool ok = errno == 0;
706 spvxml_attr_parse_real (struct spvxml_node_context *nctx,
707 const struct spvxml_attribute *a)
714 if (!try_strtod (a->value, &tail, &real) || *tail)
715 spvxml_attr_error (nctx, "Attribute %s has unexpected value "
716 "\"%s\" expecting real number.", a->name, a->value);
722 spvxml_attr_parse_dimension (struct spvxml_node_context *nctx,
723 const struct spvxml_attribute *a)
730 if (!try_strtod (a->value, &tail, &real))
733 tail += strspn (tail, " \t\r\n");
740 static const struct unit units[] = {
742 /* If you add anything to this table, update the table in
743 doc/dev/spv-file-format.texi also. */
752 /* Device-independent pixels. */
765 for (size_t i = 0; i < sizeof units / sizeof *units; i++)
766 if (!strcmp (units[i].name, tail))
767 return real / units[i].divisor;
771 spvxml_attr_error (nctx, "Attribute %s has unexpected value "
772 "\"%s\" expecting dimension.", a->name, a->value);
777 spvxml_attr_parse_ref (struct spvxml_node_context *nctx UNUSED,
778 const struct spvxml_attribute *a UNUSED)
783 void PRINTF_FORMAT (3, 4)
784 spvxml_content_error (struct spvxml_node_context *nctx, const xmlNode *node,
785 const char *format, ...)
790 nctx->up->error = xstrdup ("error");
792 struct string s = DS_EMPTY_INITIALIZER;
794 ds_put_cstr (&s, "error parsing content of ");
795 spvxml_format_node_path (nctx->parent, &s);
799 ds_put_format (&s, " at %s", xml_element_type_to_string (node->type));
801 ds_put_format (&s, " \"%s\"", node->name);
804 ds_put_format (&s, " at end of content");
807 va_start (args, format);
808 ds_put_cstr (&s, ": ");
809 ds_put_vformat (&s, format, args);
812 //puts (ds_cstr (&s));
814 nctx->up->error = ds_steal_cstr (&s);
818 spvxml_content_parse_element (struct spvxml_node_context *nctx,
820 const char *elem_name, xmlNode **outp)
822 xmlNode *node = *nodep;
825 if (node->type == XML_ELEMENT_NODE
826 && (!strcmp (CHAR_CAST (char *, node->name), elem_name)
827 || !strcmp (elem_name, "any")))
833 else if (node->type != XML_COMMENT_NODE)
839 spvxml_content_error (nctx, node, "\"%s\" element expected.", elem_name);
845 spvxml_content_parse_text (struct spvxml_node_context *nctx UNUSED, xmlNode **nodep,
848 struct string text = DS_EMPTY_INITIALIZER;
850 xmlNode *node = *nodep;
853 if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE)
855 char *segment = CHAR_CAST (char *, xmlNodeGetContent (node));
858 text.ss = ss_cstr (segment);
859 text.capacity = text.ss.length;
863 ds_put_cstr (&text, segment);
867 else if (node->type != XML_COMMENT_NODE)
874 *textp = ds_steal_cstr (&text);
880 spvxml_content_parse_end (struct spvxml_node_context *nctx, xmlNode *node)
886 else if (node->type != XML_COMMENT_NODE)
892 struct string s = DS_EMPTY_INITIALIZER;
894 for (int i = 0; i < 4 && node; i++, node = node->next)
897 ds_put_cstr (&s, ", ");
898 ds_put_cstr (&s, xml_element_type_to_string (node->type));
900 ds_put_format (&s, " \"%s\"", node->name);
903 ds_put_format (&s, ", ...");
905 spvxml_content_error (nctx, node, "Extra content found expecting end: %s",