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"
33 char * WARN_UNUSED_RESULT
34 spvxml_context_finish (struct spvxml_context *ctx, struct spvxml_node *root)
37 root->class_->spvxml_node_collect_ids (ctx, root);
39 root->class_->spvxml_node_resolve_refs (ctx, root);
41 hmap_destroy (&ctx->id_map);
47 spvxml_node_context_uninit (struct spvxml_node_context *nctx)
49 for (struct spvxml_attribute *a = nctx->attrs;
50 a < &nctx->attrs[nctx->n_attrs]; a++)
55 xml_element_type_to_string (xmlElementType type)
59 case XML_ELEMENT_NODE: return "element";
60 case XML_ATTRIBUTE_NODE: return "attribute";
61 case XML_TEXT_NODE: return "text";
62 case XML_CDATA_SECTION_NODE: return "CDATA section";
63 case XML_ENTITY_REF_NODE: return "entity reference";
64 case XML_ENTITY_NODE: return "entity";
65 case XML_PI_NODE: return "PI";
66 case XML_COMMENT_NODE: return "comment";
67 case XML_DOCUMENT_NODE: return "document";
68 case XML_DOCUMENT_TYPE_NODE: return "document type";
69 case XML_DOCUMENT_FRAG_NODE: return "document fragment";
70 case XML_NOTATION_NODE: return "notation";
71 case XML_HTML_DOCUMENT_NODE: return "HTML document";
72 case XML_DTD_NODE: return "DTD";
73 case XML_ELEMENT_DECL: return "element declaration";
74 case XML_ATTRIBUTE_DECL: return "attribute declaration";
75 case XML_ENTITY_DECL: return "entity declaration";
76 case XML_NAMESPACE_DECL: return "namespace declaration";
77 case XML_XINCLUDE_START: return "XInclude start";
78 case XML_XINCLUDE_END: return "XInclude end";
79 case XML_DOCB_DOCUMENT_NODE: return "docb document";
80 default: return "<error>";
85 spvxml_format_node_path (const xmlNode *node, struct string *s)
87 enum { MAX_STACK = 32 };
88 const xmlNode *stack[MAX_STACK];
91 while (node != NULL && node->type != XML_DOCUMENT_NODE && n < MAX_STACK)
100 ds_put_byte (s, '/');
102 ds_put_cstr (s, CHAR_CAST (char *, node->name));
103 if (node->type == XML_ELEMENT_NODE)
109 for (const xmlNode *sibling = node->parent->children;
110 sibling; sibling = sibling->next)
114 else if (sibling->type == XML_ELEMENT_NODE
115 && !strcmp (CHAR_CAST (char *, sibling->name),
116 CHAR_CAST (char *, node->name)))
120 ds_put_format (s, "[%zu]", index);
124 ds_put_format (s, "(%s)", xml_element_type_to_string (node->type));
128 static struct spvxml_node *
129 spvxml_node_find (struct spvxml_context *ctx, const char *name,
132 struct spvxml_node *node;
133 HMAP_FOR_EACH_WITH_HASH (node, struct spvxml_node, id_node, hash,
135 if (!strcmp (node->id, name))
142 spvxml_node_collect_id (struct spvxml_context *ctx, struct spvxml_node *node)
147 unsigned int hash = hash_string (node->id, 0);
148 struct spvxml_node *other = spvxml_node_find (ctx, node->id, hash);
153 struct string node_path = DS_EMPTY_INITIALIZER;
154 spvxml_format_node_path (node->raw, &node_path);
156 struct string other_path = DS_EMPTY_INITIALIZER;
157 spvxml_format_node_path (other->raw, &other_path);
159 ctx->error = xasprintf ("Nodes %s and %s both have ID \"%s\".",
160 ds_cstr (&node_path),
161 ds_cstr (&other_path), node->id);
163 ds_destroy (&node_path);
164 ds_destroy (&other_path);
170 hmap_insert (&ctx->id_map, &node->id_node, hash);
174 spvxml_node_resolve_ref (struct spvxml_context *ctx,
175 const xmlNode *src, const char *attr_name,
176 const struct spvxml_node_class *const *classes,
179 char *dst_id = CHAR_CAST (
180 char *, xmlGetProp (CONST_CAST (xmlNode *, src),
181 CHAR_CAST (xmlChar *, attr_name)));
185 struct spvxml_node *dst = spvxml_node_find (ctx, dst_id,
186 hash_string (dst_id, 0));
189 struct string node_path = DS_EMPTY_INITIALIZER;
190 spvxml_format_node_path (src, &node_path);
192 ctx->error = xasprintf (
193 "%s: Attribute %s has unknown target ID \"%s\".",
194 ds_cstr (&node_path), attr_name, dst_id);
196 ds_destroy (&node_path);
206 for (size_t i = 0; i < n; i++)
207 if (classes[i] == dst->class_)
215 struct string s = DS_EMPTY_INITIALIZER;
216 spvxml_format_node_path (src, &s);
218 ds_put_format (&s, ": Attribute \"%s\" should refer to a \"%s\"",
219 attr_name, classes[0]->name);
221 ds_put_format (&s, " or \"%s\"", classes[1]->name);
224 for (size_t i = 1; i < n - 1; i++)
225 ds_put_format (&s, ", \"%s\"", classes[i]->name);
226 ds_put_format (&s, ", or \"%s\"", classes[n - 1]->name);
228 ds_put_format (&s, " element, but its target ID \"%s\" "
229 "actually refers to a \"%s\" element.",
230 dst_id, dst->class_->name);
232 ctx->error = ds_steal_cstr (&s);
239 void PRINTF_FORMAT (2, 3)
240 spvxml_attr_error (struct spvxml_node_context *nctx, const char *format, ...)
245 struct string s = DS_EMPTY_INITIALIZER;
246 ds_put_cstr (&s, "error parsing attributes of ");
247 spvxml_format_node_path (nctx->parent, &s);
250 va_start (args, format);
251 ds_put_cstr (&s, ": ");
252 ds_put_vformat (&s, format, args);
255 nctx->up->error = ds_steal_cstr (&s);
258 /* xmlGetPropNodeValueInternal() is from tree.c in libxml2 2.9.4+dfsg1, which
259 is covered by the following copyright and license:
261 Except where otherwise noted in the source code (e.g. the files hash.c,
262 list.c and the trio files, which are covered by a similar licence but with
263 different Copyright notices) all the files are:
265 Copyright (C) 1998-2012 Daniel Veillard. All Rights Reserved.
267 Permission is hereby granted, free of charge, to any person obtaining a copy
268 of this software and associated documentation files (the "Software"), to
269 deal in the Software without restriction, including without limitation the
270 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
271 sell copies of the Software, and to permit persons to whom the Software is
272 fur- nished to do so, subject to the following conditions:
274 The above copyright notice and this permission notice shall be included in
275 all copies or substantial portions of the Software.
277 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
278 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
279 FIT- NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
280 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
281 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
282 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
286 xmlGetPropNodeValueInternal(const xmlAttr *prop)
290 if (prop->type == XML_ATTRIBUTE_NODE) {
292 * Note that we return at least the empty string.
293 * TODO: Do we really always want that?
295 if (prop->children != NULL) {
296 if ((prop->children->next == NULL) &&
297 ((prop->children->type == XML_TEXT_NODE) ||
298 (prop->children->type == XML_CDATA_SECTION_NODE)))
301 * Optimization for the common case: only 1 text node.
303 return(xmlStrdup(prop->children->content));
307 ret = xmlNodeListGetString(prop->doc, prop->children, 1);
312 return(xmlStrdup((xmlChar *)""));
313 } else if (prop->type == XML_ATTRIBUTE_DECL) {
314 return(xmlStrdup(((xmlAttributePtr)prop)->defaultValue));
319 /* Returns true if S consists entirely of digits (and at least one digit) */
321 all_digits (const char *s)
323 for (size_t i = 0; ; i++)
324 if (!c_isdigit (s[i]))
325 return i > 0 && (s[i] == '\0' || (s[i] == '_' && s[i + 1] == '\0'));
328 static struct spvxml_attribute *
329 find_attribute (struct spvxml_node_context *nctx, const char *name)
331 /* XXX This is linear search but we could use binary search. */
332 for (struct spvxml_attribute *a = nctx->attrs;
333 a < &nctx->attrs[nctx->n_attrs]; a++)
335 size_t a_len = strlen (a->name);
336 if (!strcmp (a->name, name)
337 || (a->name[a_len - 1] == '#'
338 && !strncmp (name, a->name, a_len - 1)
339 && all_digits (name + a_len - 1))
340 || (a->name[a_len - 1] == '%'
341 && !strncmp (name, a->name, a_len - 1)))
349 format_attribute (struct string *s, const xmlAttr *attr)
351 const char *name = CHAR_CAST (char *, attr->name);
352 char *value = CHAR_CAST (char *, xmlGetPropNodeValueInternal (attr));
353 ds_put_format (s, "%s=\"%s\"", name, value);
358 spvxml_parse_attributes (struct spvxml_node_context *nctx)
360 for (const xmlAttr *node = nctx->parent->properties; node; node = node->next)
362 const char *node_name = CHAR_CAST (char *, node->name);
363 struct spvxml_attribute *a = find_attribute (nctx, node_name);
366 if (!strcmp (node_name, "id"))
369 struct string unexpected = DS_EMPTY_INITIALIZER;
370 format_attribute (&unexpected, node);
373 for (node = node->next; node; node = node->next)
375 node_name = CHAR_CAST (char *, node->name);
376 if (!find_attribute (nctx, node_name)
377 && strcmp (node_name, "id"))
379 ds_put_byte (&unexpected, ' ');
380 format_attribute (&unexpected, node);
385 spvxml_attr_error (nctx, "Node has unexpected attribute%s: %s",
386 n > 1 ? "s" : "", ds_cstr (&unexpected));
387 ds_destroy (&unexpected);
392 if (a->name[strlen (a->name) - 1] != '#'
393 && a->name[strlen (a->name) - 1] != '%')
395 spvxml_attr_error (nctx, "Duplicate attribute \"%s\".", a->name);
400 a->value = CHAR_CAST (char *, xmlGetPropNodeValueInternal (node));
403 for (struct spvxml_attribute *a = nctx->attrs;
404 a < &nctx->attrs[nctx->n_attrs]; a++)
406 if (a->required && !a->value)
407 spvxml_attr_error (nctx, "Missing required attribute \"%s\".",
414 spvxml_attr_parse_enum (struct spvxml_node_context *nctx,
415 const struct spvxml_attribute *a,
416 const struct spvxml_enum enums[])
421 for (const struct spvxml_enum *e = enums; e->name; e++)
422 if (!strcmp (a->value, e->name))
425 for (const struct spvxml_enum *e = enums; e->name; e++)
426 if (!strcmp (e->name, "OTHER"))
429 spvxml_attr_error (nctx, "Attribute %s has unexpected value \"%s\".",
435 spvxml_attr_parse_bool (struct spvxml_node_context *nctx,
436 const struct spvxml_attribute *a)
438 static const struct spvxml_enum bool_enums[] = {
444 return !a->value ? -1 : spvxml_attr_parse_enum (nctx, a, bool_enums);
448 spvxml_attr_parse_fixed (struct spvxml_node_context *nctx,
449 const struct spvxml_attribute *a,
450 const char *attr_value)
452 const struct spvxml_enum fixed_enums[] = {
453 { attr_value, true },
457 return spvxml_attr_parse_enum (nctx, a, fixed_enums);
461 spvxml_attr_parse_int (struct spvxml_node_context *nctx,
462 const struct spvxml_attribute *a)
468 int save_errno = errno;
470 long int integer = strtol (a->value, &tail, 10);
471 if (errno || *tail || integer <= INT_MIN || integer > INT_MAX)
473 spvxml_attr_error (nctx, "Attribute %s has unexpected value "
474 "\"%s\" expecting small integer.", a->name, a->value);
483 lookup_color_name (const char *s)
487 struct hmap_node hmap_node;
492 static struct color colors[] =
494 { .name = "aliceblue", .code = 0xf0f8ff },
495 { .name = "antiquewhite", .code = 0xfaebd7 },
496 { .name = "aqua", .code = 0x00ffff },
497 { .name = "aquamarine", .code = 0x7fffd4 },
498 { .name = "azure", .code = 0xf0ffff },
499 { .name = "beige", .code = 0xf5f5dc },
500 { .name = "bisque", .code = 0xffe4c4 },
501 { .name = "black", .code = 0x000000 },
502 { .name = "blanchedalmond", .code = 0xffebcd },
503 { .name = "blue", .code = 0x0000ff },
504 { .name = "blueviolet", .code = 0x8a2be2 },
505 { .name = "brown", .code = 0xa52a2a },
506 { .name = "burlywood", .code = 0xdeb887 },
507 { .name = "cadetblue", .code = 0x5f9ea0 },
508 { .name = "chartreuse", .code = 0x7fff00 },
509 { .name = "chocolate", .code = 0xd2691e },
510 { .name = "coral", .code = 0xff7f50 },
511 { .name = "cornflowerblue", .code = 0x6495ed },
512 { .name = "cornsilk", .code = 0xfff8dc },
513 { .name = "crimson", .code = 0xdc143c },
514 { .name = "cyan", .code = 0x00ffff },
515 { .name = "darkblue", .code = 0x00008b },
516 { .name = "darkcyan", .code = 0x008b8b },
517 { .name = "darkgoldenrod", .code = 0xb8860b },
518 { .name = "darkgray", .code = 0xa9a9a9 },
519 { .name = "darkgreen", .code = 0x006400 },
520 { .name = "darkgrey", .code = 0xa9a9a9 },
521 { .name = "darkkhaki", .code = 0xbdb76b },
522 { .name = "darkmagenta", .code = 0x8b008b },
523 { .name = "darkolivegreen", .code = 0x556b2f },
524 { .name = "darkorange", .code = 0xff8c00 },
525 { .name = "darkorchid", .code = 0x9932cc },
526 { .name = "darkred", .code = 0x8b0000 },
527 { .name = "darksalmon", .code = 0xe9967a },
528 { .name = "darkseagreen", .code = 0x8fbc8f },
529 { .name = "darkslateblue", .code = 0x483d8b },
530 { .name = "darkslategray", .code = 0x2f4f4f },
531 { .name = "darkslategrey", .code = 0x2f4f4f },
532 { .name = "darkturquoise", .code = 0x00ced1 },
533 { .name = "darkviolet", .code = 0x9400d3 },
534 { .name = "deeppink", .code = 0xff1493 },
535 { .name = "deepskyblue", .code = 0x00bfff },
536 { .name = "dimgray", .code = 0x696969 },
537 { .name = "dimgrey", .code = 0x696969 },
538 { .name = "dodgerblue", .code = 0x1e90ff },
539 { .name = "firebrick", .code = 0xb22222 },
540 { .name = "floralwhite", .code = 0xfffaf0 },
541 { .name = "forestgreen", .code = 0x228b22 },
542 { .name = "fuchsia", .code = 0xff00ff },
543 { .name = "gainsboro", .code = 0xdcdcdc },
544 { .name = "ghostwhite", .code = 0xf8f8ff },
545 { .name = "gold", .code = 0xffd700 },
546 { .name = "goldenrod", .code = 0xdaa520 },
547 { .name = "gray", .code = 0x808080 },
548 { .name = "green", .code = 0x008000 },
549 { .name = "greenyellow", .code = 0xadff2f },
550 { .name = "grey", .code = 0x808080 },
551 { .name = "honeydew", .code = 0xf0fff0 },
552 { .name = "hotpink", .code = 0xff69b4 },
553 { .name = "indianred", .code = 0xcd5c5c },
554 { .name = "indigo", .code = 0x4b0082 },
555 { .name = "ivory", .code = 0xfffff0 },
556 { .name = "khaki", .code = 0xf0e68c },
557 { .name = "lavender", .code = 0xe6e6fa },
558 { .name = "lavenderblush", .code = 0xfff0f5 },
559 { .name = "lawngreen", .code = 0x7cfc00 },
560 { .name = "lemonchiffon", .code = 0xfffacd },
561 { .name = "lightblue", .code = 0xadd8e6 },
562 { .name = "lightcoral", .code = 0xf08080 },
563 { .name = "lightcyan", .code = 0xe0ffff },
564 { .name = "lightgoldenrodyellow", .code = 0xfafad2 },
565 { .name = "lightgray", .code = 0xd3d3d3 },
566 { .name = "lightgreen", .code = 0x90ee90 },
567 { .name = "lightgrey", .code = 0xd3d3d3 },
568 { .name = "lightpink", .code = 0xffb6c1 },
569 { .name = "lightsalmon", .code = 0xffa07a },
570 { .name = "lightseagreen", .code = 0x20b2aa },
571 { .name = "lightskyblue", .code = 0x87cefa },
572 { .name = "lightslategray", .code = 0x778899 },
573 { .name = "lightslategrey", .code = 0x778899 },
574 { .name = "lightsteelblue", .code = 0xb0c4de },
575 { .name = "lightyellow", .code = 0xffffe0 },
576 { .name = "lime", .code = 0x00ff00 },
577 { .name = "limegreen", .code = 0x32cd32 },
578 { .name = "linen", .code = 0xfaf0e6 },
579 { .name = "magenta", .code = 0xff00ff },
580 { .name = "maroon", .code = 0x800000 },
581 { .name = "mediumaquamarine", .code = 0x66cdaa },
582 { .name = "mediumblue", .code = 0x0000cd },
583 { .name = "mediumorchid", .code = 0xba55d3 },
584 { .name = "mediumpurple", .code = 0x9370db },
585 { .name = "mediumseagreen", .code = 0x3cb371 },
586 { .name = "mediumslateblue", .code = 0x7b68ee },
587 { .name = "mediumspringgreen", .code = 0x00fa9a },
588 { .name = "mediumturquoise", .code = 0x48d1cc },
589 { .name = "mediumvioletred", .code = 0xc71585 },
590 { .name = "midnightblue", .code = 0x191970 },
591 { .name = "mintcream", .code = 0xf5fffa },
592 { .name = "mistyrose", .code = 0xffe4e1 },
593 { .name = "moccasin", .code = 0xffe4b5 },
594 { .name = "navajowhite", .code = 0xffdead },
595 { .name = "navy", .code = 0x000080 },
596 { .name = "oldlace", .code = 0xfdf5e6 },
597 { .name = "olive", .code = 0x808000 },
598 { .name = "olivedrab", .code = 0x6b8e23 },
599 { .name = "orange", .code = 0xffa500 },
600 { .name = "orangered", .code = 0xff4500 },
601 { .name = "orchid", .code = 0xda70d6 },
602 { .name = "palegoldenrod", .code = 0xeee8aa },
603 { .name = "palegreen", .code = 0x98fb98 },
604 { .name = "paleturquoise", .code = 0xafeeee },
605 { .name = "palevioletred", .code = 0xdb7093 },
606 { .name = "papayawhip", .code = 0xffefd5 },
607 { .name = "peachpuff", .code = 0xffdab9 },
608 { .name = "peru", .code = 0xcd853f },
609 { .name = "pink", .code = 0xffc0cb },
610 { .name = "plum", .code = 0xdda0dd },
611 { .name = "powderblue", .code = 0xb0e0e6 },
612 { .name = "purple", .code = 0x800080 },
613 { .name = "red", .code = 0xff0000 },
614 { .name = "rosybrown", .code = 0xbc8f8f },
615 { .name = "royalblue", .code = 0x4169e1 },
616 { .name = "saddlebrown", .code = 0x8b4513 },
617 { .name = "salmon", .code = 0xfa8072 },
618 { .name = "sandybrown", .code = 0xf4a460 },
619 { .name = "seagreen", .code = 0x2e8b57 },
620 { .name = "seashell", .code = 0xfff5ee },
621 { .name = "sienna", .code = 0xa0522d },
622 { .name = "silver", .code = 0xc0c0c0 },
623 { .name = "skyblue", .code = 0x87ceeb },
624 { .name = "slateblue", .code = 0x6a5acd },
625 { .name = "slategray", .code = 0x708090 },
626 { .name = "slategrey", .code = 0x708090 },
627 { .name = "snow", .code = 0xfffafa },
628 { .name = "springgreen", .code = 0x00ff7f },
629 { .name = "steelblue", .code = 0x4682b4 },
630 { .name = "tan", .code = 0xd2b48c },
631 { .name = "teal", .code = 0x008080 },
632 { .name = "thistle", .code = 0xd8bfd8 },
633 { .name = "tomato", .code = 0xff6347 },
634 { .name = "turquoise", .code = 0x40e0d0 },
635 { .name = "violet", .code = 0xee82ee },
636 { .name = "wheat", .code = 0xf5deb3 },
637 { .name = "white", .code = 0xffffff },
638 { .name = "whitesmoke", .code = 0xf5f5f5 },
639 { .name = "yellow", .code = 0xffff00 },
640 { .name = "yellowgreen", .code = 0x9acd32 },
643 static struct hmap color_table = HMAP_INITIALIZER (color_table);
645 if (hmap_is_empty (&color_table))
646 for (size_t i = 0; i < sizeof colors / sizeof *colors; i++)
647 hmap_insert (&color_table, &colors[i].hmap_node,
648 hash_string (colors[i].name, 0));
650 const struct color *color;
651 HMAP_FOR_EACH_WITH_HASH (color, struct color, hmap_node,
652 hash_string (s, 0), &color_table)
653 if (!strcmp (color->name, s))
659 spvxml_attr_parse_color (struct spvxml_node_context *nctx,
660 const struct spvxml_attribute *a)
662 if (!a->value || !strcmp (a->value, "transparent"))
666 if (sscanf (a->value, "#%2x%2x%2x", &r, &g, &b) == 3
667 || sscanf (a->value, "%2x%2x%2x", &r, &g, &b) == 3)
668 return (r << 16) | (g << 8) | b;
670 int code = lookup_color_name (a->value);
674 spvxml_attr_error (nctx, "Attribute %s has unexpected value "
675 "\"%s\" expecting #rrggbb or rrggbb or web color name.",
681 try_strtod (char *s, char **tail, double *real)
683 char *comma = strchr (s, ',');
687 int save_errno = errno;
690 *real = strtod (s, tail);
691 bool ok = errno == 0;
700 spvxml_attr_parse_real (struct spvxml_node_context *nctx,
701 const struct spvxml_attribute *a)
708 if (!try_strtod (a->value, &tail, &real) || *tail)
709 spvxml_attr_error (nctx, "Attribute %s has unexpected value "
710 "\"%s\" expecting real number.", a->name, a->value);
716 spvxml_attr_parse_dimension (struct spvxml_node_context *nctx,
717 const struct spvxml_attribute *a)
724 if (!try_strtod (a->value, &tail, &real))
727 tail += strspn (tail, " \t\r\n");
734 static const struct unit units[] = {
736 /* If you add anything to this table, update the table in
737 doc/dev/spv-file-format.texi also. */
746 /* Device-independent pixels. */
759 for (size_t i = 0; i < sizeof units / sizeof *units; i++)
760 if (!strcmp (units[i].name, tail))
761 return real / units[i].divisor;
765 spvxml_attr_error (nctx, "Attribute %s has unexpected value "
766 "\"%s\" expecting dimension.", a->name, a->value);
771 spvxml_attr_parse_ref (struct spvxml_node_context *nctx UNUSED,
772 const struct spvxml_attribute *a UNUSED)
777 void PRINTF_FORMAT (3, 4)
778 spvxml_content_error (struct spvxml_node_context *nctx, const xmlNode *node,
779 const char *format, ...)
784 struct string s = DS_EMPTY_INITIALIZER;
786 ds_put_cstr (&s, "error parsing content of ");
787 spvxml_format_node_path (nctx->parent, &s);
791 ds_put_format (&s, " at %s", xml_element_type_to_string (node->type));
793 ds_put_format (&s, " \"%s\"", node->name);
796 ds_put_format (&s, " at end of content");
799 va_start (args, format);
800 ds_put_cstr (&s, ": ");
801 ds_put_vformat (&s, format, args);
804 //puts (ds_cstr (&s));
806 nctx->up->error = ds_steal_cstr (&s);
810 spvxml_content_parse_element (struct spvxml_node_context *nctx,
812 const char *elem_name, xmlNode **outp)
814 xmlNode *node = *nodep;
817 if (node->type == XML_ELEMENT_NODE
818 && (!strcmp (CHAR_CAST (char *, node->name), elem_name)
819 || !strcmp (elem_name, "any")))
825 else if (node->type != XML_COMMENT_NODE)
831 spvxml_content_error (nctx, node, "\"%s\" element expected.", elem_name);
837 spvxml_content_parse_text (struct spvxml_node_context *nctx UNUSED, xmlNode **nodep,
840 struct string text = DS_EMPTY_INITIALIZER;
842 xmlNode *node = *nodep;
845 if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE)
847 char *segment = CHAR_CAST (char *, xmlNodeGetContent (node));
850 text.ss = ss_cstr (segment);
851 text.capacity = text.ss.length;
855 ds_put_cstr (&text, segment);
859 else if (node->type != XML_COMMENT_NODE)
866 *textp = ds_steal_cstr (&text);
872 spvxml_content_parse_end (struct spvxml_node_context *nctx, xmlNode *node)
878 else if (node->type != XML_COMMENT_NODE)
884 struct string s = DS_EMPTY_INITIALIZER;
886 for (int i = 0; i < 4 && node; i++, node = node->next)
889 ds_put_cstr (&s, ", ");
890 ds_put_cstr (&s, xml_element_type_to_string (node->type));
892 ds_put_format (&s, " \"%s\"", node->name);
895 ds_put_format (&s, ", ...");
897 spvxml_content_error (nctx, node, "Extra content found expecting end: %s",