d9d05e0d0a2de5957f68192994dac0b24c603b63
[pspp] / src / output / spv / spvxml-helpers.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2018 Free Software Foundation, Inc.
3
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.
8
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.
13
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/>. */
16
17 #include <config.h>
18
19 #include "output/spv/spvxml-helpers.h"
20
21 #include <errno.h>
22 #include <float.h>
23 #include <string.h>
24
25 #include "libpspp/cast.h"
26 #include "libpspp/compiler.h"
27 #include "libpspp/hash-functions.h"
28 #include "libpspp/str.h"
29
30 #include "gl/c-ctype.h"
31 #include "gl/xvasprintf.h"
32 #include "gl/xalloc.h"
33
34 char * WARN_UNUSED_RESULT
35 spvxml_context_finish (struct spvxml_context *ctx, struct spvxml_node *root)
36 {
37   if (!ctx->error)
38     root->class_->spvxml_node_collect_ids (ctx, root);
39   if (!ctx->error)
40     root->class_->spvxml_node_resolve_refs (ctx, root);
41
42   hmap_destroy (&ctx->id_map);
43
44   return ctx->error;
45 }
46
47 void
48 spvxml_node_context_uninit (struct spvxml_node_context *nctx)
49 {
50   for (struct spvxml_attribute *a = nctx->attrs;
51        a < &nctx->attrs[nctx->n_attrs]; a++)
52     free (a->value);
53 }
54
55 static const char *
56 xml_element_type_to_string (xmlElementType type)
57 {
58   switch (type)
59     {
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>";
82     }
83 }
84
85 static void
86 spvxml_format_node_path (const xmlNode *node, struct string *s)
87 {
88   enum { MAX_STACK = 32 };
89   const xmlNode *stack[MAX_STACK];
90   size_t n = 0;
91
92   while (node != NULL && node->type != XML_DOCUMENT_NODE && n < MAX_STACK)
93     {
94       stack[n++] = node;
95       node = node->parent;
96     }
97
98   while (n > 0)
99     {
100       node = stack[--n];
101       ds_put_byte (s, '/');
102       if (node->name)
103         ds_put_cstr (s, CHAR_CAST (char *, node->name));
104       if (node->type == XML_ELEMENT_NODE)
105         {
106           if (node->parent)
107             {
108               size_t total = 1;
109               size_t index = 1;
110               for (const xmlNode *sibling = node->parent->children;
111                    sibling; sibling = sibling->next)
112                 {
113                   if (sibling == node)
114                     index = total;
115                   else if (sibling->type == XML_ELEMENT_NODE
116                            && !strcmp (CHAR_CAST (char *, sibling->name),
117                                        CHAR_CAST (char *, node->name)))
118                     total++;
119                 }
120               if (total > 1)
121                 ds_put_format (s, "[%zu]", index);
122             }
123         }
124       else
125         ds_put_format (s, "(%s)", xml_element_type_to_string (node->type));
126     }
127 }
128
129 static struct spvxml_node *
130 spvxml_node_find (struct spvxml_context *ctx, const char *name,
131                   unsigned int hash)
132 {
133   struct spvxml_node *node;
134   HMAP_FOR_EACH_WITH_HASH (node, struct spvxml_node, id_node, hash,
135                            &ctx->id_map)
136     if (!strcmp (node->id, name))
137       return node;
138
139   return NULL;
140 }
141
142 void
143 spvxml_node_collect_id (struct spvxml_context *ctx, struct spvxml_node *node)
144 {
145   if (!node->id)
146     return;
147
148   unsigned int hash = hash_string (node->id, 0);
149   struct spvxml_node *other = spvxml_node_find (ctx, node->id, hash);
150   if (other)
151     {
152       if (!ctx->error)
153         {
154           struct string node_path = DS_EMPTY_INITIALIZER;
155           spvxml_format_node_path (node->raw, &node_path);
156
157           struct string other_path = DS_EMPTY_INITIALIZER;
158           spvxml_format_node_path (other->raw, &other_path);
159
160           ctx->error = xasprintf ("Nodes %s and %s both have ID \"%s\".",
161                                   ds_cstr (&node_path),
162                                   ds_cstr (&other_path), node->id);
163
164           ds_destroy (&node_path);
165           ds_destroy (&other_path);
166         }
167
168       return;
169     }
170
171   hmap_insert (&ctx->id_map, &node->id_node, hash);
172 }
173
174 struct spvxml_node *
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,
178                          size_t n)
179 {
180   char *dst_id = CHAR_CAST (
181     char *, xmlGetProp (CONST_CAST (xmlNode *, src),
182                         CHAR_CAST (xmlChar *, attr_name)));
183   if (!dst_id)
184     return NULL;
185
186   struct spvxml_node *dst = spvxml_node_find (ctx, dst_id,
187                                               hash_string (dst_id, 0));
188   if (!dst)
189     {
190       struct string node_path = DS_EMPTY_INITIALIZER;
191       spvxml_format_node_path (src, &node_path);
192
193       ctx->error = xasprintf (
194         "%s: Attribute %s has unknown target ID \"%s\".",
195         ds_cstr (&node_path), attr_name, dst_id);
196
197       ds_destroy (&node_path);
198       free (dst_id);
199       return NULL;
200     }
201
202   if (!n)
203     {
204       free (dst_id);
205       return dst;
206     }
207   for (size_t i = 0; i < n; i++)
208     if (classes[i] == dst->class_)
209       {
210         free (dst_id);
211         return dst;
212       }
213
214   if (!ctx->error)
215     {
216       struct string s = DS_EMPTY_INITIALIZER;
217       spvxml_format_node_path (src, &s);
218
219       ds_put_format (&s, ": Attribute \"%s\" should refer to a \"%s\"",
220                      attr_name, classes[0]->name);
221       if (n == 2)
222         ds_put_format (&s, " or \"%s\"", classes[1]->name);
223       else if (n > 2)
224         {
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);
228         }
229       ds_put_format (&s, " element, but its target ID \"%s\" "
230                      "actually refers to a \"%s\" element.",
231                      dst_id, dst->class_->name);
232
233       ctx->error = ds_steal_cstr (&s);
234     }
235
236   free (dst_id);
237   return NULL;
238 }
239
240 void PRINTF_FORMAT (2, 3)
241 spvxml_attr_error (struct spvxml_node_context *nctx, const char *format, ...)
242 {
243   if (nctx->up->error)
244     return;
245
246   struct string s = DS_EMPTY_INITIALIZER;
247   ds_put_cstr (&s, "error parsing attributes of ");
248   spvxml_format_node_path (nctx->parent, &s);
249
250   va_list args;
251   va_start (args, format);
252   ds_put_cstr (&s, ": ");
253   ds_put_vformat (&s, format, args);
254   va_end (args);
255
256   nctx->up->error = ds_steal_cstr (&s);
257 }
258
259 /* xmlGetPropNodeValueInternal() is from tree.c in libxml2 2.9.4+dfsg1, which
260    is covered by the following copyright and license:
261
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:
265
266    Copyright (C) 1998-2012 Daniel Veillard.  All Rights Reserved.
267
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:
274
275    The above copyright notice and this permission notice shall be included in
276    all copies or substantial portions of the Software.
277
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
284    IN THE SOFTWARE.
285 */
286 static xmlChar*
287 xmlGetPropNodeValueInternal(const xmlAttr *prop)
288 {
289     if (prop == NULL)
290         return(NULL);
291     if (prop->type == XML_ATTRIBUTE_NODE) {
292         /*
293         * Note that we return at least the empty string.
294         *   TODO: Do we really always want that?
295         */
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)))
300             {
301                 /*
302                 * Optimization for the common case: only 1 text node.
303                 */
304                 return(xmlStrdup(prop->children->content));
305             } else {
306                 xmlChar *ret;
307
308                 ret = xmlNodeListGetString(prop->doc, prop->children, 1);
309                 if (ret != NULL)
310                     return(ret);
311             }
312         }
313         return(xmlStrdup((xmlChar *)""));
314     } else if (prop->type == XML_ATTRIBUTE_DECL) {
315         return(xmlStrdup(((xmlAttributePtr)prop)->defaultValue));
316     }
317     return(NULL);
318 }
319
320 /* Returns true if S consists entirely of digits (and at least one digit) */
321 static bool
322 all_digits (const char *s)
323 {
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'));
327 }
328
329 static struct spvxml_attribute *
330 find_attribute (struct spvxml_node_context *nctx, const char *name)
331 {
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++)
335     {
336       if (!strcmp (a->name, name))
337         return a;
338
339       if (!a->multi)
340         continue;
341
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)))
348         return a;
349     }
350
351   return NULL;
352 }
353
354 static void
355 format_attribute (struct string *s, const xmlAttr *attr)
356 {
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);
360   free (value);
361 }
362
363 void
364 spvxml_parse_attributes (struct spvxml_node_context *nctx)
365 {
366   for (const xmlAttr *node = nctx->parent->properties; node; node = node->next)
367     {
368       const char *node_name = CHAR_CAST (char *, node->name);
369       struct spvxml_attribute *a = find_attribute (nctx, node_name);
370       if (!a)
371         {
372           if (!strcmp (node_name, "id"))
373             continue;
374
375           struct string unexpected = DS_EMPTY_INITIALIZER;
376           format_attribute (&unexpected, node);
377           int n = 1;
378
379           for (node = node->next; node; node = node->next)
380             {
381               node_name = CHAR_CAST (char *, node->name);
382               if (!find_attribute (nctx, node_name)
383                   && strcmp (node_name, "id"))
384                 {
385                   ds_put_byte (&unexpected, ' ');
386                   format_attribute (&unexpected, node);
387                   n++;
388                 }
389             }
390
391           spvxml_attr_error (nctx, "Node has unexpected attribute%s: %s",
392                              n > 1 ? "s" : "", ds_cstr (&unexpected));
393           ds_destroy (&unexpected);
394           return;
395         }
396       if (a->value)
397         {
398           if (a->name[strlen (a->name) - 1] != '#'
399               && a->name[strlen (a->name) - 1] != '%')
400             {
401               spvxml_attr_error (nctx, "Duplicate attribute \"%s\".", a->name);
402               return;
403             }
404           free (a->value);
405         }
406       a->value = CHAR_CAST (char *, xmlGetPropNodeValueInternal (node));
407     }
408
409   for (struct spvxml_attribute *a = nctx->attrs;
410        a < &nctx->attrs[nctx->n_attrs]; a++)
411     {
412       if (a->required && !a->value)
413         spvxml_attr_error (nctx, "Missing required attribute \"%s\".",
414                            a->name);
415       return;
416     }
417 }
418
419 int
420 spvxml_attr_parse_enum (struct spvxml_node_context *nctx,
421                         const struct spvxml_attribute *a,
422                         const struct spvxml_enum enums[])
423 {
424   if (!a->value)
425     return 0;
426
427   for (const struct spvxml_enum *e = enums; e->name; e++)
428     if (!strcmp (a->value, e->name))
429       return e->value;
430
431   for (const struct spvxml_enum *e = enums; e->name; e++)
432     if (!strcmp (e->name, "OTHER"))
433       return e->value;
434
435   spvxml_attr_error (nctx, "Attribute %s has unexpected value \"%s\".",
436                 a->name, a->value);
437   return 0;
438 }
439
440 int
441 spvxml_attr_parse_bool (struct spvxml_node_context *nctx,
442                         const struct spvxml_attribute *a)
443 {
444   static const struct spvxml_enum bool_enums[] = {
445     { "true", 1 },
446     { "false", 0 },
447     { NULL, 0 },
448   };
449
450   return !a->value ? -1 : spvxml_attr_parse_enum (nctx, a, bool_enums);
451 }
452
453 bool
454 spvxml_attr_parse_fixed (struct spvxml_node_context *nctx,
455                          const struct spvxml_attribute *a,
456                          const char *attr_value)
457 {
458   const struct spvxml_enum fixed_enums[] = {
459     { attr_value, true },
460     { NULL, 0 },
461   };
462
463   return spvxml_attr_parse_enum (nctx, a, fixed_enums);
464 }
465
466 int
467 spvxml_attr_parse_int (struct spvxml_node_context *nctx,
468                        const struct spvxml_attribute *a)
469 {
470   if (!a->value)
471     return INT_MIN;
472
473   char *tail = NULL;
474   int save_errno = errno;
475   errno = 0;
476   long int integer = strtol (a->value, &tail, 10);
477   if (errno || *tail || integer <= INT_MIN || integer > INT_MAX)
478     {
479       spvxml_attr_error (nctx, "Attribute %s has unexpected value "
480                          "\"%s\" expecting small integer.", a->name, a->value);
481       integer = INT_MIN;
482     }
483   errno = save_errno;
484
485   return integer;
486 }
487
488 static int
489 lookup_color_name (const char *s)
490 {
491   struct color
492     {
493       struct hmap_node hmap_node;
494       const char *name;
495       int code;
496     };
497
498   static struct color colors[] =
499     {
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 },
647     };
648
649   static struct hmap color_table = HMAP_INITIALIZER (color_table);
650
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));
655
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))
660       return color->code;
661   return -1;
662 }
663
664 int
665 spvxml_attr_parse_color (struct spvxml_node_context *nctx,
666                          const struct spvxml_attribute *a)
667 {
668   if (!a->value || !strcmp (a->value, "transparent"))
669     return -1;
670
671   int r, g, b;
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;
675
676   int code = lookup_color_name (a->value);
677   if (code >= 0)
678     return code;
679
680   spvxml_attr_error (nctx, "Attribute %s has unexpected value "
681                      "\"%s\" expecting #rrggbb or rrggbb or web color name.",
682                      a->name, a->value);
683   return 0;
684 }
685
686 static bool
687 try_strtod (char *s, char **tail, double *real)
688 {
689   char *comma = strchr (s, ',');
690   if (comma)
691     *comma = '.';
692
693   int save_errno = errno;
694   errno = 0;
695   *tail = NULL;
696   *real = strtod (s, tail);
697   bool ok = errno == 0;
698   errno = save_errno;
699
700   if (!ok)
701     *real = DBL_MAX;
702   return ok;
703 }
704
705 double
706 spvxml_attr_parse_real (struct spvxml_node_context *nctx,
707                         const struct spvxml_attribute *a)
708 {
709   if (!a->value)
710     return DBL_MAX;
711
712   char *tail;
713   double real;
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);
717
718   return real;
719 }
720
721 double
722 spvxml_attr_parse_dimension (struct spvxml_node_context *nctx,
723                              const struct spvxml_attribute *a)
724 {
725   if (!a->value)
726     return DBL_MAX;
727
728   char *tail;
729   double real;
730   if (!try_strtod (a->value, &tail, &real))
731     goto error;
732
733   tail += strspn (tail, " \t\r\n");
734
735   struct unit
736     {
737       const char *name;
738       double divisor;
739     };
740   static const struct unit units[] = {
741
742 /* If you add anything to this table, update the table in
743    doc/dev/spv-file-format.texi also.  */
744
745     /* Inches. */
746     { "in", 1.0 },
747     { "인치", 1.0 },
748     { "pol.", 1.0 },
749     { "cala", 1.0 },
750     { "cali", 1.0 },
751
752     /* Device-independent pixels. */
753     { "px", 96.0 },
754
755     /* Points. */
756     { "pt", 72.0 },
757     { "пт", 72.0 },
758     { "", 72.0 },
759
760     /* Centimeters. */
761     { "cm", 2.54 },
762     { "см", 2.54 },
763   };
764
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;
768   goto error;
769
770 error:
771   spvxml_attr_error (nctx, "Attribute %s has unexpected value "
772                      "\"%s\" expecting dimension.", a->name, a->value);
773   return DBL_MAX;
774 }
775
776 struct spvxml_node *
777 spvxml_attr_parse_ref (struct spvxml_node_context *nctx UNUSED,
778                        const struct spvxml_attribute *a UNUSED)
779 {
780   return NULL;
781 }
782 \f
783 void PRINTF_FORMAT (3, 4)
784 spvxml_content_error (struct spvxml_node_context *nctx, const xmlNode *node,
785                       const char *format, ...)
786 {
787   if (nctx->up->error)
788     return;
789
790   nctx->up->error = xstrdup ("error");
791   return;
792   struct string s = DS_EMPTY_INITIALIZER;
793
794   ds_put_cstr (&s, "error parsing content of ");
795   spvxml_format_node_path (nctx->parent, &s);
796
797   if (node)
798     {
799       ds_put_format (&s, " at %s", xml_element_type_to_string (node->type));
800       if (node->name)
801         ds_put_format (&s, " \"%s\"", node->name);
802     }
803   else
804     ds_put_format (&s, " at end of content");
805
806   va_list args;
807   va_start (args, format);
808   ds_put_cstr (&s, ": ");
809   ds_put_vformat (&s, format, args);
810   va_end (args);
811
812   //puts (ds_cstr (&s));
813
814   nctx->up->error = ds_steal_cstr (&s);
815 }
816
817 bool
818 spvxml_content_parse_element (struct spvxml_node_context *nctx,
819                               xmlNode **nodep,
820                               const char *elem_name, xmlNode **outp)
821 {
822   xmlNode *node = *nodep;
823   while (node)
824     {
825       if (node->type == XML_ELEMENT_NODE
826           && (!strcmp (CHAR_CAST (char *, node->name), elem_name)
827               || !strcmp (elem_name, "any")))
828         {
829           *outp = node;
830           *nodep = node->next;
831           return true;
832         }
833       else if (node->type != XML_COMMENT_NODE)
834         break;
835
836       node = node->next;
837     }
838
839   spvxml_content_error (nctx, node, "\"%s\" element expected.", elem_name);
840   *outp = NULL;
841   return false;
842 }
843
844 bool
845 spvxml_content_parse_text (struct spvxml_node_context *nctx UNUSED, xmlNode **nodep,
846                            char **textp)
847 {
848   struct string text = DS_EMPTY_INITIALIZER;
849
850   xmlNode *node = *nodep;
851   while (node)
852     {
853       if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE)
854         {
855           char *segment = CHAR_CAST (char *, xmlNodeGetContent (node));
856           if (!text.ss.string)
857             {
858               text.ss = ss_cstr (segment);
859               text.capacity = text.ss.length;
860             }
861           else
862             {
863               ds_put_cstr (&text, segment);
864               free (segment);
865             }
866         }
867       else if (node->type != XML_COMMENT_NODE)
868         break;
869
870       node = node->next;
871     }
872   *nodep = node;
873
874   *textp = ds_steal_cstr (&text);
875
876   return true;
877 }
878
879 bool
880 spvxml_content_parse_end (struct spvxml_node_context *nctx, xmlNode *node)
881 {
882   for (;;)
883     {
884       if (!node)
885         return true;
886       else if (node->type != XML_COMMENT_NODE)
887         break;
888
889       node = node->next;
890     }
891
892   struct string s = DS_EMPTY_INITIALIZER;
893
894   for (int i = 0; i < 4 && node; i++, node = node->next)
895     {
896       if (i)
897         ds_put_cstr (&s, ", ");
898       ds_put_cstr (&s, xml_element_type_to_string (node->type));
899       if (node->name)
900         ds_put_format (&s, " \"%s\"", node->name);
901     }
902   if (node)
903     ds_put_format (&s, ", ...");
904
905   spvxml_content_error (nctx, node, "Extra content found expecting end: %s",
906                         ds_cstr (&s));
907   ds_destroy (&s);
908
909   return false;
910 }
911