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