X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Foutput%2Fspv%2Fspvxml-helpers.c;fp=src%2Foutput%2Fspv%2Fspvxml-helpers.c;h=f29431668572bf07335ab0babdc07a14b16ad534;hb=bcaaee5f0bd21f443c8dcb5f67114e63d43673af;hp=0000000000000000000000000000000000000000;hpb=1abd7f599dd0d773add0a98fa3b612bc15aaf422;p=pspp diff --git a/src/output/spv/spvxml-helpers.c b/src/output/spv/spvxml-helpers.c new file mode 100644 index 0000000000..f294316685 --- /dev/null +++ b/src/output/spv/spvxml-helpers.c @@ -0,0 +1,873 @@ +/* PSPP - a program for statistical analysis. + Copyright (C) 2018 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#include "output/spv/spvxml-helpers.h" + +#include +#include +#include + +#include "libpspp/cast.h" +#include "libpspp/compiler.h" +#include "libpspp/hash-functions.h" +#include "libpspp/str.h" + +#include "gl/xvasprintf.h" + +char * WARN_UNUSED_RESULT +spvxml_context_finish (struct spvxml_context *ctx, struct spvxml_node *root) +{ + if (!ctx->error) + root->class_->spvxml_node_collect_ids (ctx, root); + if (!ctx->error) + root->class_->spvxml_node_resolve_refs (ctx, root); + + hmap_destroy (&ctx->id_map); + + return ctx->error; +} + +void +spvxml_node_context_uninit (struct spvxml_node_context *nctx) +{ + for (struct spvxml_attribute *a = nctx->attrs; + a < &nctx->attrs[nctx->n_attrs]; a++) + free (a->value); +} + +static const char * +xml_element_type_to_string (xmlElementType type) +{ + switch (type) + { + case XML_ELEMENT_NODE: return "element"; + case XML_ATTRIBUTE_NODE: return "attribute"; + case XML_TEXT_NODE: return "text"; + case XML_CDATA_SECTION_NODE: return "CDATA section"; + case XML_ENTITY_REF_NODE: return "entity reference"; + case XML_ENTITY_NODE: return "entity"; + case XML_PI_NODE: return "PI"; + case XML_COMMENT_NODE: return "comment"; + case XML_DOCUMENT_NODE: return "document"; + case XML_DOCUMENT_TYPE_NODE: return "document type"; + case XML_DOCUMENT_FRAG_NODE: return "document fragment"; + case XML_NOTATION_NODE: return "notation"; + case XML_HTML_DOCUMENT_NODE: return "HTML document"; + case XML_DTD_NODE: return "DTD"; + case XML_ELEMENT_DECL: return "element declaration"; + case XML_ATTRIBUTE_DECL: return "attribute declaration"; + case XML_ENTITY_DECL: return "entity declaration"; + case XML_NAMESPACE_DECL: return "namespace declaration"; + case XML_XINCLUDE_START: return "XInclude start"; + case XML_XINCLUDE_END: return "XInclude end"; + case XML_DOCB_DOCUMENT_NODE: return "docb document"; + default: return ""; + } +} + +static void +spvxml_format_node_path (const xmlNode *node, struct string *s) +{ + enum { MAX_STACK = 32 }; + const xmlNode *stack[MAX_STACK]; + size_t n = 0; + + while (node != NULL && node->type != XML_DOCUMENT_NODE && n < MAX_STACK) + { + stack[n++] = node; + node = node->parent; + } + + while (n > 0) + { + node = stack[--n]; + ds_put_byte (s, '/'); + if (node->name) + ds_put_cstr (s, CHAR_CAST (char *, node->name)); + if (node->type == XML_ELEMENT_NODE) + { + if (node->parent) + { + size_t total = 1; + size_t index = 1; + for (const xmlNode *sibling = node->parent->children; + sibling; sibling = sibling->next) + { + if (sibling == node) + index = total; + else if (sibling->type == XML_ELEMENT_NODE + && !strcmp (CHAR_CAST (char *, sibling->name), + CHAR_CAST (char *, node->name))) + total++; + } + if (total > 1) + ds_put_format (s, "[%zu]", index); + } + } + else + ds_put_format (s, "(%s)", xml_element_type_to_string (node->type)); + } +} + +static struct spvxml_node * +spvxml_node_find (struct spvxml_context *ctx, const char *name, + unsigned int hash) +{ + struct spvxml_node *node; + HMAP_FOR_EACH_WITH_HASH (node, struct spvxml_node, id_node, hash, + &ctx->id_map) + if (!strcmp (node->id, name)) + return node; + + return NULL; +} + +void +spvxml_node_collect_id (struct spvxml_context *ctx, struct spvxml_node *node) +{ + if (!node->id) + return; + + unsigned int hash = hash_string (node->id, 0); + struct spvxml_node *other = spvxml_node_find (ctx, node->id, hash); + if (other) + { + if (!ctx->error) + { + struct string node_path = DS_EMPTY_INITIALIZER; + spvxml_format_node_path (node->raw, &node_path); + + struct string other_path = DS_EMPTY_INITIALIZER; + spvxml_format_node_path (other->raw, &other_path); + + ctx->error = xasprintf ("Nodes %s and %s both have ID \"%s\".", + ds_cstr (&node_path), + ds_cstr (&other_path), node->id); + + ds_destroy (&node_path); + ds_destroy (&other_path); + } + + return; + } + + hmap_insert (&ctx->id_map, &node->id_node, hash); +} + +struct spvxml_node * +spvxml_node_resolve_ref (struct spvxml_context *ctx, + const xmlNode *src, const char *attr_name, + const struct spvxml_node_class *const *classes, + size_t n) +{ + char *dst_id = CHAR_CAST ( + char *, xmlGetProp (CONST_CAST (xmlNode *, src), + CHAR_CAST (xmlChar *, attr_name))); + if (!dst_id) + return NULL; + + struct spvxml_node *dst = spvxml_node_find (ctx, dst_id, + hash_string (dst_id, 0)); + if (!dst) + { + struct string node_path = DS_EMPTY_INITIALIZER; + spvxml_format_node_path (src, &node_path); + + ctx->error = xasprintf ( + "%s: Attribute %s has unknown target ID \"%s\".", + ds_cstr (&node_path), attr_name, dst_id); + + ds_destroy (&node_path); + free (dst_id); + return NULL; + } + + if (!n) + { + free (dst_id); + return dst; + } + for (size_t i = 0; i < n; i++) + if (classes[i] == dst->class_) + { + free (dst_id); + return dst; + } + + if (!ctx->error) + { + struct string s = DS_EMPTY_INITIALIZER; + spvxml_format_node_path (src, &s); + + ds_put_format (&s, ": Attribute \"%s\" should refer to a \"%s\"", + attr_name, classes[0]->name); + if (n == 2) + ds_put_format (&s, " or \"%s\"", classes[1]->name); + else if (n > 2) + { + for (size_t i = 1; i < n - 1; i++) + ds_put_format (&s, ", \"%s\"", classes[i]->name); + ds_put_format (&s, ", or \"%s\"", classes[n - 1]->name); + } + ds_put_format (&s, " element, but its target ID \"%s\" " + "actually refers to a \"%s\" element.", + dst_id, dst->class_->name); + + ctx->error = ds_steal_cstr (&s); + } + + free (dst_id); + return NULL; +} + +void PRINTF_FORMAT (2, 3) +spvxml_attr_error (struct spvxml_node_context *nctx, const char *format, ...) +{ + if (nctx->up->error) + return; + + struct string s = DS_EMPTY_INITIALIZER; + ds_put_cstr (&s, "error parsing attributes of "); + spvxml_format_node_path (nctx->parent, &s); + + va_list args; + va_start (args, format); + ds_put_cstr (&s, ": "); + ds_put_vformat (&s, format, args); + va_end (args); + + nctx->up->error = ds_steal_cstr (&s); +} + +/* xmlGetPropNodeValueInternal() is from tree.c in libxml2 2.9.4+dfsg1, which + is covered by the following copyright and license: + + Except where otherwise noted in the source code (e.g. the files hash.c, + list.c and the trio files, which are covered by a similar licence but with + different Copyright notices) all the files are: + + Copyright (C) 1998-2012 Daniel Veillard. All Rights Reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + fur- nished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FIT- NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. +*/ +static xmlChar* +xmlGetPropNodeValueInternal(const xmlAttr *prop) +{ + if (prop == NULL) + return(NULL); + if (prop->type == XML_ATTRIBUTE_NODE) { + /* + * Note that we return at least the empty string. + * TODO: Do we really always want that? + */ + if (prop->children != NULL) { + if ((prop->children->next == NULL) && + ((prop->children->type == XML_TEXT_NODE) || + (prop->children->type == XML_CDATA_SECTION_NODE))) + { + /* + * Optimization for the common case: only 1 text node. + */ + return(xmlStrdup(prop->children->content)); + } else { + xmlChar *ret; + + ret = xmlNodeListGetString(prop->doc, prop->children, 1); + if (ret != NULL) + return(ret); + } + } + return(xmlStrdup((xmlChar *)"")); + } else if (prop->type == XML_ATTRIBUTE_DECL) { + return(xmlStrdup(((xmlAttributePtr)prop)->defaultValue)); + } + return(NULL); +} + +static struct spvxml_attribute * +find_attribute (struct spvxml_node_context *nctx, const char *name) +{ + /* XXX This is linear search but we could use binary search. */ + for (struct spvxml_attribute *a = nctx->attrs; + a < &nctx->attrs[nctx->n_attrs]; a++) + if (!strcmp (a->name, name)) + return a; + + return NULL; +} + +static void +format_attribute (struct string *s, const xmlAttr *attr) +{ + const char *name = CHAR_CAST (char *, attr->name); + char *value = CHAR_CAST (char *, xmlGetPropNodeValueInternal (attr)); + ds_put_format (s, "%s=\"%s\"", name, value); + free (value); +} + +void +spvxml_parse_attributes (struct spvxml_node_context *nctx) +{ + for (const xmlAttr *node = nctx->parent->properties; node; node = node->next) + { + const char *node_name = CHAR_CAST (char *, node->name); + struct spvxml_attribute *a = find_attribute (nctx, node_name); + if (!a) + { + if (!strcmp (node_name, "id")) + continue; + + struct string unexpected = DS_EMPTY_INITIALIZER; + format_attribute (&unexpected, node); + int n = 1; + + for (node = node->next; node; node = node->next) + { + node_name = CHAR_CAST (char *, node->name); + if (!find_attribute (nctx, node_name) + && strcmp (node_name, "id")) + { + ds_put_byte (&unexpected, ' '); + format_attribute (&unexpected, node); + n++; + } + } + + spvxml_attr_error (nctx, "Node has unexpected attribute%s: %s", + n > 1 ? "s" : "", ds_cstr (&unexpected)); + ds_destroy (&unexpected); + return; + } + if (a->value) + { + spvxml_attr_error (nctx, "Duplicate attribute \"%s\".", a->name); + return; + } + a->value = CHAR_CAST (char *, xmlGetPropNodeValueInternal (node)); + } + + for (struct spvxml_attribute *a = nctx->attrs; + a < &nctx->attrs[nctx->n_attrs]; a++) + { + if (a->required && !a->value) + spvxml_attr_error (nctx, "Missing required attribute \"%s\".", + a->name); + return; + } +} + +int +spvxml_attr_parse_enum (struct spvxml_node_context *nctx, + const struct spvxml_attribute *a, + const struct spvxml_enum enums[]) +{ + if (!a->value) + return 0; + + for (const struct spvxml_enum *e = enums; e->name; e++) + if (!strcmp (a->value, e->name)) + return e->value; + + for (const struct spvxml_enum *e = enums; e->name; e++) + if (!strcmp (e->name, "OTHER")) + return e->value; + + spvxml_attr_error (nctx, "Attribute %s has unexpected value \"%s\".", + a->name, a->value); + return 0; +} + +int +spvxml_attr_parse_bool (struct spvxml_node_context *nctx, + const struct spvxml_attribute *a) +{ + static const struct spvxml_enum bool_enums[] = { + { "true", 1 }, + { "false", 0 }, + { NULL, 0 }, + }; + + return !a->value ? -1 : spvxml_attr_parse_enum (nctx, a, bool_enums); +} + +bool +spvxml_attr_parse_fixed (struct spvxml_node_context *nctx, + const struct spvxml_attribute *a, + const char *attr_value) +{ + const struct spvxml_enum fixed_enums[] = { + { attr_value, true }, + { NULL, 0 }, + }; + + return spvxml_attr_parse_enum (nctx, a, fixed_enums); +} + +int +spvxml_attr_parse_int (struct spvxml_node_context *nctx, + const struct spvxml_attribute *a) +{ + if (!a->value) + return INT_MIN; + + char *tail = NULL; + int save_errno = errno; + errno = 0; + long int integer = strtol (a->value, &tail, 10); + if (errno || *tail || integer <= INT_MIN || integer > INT_MAX) + { + spvxml_attr_error (nctx, "Attribute %s has unexpected value " + "\"%s\" expecting small integer.", a->name, a->value); + integer = INT_MIN; + } + errno = save_errno; + + return integer; +} + +static int +lookup_color_name (const char *s) +{ + struct color + { + struct hmap_node hmap_node; + const char *name; + int code; + }; + + static struct color colors[] = + { + { .name = "aliceblue", .code = 0xf0f8ff }, + { .name = "antiquewhite", .code = 0xfaebd7 }, + { .name = "aqua", .code = 0x00ffff }, + { .name = "aquamarine", .code = 0x7fffd4 }, + { .name = "azure", .code = 0xf0ffff }, + { .name = "beige", .code = 0xf5f5dc }, + { .name = "bisque", .code = 0xffe4c4 }, + { .name = "black", .code = 0x000000 }, + { .name = "blanchedalmond", .code = 0xffebcd }, + { .name = "blue", .code = 0x0000ff }, + { .name = "blueviolet", .code = 0x8a2be2 }, + { .name = "brown", .code = 0xa52a2a }, + { .name = "burlywood", .code = 0xdeb887 }, + { .name = "cadetblue", .code = 0x5f9ea0 }, + { .name = "chartreuse", .code = 0x7fff00 }, + { .name = "chocolate", .code = 0xd2691e }, + { .name = "coral", .code = 0xff7f50 }, + { .name = "cornflowerblue", .code = 0x6495ed }, + { .name = "cornsilk", .code = 0xfff8dc }, + { .name = "crimson", .code = 0xdc143c }, + { .name = "cyan", .code = 0x00ffff }, + { .name = "darkblue", .code = 0x00008b }, + { .name = "darkcyan", .code = 0x008b8b }, + { .name = "darkgoldenrod", .code = 0xb8860b }, + { .name = "darkgray", .code = 0xa9a9a9 }, + { .name = "darkgreen", .code = 0x006400 }, + { .name = "darkgrey", .code = 0xa9a9a9 }, + { .name = "darkkhaki", .code = 0xbdb76b }, + { .name = "darkmagenta", .code = 0x8b008b }, + { .name = "darkolivegreen", .code = 0x556b2f }, + { .name = "darkorange", .code = 0xff8c00 }, + { .name = "darkorchid", .code = 0x9932cc }, + { .name = "darkred", .code = 0x8b0000 }, + { .name = "darksalmon", .code = 0xe9967a }, + { .name = "darkseagreen", .code = 0x8fbc8f }, + { .name = "darkslateblue", .code = 0x483d8b }, + { .name = "darkslategray", .code = 0x2f4f4f }, + { .name = "darkslategrey", .code = 0x2f4f4f }, + { .name = "darkturquoise", .code = 0x00ced1 }, + { .name = "darkviolet", .code = 0x9400d3 }, + { .name = "deeppink", .code = 0xff1493 }, + { .name = "deepskyblue", .code = 0x00bfff }, + { .name = "dimgray", .code = 0x696969 }, + { .name = "dimgrey", .code = 0x696969 }, + { .name = "dodgerblue", .code = 0x1e90ff }, + { .name = "firebrick", .code = 0xb22222 }, + { .name = "floralwhite", .code = 0xfffaf0 }, + { .name = "forestgreen", .code = 0x228b22 }, + { .name = "fuchsia", .code = 0xff00ff }, + { .name = "gainsboro", .code = 0xdcdcdc }, + { .name = "ghostwhite", .code = 0xf8f8ff }, + { .name = "gold", .code = 0xffd700 }, + { .name = "goldenrod", .code = 0xdaa520 }, + { .name = "gray", .code = 0x808080 }, + { .name = "green", .code = 0x008000 }, + { .name = "greenyellow", .code = 0xadff2f }, + { .name = "grey", .code = 0x808080 }, + { .name = "honeydew", .code = 0xf0fff0 }, + { .name = "hotpink", .code = 0xff69b4 }, + { .name = "indianred", .code = 0xcd5c5c }, + { .name = "indigo", .code = 0x4b0082 }, + { .name = "ivory", .code = 0xfffff0 }, + { .name = "khaki", .code = 0xf0e68c }, + { .name = "lavender", .code = 0xe6e6fa }, + { .name = "lavenderblush", .code = 0xfff0f5 }, + { .name = "lawngreen", .code = 0x7cfc00 }, + { .name = "lemonchiffon", .code = 0xfffacd }, + { .name = "lightblue", .code = 0xadd8e6 }, + { .name = "lightcoral", .code = 0xf08080 }, + { .name = "lightcyan", .code = 0xe0ffff }, + { .name = "lightgoldenrodyellow", .code = 0xfafad2 }, + { .name = "lightgray", .code = 0xd3d3d3 }, + { .name = "lightgreen", .code = 0x90ee90 }, + { .name = "lightgrey", .code = 0xd3d3d3 }, + { .name = "lightpink", .code = 0xffb6c1 }, + { .name = "lightsalmon", .code = 0xffa07a }, + { .name = "lightseagreen", .code = 0x20b2aa }, + { .name = "lightskyblue", .code = 0x87cefa }, + { .name = "lightslategray", .code = 0x778899 }, + { .name = "lightslategrey", .code = 0x778899 }, + { .name = "lightsteelblue", .code = 0xb0c4de }, + { .name = "lightyellow", .code = 0xffffe0 }, + { .name = "lime", .code = 0x00ff00 }, + { .name = "limegreen", .code = 0x32cd32 }, + { .name = "linen", .code = 0xfaf0e6 }, + { .name = "magenta", .code = 0xff00ff }, + { .name = "maroon", .code = 0x800000 }, + { .name = "mediumaquamarine", .code = 0x66cdaa }, + { .name = "mediumblue", .code = 0x0000cd }, + { .name = "mediumorchid", .code = 0xba55d3 }, + { .name = "mediumpurple", .code = 0x9370db }, + { .name = "mediumseagreen", .code = 0x3cb371 }, + { .name = "mediumslateblue", .code = 0x7b68ee }, + { .name = "mediumspringgreen", .code = 0x00fa9a }, + { .name = "mediumturquoise", .code = 0x48d1cc }, + { .name = "mediumvioletred", .code = 0xc71585 }, + { .name = "midnightblue", .code = 0x191970 }, + { .name = "mintcream", .code = 0xf5fffa }, + { .name = "mistyrose", .code = 0xffe4e1 }, + { .name = "moccasin", .code = 0xffe4b5 }, + { .name = "navajowhite", .code = 0xffdead }, + { .name = "navy", .code = 0x000080 }, + { .name = "oldlace", .code = 0xfdf5e6 }, + { .name = "olive", .code = 0x808000 }, + { .name = "olivedrab", .code = 0x6b8e23 }, + { .name = "orange", .code = 0xffa500 }, + { .name = "orangered", .code = 0xff4500 }, + { .name = "orchid", .code = 0xda70d6 }, + { .name = "palegoldenrod", .code = 0xeee8aa }, + { .name = "palegreen", .code = 0x98fb98 }, + { .name = "paleturquoise", .code = 0xafeeee }, + { .name = "palevioletred", .code = 0xdb7093 }, + { .name = "papayawhip", .code = 0xffefd5 }, + { .name = "peachpuff", .code = 0xffdab9 }, + { .name = "peru", .code = 0xcd853f }, + { .name = "pink", .code = 0xffc0cb }, + { .name = "plum", .code = 0xdda0dd }, + { .name = "powderblue", .code = 0xb0e0e6 }, + { .name = "purple", .code = 0x800080 }, + { .name = "red", .code = 0xff0000 }, + { .name = "rosybrown", .code = 0xbc8f8f }, + { .name = "royalblue", .code = 0x4169e1 }, + { .name = "saddlebrown", .code = 0x8b4513 }, + { .name = "salmon", .code = 0xfa8072 }, + { .name = "sandybrown", .code = 0xf4a460 }, + { .name = "seagreen", .code = 0x2e8b57 }, + { .name = "seashell", .code = 0xfff5ee }, + { .name = "sienna", .code = 0xa0522d }, + { .name = "silver", .code = 0xc0c0c0 }, + { .name = "skyblue", .code = 0x87ceeb }, + { .name = "slateblue", .code = 0x6a5acd }, + { .name = "slategray", .code = 0x708090 }, + { .name = "slategrey", .code = 0x708090 }, + { .name = "snow", .code = 0xfffafa }, + { .name = "springgreen", .code = 0x00ff7f }, + { .name = "steelblue", .code = 0x4682b4 }, + { .name = "tan", .code = 0xd2b48c }, + { .name = "teal", .code = 0x008080 }, + { .name = "thistle", .code = 0xd8bfd8 }, + { .name = "tomato", .code = 0xff6347 }, + { .name = "turquoise", .code = 0x40e0d0 }, + { .name = "violet", .code = 0xee82ee }, + { .name = "wheat", .code = 0xf5deb3 }, + { .name = "white", .code = 0xffffff }, + { .name = "whitesmoke", .code = 0xf5f5f5 }, + { .name = "yellow", .code = 0xffff00 }, + { .name = "yellowgreen", .code = 0x9acd32 }, + }; + + static struct hmap color_table = HMAP_INITIALIZER (color_table); + + if (hmap_is_empty (&color_table)) + for (size_t i = 0; i < sizeof colors / sizeof *colors; i++) + hmap_insert (&color_table, &colors[i].hmap_node, + hash_string (colors[i].name, 0)); + + const struct color *color; + HMAP_FOR_EACH_WITH_HASH (color, struct color, hmap_node, + hash_string (s, 0), &color_table) + if (!strcmp (color->name, s)) + return color->code; + return -1; +} + +int +spvxml_attr_parse_color (struct spvxml_node_context *nctx, + const struct spvxml_attribute *a) +{ + if (!a->value || !strcmp (a->value, "transparent")) + return -1; + + int r, g, b; + if (sscanf (a->value, "#%2x%2x%2x", &r, &g, &b) == 3 + || sscanf (a->value, "%2x%2x%2x", &r, &g, &b) == 3) + return (r << 16) | (g << 8) | b; + + int code = lookup_color_name (a->value); + if (code >= 0) + return code; + + spvxml_attr_error (nctx, "Attribute %s has unexpected value " + "\"%s\" expecting #rrggbb or rrggbb or web color name.", + a->name, a->value); + return 0; +} + +static bool +try_strtod (char *s, char **tail, double *real) +{ + char *comma = strchr (s, ','); + if (comma) + *comma = '.'; + + int save_errno = errno; + errno = 0; + *tail = NULL; + *real = strtod (s, tail); + bool ok = errno == 0; + errno = save_errno; + + if (!ok) + *real = DBL_MAX; + return ok; +} + +double +spvxml_attr_parse_real (struct spvxml_node_context *nctx, + const struct spvxml_attribute *a) +{ + if (!a->value) + return DBL_MAX; + + char *tail; + double real; + if (!try_strtod (a->value, &tail, &real) || *tail) + spvxml_attr_error (nctx, "Attribute %s has unexpected value " + "\"%s\" expecting real number.", a->name, a->value); + + return real; +} + +double +spvxml_attr_parse_dimension (struct spvxml_node_context *nctx, + const struct spvxml_attribute *a) +{ + if (!a->value) + return DBL_MAX; + + char *tail; + double real; + if (!try_strtod (a->value, &tail, &real)) + goto error; + + tail += strspn (tail, " \t\r\n"); + + struct unit + { + const char *name; + double divisor; + }; + static const struct unit units[] = { + /* Inches. */ + { "in", 1.0 }, + { "인치", 1.0 }, + + /* Device-independent pixels. */ + { "px", 96.0 }, + + /* Points. */ + { "pt", 72.0 }, + { "пт", 72.0 }, + { "", 72.0 }, + + /* Centimeters. */ + { "cm", 2.54 }, + { "см", 2.54 }, + }; + + for (size_t i = 0; i < sizeof units / sizeof *units; i++) + if (!strcmp (units[i].name, tail)) + return real / units[i].divisor; + goto error; + +error: + spvxml_attr_error (nctx, "Attribute %s has unexpected value " + "\"%s\" expecting dimension.", a->name, a->value); + return DBL_MAX; +} + +struct spvxml_node * +spvxml_attr_parse_ref (struct spvxml_node_context *nctx UNUSED, + const struct spvxml_attribute *a UNUSED) +{ + return NULL; +} + +void PRINTF_FORMAT (3, 4) +spvxml_content_error (struct spvxml_node_context *nctx, const xmlNode *node, + const char *format, ...) +{ + if (nctx->up->error) + return; + + struct string s = DS_EMPTY_INITIALIZER; + + ds_put_cstr (&s, "error parsing content of "); + spvxml_format_node_path (nctx->parent, &s); + + if (node) + { + ds_put_format (&s, " at %s", xml_element_type_to_string (node->type)); + if (node->name) + ds_put_format (&s, " \"%s\"", node->name); + } + else + ds_put_format (&s, " at end of content"); + + va_list args; + va_start (args, format); + ds_put_cstr (&s, ": "); + ds_put_vformat (&s, format, args); + va_end (args); + + //puts (ds_cstr (&s)); + + nctx->up->error = ds_steal_cstr (&s); +} + +bool +spvxml_content_parse_element (struct spvxml_node_context *nctx, + xmlNode **nodep, + const char *elem_name, xmlNode **outp) +{ + xmlNode *node = *nodep; + while (node) + { + if (node->type == XML_ELEMENT_NODE + && (!strcmp (CHAR_CAST (char *, node->name), elem_name) + || !strcmp (elem_name, "any"))) + { + *outp = node; + *nodep = node->next; + return true; + } + else if (node->type != XML_COMMENT_NODE) + break; + + node = node->next; + } + + spvxml_content_error (nctx, node, "\"%s\" element expected.", elem_name); + *outp = NULL; + return false; +} + +bool +spvxml_content_parse_text (struct spvxml_node_context *nctx UNUSED, xmlNode **nodep, + char **textp) +{ + struct string text = DS_EMPTY_INITIALIZER; + + xmlNode *node = *nodep; + while (node) + { + if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE) + { + char *segment = CHAR_CAST (char *, xmlNodeGetContent (node)); + if (!text.ss.string) + { + text.ss = ss_cstr (segment); + text.capacity = text.ss.length; + } + else + { + ds_put_cstr (&text, segment); + free (segment); + } + } + else if (node->type != XML_COMMENT_NODE) + break; + + node = node->next; + } + *nodep = node; + + *textp = ds_steal_cstr (&text); + + return true; +} + +bool +spvxml_content_parse_end (struct spvxml_node_context *nctx, xmlNode *node) +{ + for (;;) + { + if (!node) + return true; + else if (node->type != XML_COMMENT_NODE) + break; + + node = node->next; + } + + struct string s = DS_EMPTY_INITIALIZER; + + for (int i = 0; i < 4 && node; i++, node = node->next) + { + if (i) + ds_put_cstr (&s, ", "); + ds_put_cstr (&s, xml_element_type_to_string (node->type)); + if (node->name) + ds_put_format (&s, " \"%s\"", node->name); + } + if (node) + ds_put_format (&s, ", ..."); + + spvxml_content_error (nctx, node, "Extra content found expecting end: %s", + ds_cstr (&s)); + ds_destroy (&s); + + return false; +} +