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/xvasprintf.h"
32 char * WARN_UNUSED_RESULT
33 spvxml_context_finish (struct spvxml_context *ctx, struct spvxml_node *root)
36 root->class_->spvxml_node_collect_ids (ctx, root);
38 root->class_->spvxml_node_resolve_refs (ctx, root);
40 hmap_destroy (&ctx->id_map);
46 spvxml_node_context_uninit (struct spvxml_node_context *nctx)
48 for (struct spvxml_attribute *a = nctx->attrs;
49 a < &nctx->attrs[nctx->n_attrs]; a++)
54 xml_element_type_to_string (xmlElementType type)
58 case XML_ELEMENT_NODE: return "element";
59 case XML_ATTRIBUTE_NODE: return "attribute";
60 case XML_TEXT_NODE: return "text";
61 case XML_CDATA_SECTION_NODE: return "CDATA section";
62 case XML_ENTITY_REF_NODE: return "entity reference";
63 case XML_ENTITY_NODE: return "entity";
64 case XML_PI_NODE: return "PI";
65 case XML_COMMENT_NODE: return "comment";
66 case XML_DOCUMENT_NODE: return "document";
67 case XML_DOCUMENT_TYPE_NODE: return "document type";
68 case XML_DOCUMENT_FRAG_NODE: return "document fragment";
69 case XML_NOTATION_NODE: return "notation";
70 case XML_HTML_DOCUMENT_NODE: return "HTML document";
71 case XML_DTD_NODE: return "DTD";
72 case XML_ELEMENT_DECL: return "element declaration";
73 case XML_ATTRIBUTE_DECL: return "attribute declaration";
74 case XML_ENTITY_DECL: return "entity declaration";
75 case XML_NAMESPACE_DECL: return "namespace declaration";
76 case XML_XINCLUDE_START: return "XInclude start";
77 case XML_XINCLUDE_END: return "XInclude end";
78 case XML_DOCB_DOCUMENT_NODE: return "docb document";
79 default: return "<error>";
84 spvxml_format_node_path (const xmlNode *node, struct string *s)
86 enum { MAX_STACK = 32 };
87 const xmlNode *stack[MAX_STACK];
90 while (node != NULL && node->type != XML_DOCUMENT_NODE && n < MAX_STACK)
101 ds_put_cstr (s, CHAR_CAST (char *, node->name));
102 if (node->type == XML_ELEMENT_NODE)
108 for (const xmlNode *sibling = node->parent->children;
109 sibling; sibling = sibling->next)
113 else if (sibling->type == XML_ELEMENT_NODE
114 && !strcmp (CHAR_CAST (char *, sibling->name),
115 CHAR_CAST (char *, node->name)))
119 ds_put_format (s, "[%zu]", index);
123 ds_put_format (s, "(%s)", xml_element_type_to_string (node->type));
127 static struct spvxml_node *
128 spvxml_node_find (struct spvxml_context *ctx, const char *name,
131 struct spvxml_node *node;
132 HMAP_FOR_EACH_WITH_HASH (node, struct spvxml_node, id_node, hash,
134 if (!strcmp (node->id, name))
141 spvxml_node_collect_id (struct spvxml_context *ctx, struct spvxml_node *node)
146 unsigned int hash = hash_string (node->id, 0);
147 struct spvxml_node *other = spvxml_node_find (ctx, node->id, hash);
152 struct string node_path = DS_EMPTY_INITIALIZER;
153 spvxml_format_node_path (node->raw, &node_path);
155 struct string other_path = DS_EMPTY_INITIALIZER;
156 spvxml_format_node_path (other->raw, &other_path);
158 ctx->error = xasprintf ("Nodes %s and %s both have ID \"%s\".",
159 ds_cstr (&node_path),
160 ds_cstr (&other_path), node->id);
162 ds_destroy (&node_path);
163 ds_destroy (&other_path);
169 hmap_insert (&ctx->id_map, &node->id_node, hash);
173 spvxml_node_resolve_ref (struct spvxml_context *ctx,
174 const xmlNode *src, const char *attr_name,
175 const struct spvxml_node_class *const *classes,
178 char *dst_id = CHAR_CAST (
179 char *, xmlGetProp (CONST_CAST (xmlNode *, src),
180 CHAR_CAST (xmlChar *, attr_name)));
184 struct spvxml_node *dst = spvxml_node_find (ctx, dst_id,
185 hash_string (dst_id, 0));
188 struct string node_path = DS_EMPTY_INITIALIZER;
189 spvxml_format_node_path (src, &node_path);
191 ctx->error = xasprintf (
192 "%s: Attribute %s has unknown target ID \"%s\".",
193 ds_cstr (&node_path), attr_name, dst_id);
195 ds_destroy (&node_path);
205 for (size_t i = 0; i < n; i++)
206 if (classes[i] == dst->class_)
214 struct string s = DS_EMPTY_INITIALIZER;
215 spvxml_format_node_path (src, &s);
217 ds_put_format (&s, ": Attribute \"%s\" should refer to a \"%s\"",
218 attr_name, classes[0]->name);
220 ds_put_format (&s, " or \"%s\"", classes[1]->name);
223 for (size_t i = 1; i < n - 1; i++)
224 ds_put_format (&s, ", \"%s\"", classes[i]->name);
225 ds_put_format (&s, ", or \"%s\"", classes[n - 1]->name);
227 ds_put_format (&s, " element, but its target ID \"%s\" "
228 "actually refers to a \"%s\" element.",
229 dst_id, dst->class_->name);
231 ctx->error = ds_steal_cstr (&s);
238 void PRINTF_FORMAT (2, 3)
239 spvxml_attr_error (struct spvxml_node_context *nctx, const char *format, ...)
244 struct string s = DS_EMPTY_INITIALIZER;
245 ds_put_cstr (&s, "error parsing attributes of ");
246 spvxml_format_node_path (nctx->parent, &s);
249 va_start (args, format);
250 ds_put_cstr (&s, ": ");
251 ds_put_vformat (&s, format, args);
254 nctx->up->error = ds_steal_cstr (&s);
257 /* xmlGetPropNodeValueInternal() is from tree.c in libxml2 2.9.4+dfsg1, which
258 is covered by the following copyright and license:
260 Except where otherwise noted in the source code (e.g. the files hash.c,
261 list.c and the trio files, which are covered by a similar licence but with
262 different Copyright notices) all the files are:
264 Copyright (C) 1998-2012 Daniel Veillard. All Rights Reserved.
266 Permission is hereby granted, free of charge, to any person obtaining a copy
267 of this software and associated documentation files (the "Software"), to
268 deal in the Software without restriction, including without limitation the
269 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
270 sell copies of the Software, and to permit persons to whom the Software is
271 fur- nished to do so, subject to the following conditions:
273 The above copyright notice and this permission notice shall be included in
274 all copies or substantial portions of the Software.
276 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
277 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
278 FIT- NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
279 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
280 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
281 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
285 xmlGetPropNodeValueInternal(const xmlAttr *prop)
289 if (prop->type == XML_ATTRIBUTE_NODE) {
291 * Note that we return at least the empty string.
292 * TODO: Do we really always want that?
294 if (prop->children != NULL) {
295 if ((prop->children->next == NULL) &&
296 ((prop->children->type == XML_TEXT_NODE) ||
297 (prop->children->type == XML_CDATA_SECTION_NODE)))
300 * Optimization for the common case: only 1 text node.
302 return(xmlStrdup(prop->children->content));
306 ret = xmlNodeListGetString(prop->doc, prop->children, 1);
311 return(xmlStrdup((xmlChar *)""));
312 } else if (prop->type == XML_ATTRIBUTE_DECL) {
313 return(xmlStrdup(((xmlAttributePtr)prop)->defaultValue));
318 static struct spvxml_attribute *
319 find_attribute (struct spvxml_node_context *nctx, const char *name)
321 /* XXX This is linear search but we could use binary search. */
322 for (struct spvxml_attribute *a = nctx->attrs;
323 a < &nctx->attrs[nctx->n_attrs]; a++)
324 if (!strcmp (a->name, name))
331 format_attribute (struct string *s, const xmlAttr *attr)
333 const char *name = CHAR_CAST (char *, attr->name);
334 char *value = CHAR_CAST (char *, xmlGetPropNodeValueInternal (attr));
335 ds_put_format (s, "%s=\"%s\"", name, value);
340 spvxml_parse_attributes (struct spvxml_node_context *nctx)
342 for (const xmlAttr *node = nctx->parent->properties; node; node = node->next)
344 const char *node_name = CHAR_CAST (char *, node->name);
345 struct spvxml_attribute *a = find_attribute (nctx, node_name);
348 if (!strcmp (node_name, "id"))
351 struct string unexpected = DS_EMPTY_INITIALIZER;
352 format_attribute (&unexpected, node);
355 for (node = node->next; node; node = node->next)
357 node_name = CHAR_CAST (char *, node->name);
358 if (!find_attribute (nctx, node_name)
359 && strcmp (node_name, "id"))
361 ds_put_byte (&unexpected, ' ');
362 format_attribute (&unexpected, node);
367 spvxml_attr_error (nctx, "Node has unexpected attribute%s: %s",
368 n > 1 ? "s" : "", ds_cstr (&unexpected));
369 ds_destroy (&unexpected);
374 spvxml_attr_error (nctx, "Duplicate attribute \"%s\".", a->name);
377 a->value = CHAR_CAST (char *, xmlGetPropNodeValueInternal (node));
380 for (struct spvxml_attribute *a = nctx->attrs;
381 a < &nctx->attrs[nctx->n_attrs]; a++)
383 if (a->required && !a->value)
384 spvxml_attr_error (nctx, "Missing required attribute \"%s\".",
391 spvxml_attr_parse_enum (struct spvxml_node_context *nctx,
392 const struct spvxml_attribute *a,
393 const struct spvxml_enum enums[])
398 for (const struct spvxml_enum *e = enums; e->name; e++)
399 if (!strcmp (a->value, e->name))
402 for (const struct spvxml_enum *e = enums; e->name; e++)
403 if (!strcmp (e->name, "OTHER"))
406 spvxml_attr_error (nctx, "Attribute %s has unexpected value \"%s\".",
412 spvxml_attr_parse_bool (struct spvxml_node_context *nctx,
413 const struct spvxml_attribute *a)
415 static const struct spvxml_enum bool_enums[] = {
421 return !a->value ? -1 : spvxml_attr_parse_enum (nctx, a, bool_enums);
425 spvxml_attr_parse_fixed (struct spvxml_node_context *nctx,
426 const struct spvxml_attribute *a,
427 const char *attr_value)
429 const struct spvxml_enum fixed_enums[] = {
430 { attr_value, true },
434 return spvxml_attr_parse_enum (nctx, a, fixed_enums);
438 spvxml_attr_parse_int (struct spvxml_node_context *nctx,
439 const struct spvxml_attribute *a)
445 int save_errno = errno;
447 long int integer = strtol (a->value, &tail, 10);
448 if (errno || *tail || integer <= INT_MIN || integer > INT_MAX)
450 spvxml_attr_error (nctx, "Attribute %s has unexpected value "
451 "\"%s\" expecting small integer.", a->name, a->value);
460 lookup_color_name (const char *s)
464 struct hmap_node hmap_node;
469 static struct color colors[] =
471 { .name = "aliceblue", .code = 0xf0f8ff },
472 { .name = "antiquewhite", .code = 0xfaebd7 },
473 { .name = "aqua", .code = 0x00ffff },
474 { .name = "aquamarine", .code = 0x7fffd4 },
475 { .name = "azure", .code = 0xf0ffff },
476 { .name = "beige", .code = 0xf5f5dc },
477 { .name = "bisque", .code = 0xffe4c4 },
478 { .name = "black", .code = 0x000000 },
479 { .name = "blanchedalmond", .code = 0xffebcd },
480 { .name = "blue", .code = 0x0000ff },
481 { .name = "blueviolet", .code = 0x8a2be2 },
482 { .name = "brown", .code = 0xa52a2a },
483 { .name = "burlywood", .code = 0xdeb887 },
484 { .name = "cadetblue", .code = 0x5f9ea0 },
485 { .name = "chartreuse", .code = 0x7fff00 },
486 { .name = "chocolate", .code = 0xd2691e },
487 { .name = "coral", .code = 0xff7f50 },
488 { .name = "cornflowerblue", .code = 0x6495ed },
489 { .name = "cornsilk", .code = 0xfff8dc },
490 { .name = "crimson", .code = 0xdc143c },
491 { .name = "cyan", .code = 0x00ffff },
492 { .name = "darkblue", .code = 0x00008b },
493 { .name = "darkcyan", .code = 0x008b8b },
494 { .name = "darkgoldenrod", .code = 0xb8860b },
495 { .name = "darkgray", .code = 0xa9a9a9 },
496 { .name = "darkgreen", .code = 0x006400 },
497 { .name = "darkgrey", .code = 0xa9a9a9 },
498 { .name = "darkkhaki", .code = 0xbdb76b },
499 { .name = "darkmagenta", .code = 0x8b008b },
500 { .name = "darkolivegreen", .code = 0x556b2f },
501 { .name = "darkorange", .code = 0xff8c00 },
502 { .name = "darkorchid", .code = 0x9932cc },
503 { .name = "darkred", .code = 0x8b0000 },
504 { .name = "darksalmon", .code = 0xe9967a },
505 { .name = "darkseagreen", .code = 0x8fbc8f },
506 { .name = "darkslateblue", .code = 0x483d8b },
507 { .name = "darkslategray", .code = 0x2f4f4f },
508 { .name = "darkslategrey", .code = 0x2f4f4f },
509 { .name = "darkturquoise", .code = 0x00ced1 },
510 { .name = "darkviolet", .code = 0x9400d3 },
511 { .name = "deeppink", .code = 0xff1493 },
512 { .name = "deepskyblue", .code = 0x00bfff },
513 { .name = "dimgray", .code = 0x696969 },
514 { .name = "dimgrey", .code = 0x696969 },
515 { .name = "dodgerblue", .code = 0x1e90ff },
516 { .name = "firebrick", .code = 0xb22222 },
517 { .name = "floralwhite", .code = 0xfffaf0 },
518 { .name = "forestgreen", .code = 0x228b22 },
519 { .name = "fuchsia", .code = 0xff00ff },
520 { .name = "gainsboro", .code = 0xdcdcdc },
521 { .name = "ghostwhite", .code = 0xf8f8ff },
522 { .name = "gold", .code = 0xffd700 },
523 { .name = "goldenrod", .code = 0xdaa520 },
524 { .name = "gray", .code = 0x808080 },
525 { .name = "green", .code = 0x008000 },
526 { .name = "greenyellow", .code = 0xadff2f },
527 { .name = "grey", .code = 0x808080 },
528 { .name = "honeydew", .code = 0xf0fff0 },
529 { .name = "hotpink", .code = 0xff69b4 },
530 { .name = "indianred", .code = 0xcd5c5c },
531 { .name = "indigo", .code = 0x4b0082 },
532 { .name = "ivory", .code = 0xfffff0 },
533 { .name = "khaki", .code = 0xf0e68c },
534 { .name = "lavender", .code = 0xe6e6fa },
535 { .name = "lavenderblush", .code = 0xfff0f5 },
536 { .name = "lawngreen", .code = 0x7cfc00 },
537 { .name = "lemonchiffon", .code = 0xfffacd },
538 { .name = "lightblue", .code = 0xadd8e6 },
539 { .name = "lightcoral", .code = 0xf08080 },
540 { .name = "lightcyan", .code = 0xe0ffff },
541 { .name = "lightgoldenrodyellow", .code = 0xfafad2 },
542 { .name = "lightgray", .code = 0xd3d3d3 },
543 { .name = "lightgreen", .code = 0x90ee90 },
544 { .name = "lightgrey", .code = 0xd3d3d3 },
545 { .name = "lightpink", .code = 0xffb6c1 },
546 { .name = "lightsalmon", .code = 0xffa07a },
547 { .name = "lightseagreen", .code = 0x20b2aa },
548 { .name = "lightskyblue", .code = 0x87cefa },
549 { .name = "lightslategray", .code = 0x778899 },
550 { .name = "lightslategrey", .code = 0x778899 },
551 { .name = "lightsteelblue", .code = 0xb0c4de },
552 { .name = "lightyellow", .code = 0xffffe0 },
553 { .name = "lime", .code = 0x00ff00 },
554 { .name = "limegreen", .code = 0x32cd32 },
555 { .name = "linen", .code = 0xfaf0e6 },
556 { .name = "magenta", .code = 0xff00ff },
557 { .name = "maroon", .code = 0x800000 },
558 { .name = "mediumaquamarine", .code = 0x66cdaa },
559 { .name = "mediumblue", .code = 0x0000cd },
560 { .name = "mediumorchid", .code = 0xba55d3 },
561 { .name = "mediumpurple", .code = 0x9370db },
562 { .name = "mediumseagreen", .code = 0x3cb371 },
563 { .name = "mediumslateblue", .code = 0x7b68ee },
564 { .name = "mediumspringgreen", .code = 0x00fa9a },
565 { .name = "mediumturquoise", .code = 0x48d1cc },
566 { .name = "mediumvioletred", .code = 0xc71585 },
567 { .name = "midnightblue", .code = 0x191970 },
568 { .name = "mintcream", .code = 0xf5fffa },
569 { .name = "mistyrose", .code = 0xffe4e1 },
570 { .name = "moccasin", .code = 0xffe4b5 },
571 { .name = "navajowhite", .code = 0xffdead },
572 { .name = "navy", .code = 0x000080 },
573 { .name = "oldlace", .code = 0xfdf5e6 },
574 { .name = "olive", .code = 0x808000 },
575 { .name = "olivedrab", .code = 0x6b8e23 },
576 { .name = "orange", .code = 0xffa500 },
577 { .name = "orangered", .code = 0xff4500 },
578 { .name = "orchid", .code = 0xda70d6 },
579 { .name = "palegoldenrod", .code = 0xeee8aa },
580 { .name = "palegreen", .code = 0x98fb98 },
581 { .name = "paleturquoise", .code = 0xafeeee },
582 { .name = "palevioletred", .code = 0xdb7093 },
583 { .name = "papayawhip", .code = 0xffefd5 },
584 { .name = "peachpuff", .code = 0xffdab9 },
585 { .name = "peru", .code = 0xcd853f },
586 { .name = "pink", .code = 0xffc0cb },
587 { .name = "plum", .code = 0xdda0dd },
588 { .name = "powderblue", .code = 0xb0e0e6 },
589 { .name = "purple", .code = 0x800080 },
590 { .name = "red", .code = 0xff0000 },
591 { .name = "rosybrown", .code = 0xbc8f8f },
592 { .name = "royalblue", .code = 0x4169e1 },
593 { .name = "saddlebrown", .code = 0x8b4513 },
594 { .name = "salmon", .code = 0xfa8072 },
595 { .name = "sandybrown", .code = 0xf4a460 },
596 { .name = "seagreen", .code = 0x2e8b57 },
597 { .name = "seashell", .code = 0xfff5ee },
598 { .name = "sienna", .code = 0xa0522d },
599 { .name = "silver", .code = 0xc0c0c0 },
600 { .name = "skyblue", .code = 0x87ceeb },
601 { .name = "slateblue", .code = 0x6a5acd },
602 { .name = "slategray", .code = 0x708090 },
603 { .name = "slategrey", .code = 0x708090 },
604 { .name = "snow", .code = 0xfffafa },
605 { .name = "springgreen", .code = 0x00ff7f },
606 { .name = "steelblue", .code = 0x4682b4 },
607 { .name = "tan", .code = 0xd2b48c },
608 { .name = "teal", .code = 0x008080 },
609 { .name = "thistle", .code = 0xd8bfd8 },
610 { .name = "tomato", .code = 0xff6347 },
611 { .name = "turquoise", .code = 0x40e0d0 },
612 { .name = "violet", .code = 0xee82ee },
613 { .name = "wheat", .code = 0xf5deb3 },
614 { .name = "white", .code = 0xffffff },
615 { .name = "whitesmoke", .code = 0xf5f5f5 },
616 { .name = "yellow", .code = 0xffff00 },
617 { .name = "yellowgreen", .code = 0x9acd32 },
620 static struct hmap color_table = HMAP_INITIALIZER (color_table);
622 if (hmap_is_empty (&color_table))
623 for (size_t i = 0; i < sizeof colors / sizeof *colors; i++)
624 hmap_insert (&color_table, &colors[i].hmap_node,
625 hash_string (colors[i].name, 0));
627 const struct color *color;
628 HMAP_FOR_EACH_WITH_HASH (color, struct color, hmap_node,
629 hash_string (s, 0), &color_table)
630 if (!strcmp (color->name, s))
636 spvxml_attr_parse_color (struct spvxml_node_context *nctx,
637 const struct spvxml_attribute *a)
639 if (!a->value || !strcmp (a->value, "transparent"))
643 if (sscanf (a->value, "#%2x%2x%2x", &r, &g, &b) == 3
644 || sscanf (a->value, "%2x%2x%2x", &r, &g, &b) == 3)
645 return (r << 16) | (g << 8) | b;
647 int code = lookup_color_name (a->value);
651 spvxml_attr_error (nctx, "Attribute %s has unexpected value "
652 "\"%s\" expecting #rrggbb or rrggbb or web color name.",
658 try_strtod (char *s, char **tail, double *real)
660 char *comma = strchr (s, ',');
664 int save_errno = errno;
667 *real = strtod (s, tail);
668 bool ok = errno == 0;
677 spvxml_attr_parse_real (struct spvxml_node_context *nctx,
678 const struct spvxml_attribute *a)
685 if (!try_strtod (a->value, &tail, &real) || *tail)
686 spvxml_attr_error (nctx, "Attribute %s has unexpected value "
687 "\"%s\" expecting real number.", a->name, a->value);
693 spvxml_attr_parse_dimension (struct spvxml_node_context *nctx,
694 const struct spvxml_attribute *a)
701 if (!try_strtod (a->value, &tail, &real))
704 tail += strspn (tail, " \t\r\n");
711 static const struct unit units[] = {
713 /* If you add anything to this table, update the table in
714 doc/dev/spv-file-format.texi also. */
723 /* Device-independent pixels. */
736 for (size_t i = 0; i < sizeof units / sizeof *units; i++)
737 if (!strcmp (units[i].name, tail))
738 return real / units[i].divisor;
742 spvxml_attr_error (nctx, "Attribute %s has unexpected value "
743 "\"%s\" expecting dimension.", a->name, a->value);
748 spvxml_attr_parse_ref (struct spvxml_node_context *nctx UNUSED,
749 const struct spvxml_attribute *a UNUSED)
754 void PRINTF_FORMAT (3, 4)
755 spvxml_content_error (struct spvxml_node_context *nctx, const xmlNode *node,
756 const char *format, ...)
761 struct string s = DS_EMPTY_INITIALIZER;
763 ds_put_cstr (&s, "error parsing content of ");
764 spvxml_format_node_path (nctx->parent, &s);
768 ds_put_format (&s, " at %s", xml_element_type_to_string (node->type));
770 ds_put_format (&s, " \"%s\"", node->name);
773 ds_put_format (&s, " at end of content");
776 va_start (args, format);
777 ds_put_cstr (&s, ": ");
778 ds_put_vformat (&s, format, args);
781 //puts (ds_cstr (&s));
783 nctx->up->error = ds_steal_cstr (&s);
787 spvxml_content_parse_element (struct spvxml_node_context *nctx,
789 const char *elem_name, xmlNode **outp)
791 xmlNode *node = *nodep;
794 if (node->type == XML_ELEMENT_NODE
795 && (!strcmp (CHAR_CAST (char *, node->name), elem_name)
796 || !strcmp (elem_name, "any")))
802 else if (node->type != XML_COMMENT_NODE)
808 spvxml_content_error (nctx, node, "\"%s\" element expected.", elem_name);
814 spvxml_content_parse_text (struct spvxml_node_context *nctx UNUSED, xmlNode **nodep,
817 struct string text = DS_EMPTY_INITIALIZER;
819 xmlNode *node = *nodep;
822 if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE)
824 char *segment = CHAR_CAST (char *, xmlNodeGetContent (node));
827 text.ss = ss_cstr (segment);
828 text.capacity = text.ss.length;
832 ds_put_cstr (&text, segment);
836 else if (node->type != XML_COMMENT_NODE)
843 *textp = ds_steal_cstr (&text);
849 spvxml_content_parse_end (struct spvxml_node_context *nctx, xmlNode *node)
855 else if (node->type != XML_COMMENT_NODE)
861 struct string s = DS_EMPTY_INITIALIZER;
863 for (int i = 0; i < 4 && node; i++, node = node->next)
866 ds_put_cstr (&s, ", ");
867 ds_put_cstr (&s, xml_element_type_to_string (node->type));
869 ds_put_format (&s, " \"%s\"", node->name);
872 ds_put_format (&s, ", ...");
874 spvxml_content_error (nctx, node, "Extra content found expecting end: %s",