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