spvxml-helpers: Avoid warning on recent versions of libxml2.
[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 #include "output/options.h"
30 #include "output/table.h"
31
32 #include "gl/xvasprintf.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
81 #ifndef XML_DOCB_DOCUMENT_NODE
82       /* libxml2 removed this value from the enum and redefined it as a macro
83          for backward compatibility.  When it's not in the enum, it's best not
84          to have a 'case' for it because that provokes a compiler warning. */
85     case XML_DOCB_DOCUMENT_NODE: return "docb document";
86 #endif
87     default: return "<error>";
88     }
89 }
90
91 static void
92 spvxml_format_node_path (const xmlNode *node, struct string *s)
93 {
94   enum { MAX_STACK = 32 };
95   const xmlNode *stack[MAX_STACK];
96   size_t n = 0;
97
98   while (node != NULL && node->type != XML_DOCUMENT_NODE && n < MAX_STACK)
99     {
100       stack[n++] = node;
101       node = node->parent;
102     }
103
104   while (n > 0)
105     {
106       node = stack[--n];
107       ds_put_byte (s, '/');
108       if (node->name)
109         ds_put_cstr (s, CHAR_CAST (char *, node->name));
110       if (node->type == XML_ELEMENT_NODE)
111         {
112           if (node->parent)
113             {
114               size_t total = 1;
115               size_t index = 1;
116               for (const xmlNode *sibling = node->parent->children;
117                    sibling; sibling = sibling->next)
118                 {
119                   if (sibling == node)
120                     index = total;
121                   else if (sibling->type == XML_ELEMENT_NODE
122                            && !strcmp (CHAR_CAST (char *, sibling->name),
123                                        CHAR_CAST (char *, node->name)))
124                     total++;
125                 }
126               if (total > 1)
127                 ds_put_format (s, "[%zu]", index);
128             }
129         }
130       else
131         ds_put_format (s, "(%s)", xml_element_type_to_string (node->type));
132     }
133 }
134
135 static struct spvxml_node *
136 spvxml_node_find (struct spvxml_context *ctx, const char *name,
137                   unsigned int hash)
138 {
139   struct spvxml_node *node;
140   HMAP_FOR_EACH_WITH_HASH (node, struct spvxml_node, id_node, hash,
141                            &ctx->id_map)
142     if (!strcmp (node->id, name))
143       return node;
144
145   return NULL;
146 }
147
148 void
149 spvxml_node_collect_id (struct spvxml_context *ctx, struct spvxml_node *node)
150 {
151   if (!node->id)
152     return;
153
154   unsigned int hash = hash_string (node->id, 0);
155   struct spvxml_node *other = spvxml_node_find (ctx, node->id, hash);
156   if (other)
157     {
158       if (!ctx->error)
159         {
160           struct string node_path = DS_EMPTY_INITIALIZER;
161           spvxml_format_node_path (node->raw, &node_path);
162
163           struct string other_path = DS_EMPTY_INITIALIZER;
164           spvxml_format_node_path (other->raw, &other_path);
165
166           ctx->error = xasprintf ("Nodes %s and %s both have ID \"%s\".",
167                                   ds_cstr (&node_path),
168                                   ds_cstr (&other_path), node->id);
169
170           ds_destroy (&node_path);
171           ds_destroy (&other_path);
172         }
173
174       return;
175     }
176
177   hmap_insert (&ctx->id_map, &node->id_node, hash);
178 }
179
180 struct spvxml_node *
181 spvxml_node_resolve_ref (struct spvxml_context *ctx,
182                          const xmlNode *src, const char *attr_name,
183                          const struct spvxml_node_class *const *classes,
184                          size_t n)
185 {
186   char *dst_id = CHAR_CAST (
187     char *, xmlGetProp (CONST_CAST (xmlNode *, src),
188                         CHAR_CAST (xmlChar *, attr_name)));
189   if (!dst_id)
190     return NULL;
191
192   struct spvxml_node *dst = spvxml_node_find (ctx, dst_id,
193                                               hash_string (dst_id, 0));
194   if (!dst)
195     {
196       struct string node_path = DS_EMPTY_INITIALIZER;
197       spvxml_format_node_path (src, &node_path);
198
199       ctx->error = xasprintf (
200         "%s: Attribute %s has unknown target ID \"%s\".",
201         ds_cstr (&node_path), attr_name, dst_id);
202
203       ds_destroy (&node_path);
204       free (dst_id);
205       return NULL;
206     }
207
208   if (!n)
209     {
210       free (dst_id);
211       return dst;
212     }
213   for (size_t i = 0; i < n; i++)
214     if (classes[i] == dst->class_)
215       {
216         free (dst_id);
217         return dst;
218       }
219
220   if (!ctx->error)
221     {
222       struct string s = DS_EMPTY_INITIALIZER;
223       spvxml_format_node_path (src, &s);
224
225       ds_put_format (&s, ": Attribute \"%s\" should refer to a \"%s\"",
226                      attr_name, classes[0]->name);
227       if (n == 2)
228         ds_put_format (&s, " or \"%s\"", classes[1]->name);
229       else if (n > 2)
230         {
231           for (size_t i = 1; i < n - 1; i++)
232             ds_put_format (&s, ", \"%s\"", classes[i]->name);
233           ds_put_format (&s, ", or \"%s\"", classes[n - 1]->name);
234         }
235       ds_put_format (&s, " element, but its target ID \"%s\" "
236                      "actually refers to a \"%s\" element.",
237                      dst_id, dst->class_->name);
238
239       ctx->error = ds_steal_cstr (&s);
240     }
241
242   free (dst_id);
243   return NULL;
244 }
245
246 void PRINTF_FORMAT (2, 3)
247 spvxml_attr_error (struct spvxml_node_context *nctx, const char *format, ...)
248 {
249   if (nctx->up->error)
250     return;
251
252   struct string s = DS_EMPTY_INITIALIZER;
253   ds_put_cstr (&s, "error parsing attributes of ");
254   spvxml_format_node_path (nctx->parent, &s);
255
256   va_list args;
257   va_start (args, format);
258   ds_put_cstr (&s, ": ");
259   ds_put_vformat (&s, format, args);
260   va_end (args);
261
262   nctx->up->error = ds_steal_cstr (&s);
263 }
264
265 /* xmlGetPropNodeValueInternal() is from tree.c in libxml2 2.9.4+dfsg1, which
266    is covered by the following copyright and license:
267
268    Except where otherwise noted in the source code (e.g. the files hash.c,
269    list.c and the trio files, which are covered by a similar licence but with
270    different Copyright notices) all the files are:
271
272    Copyright (C) 1998-2012 Daniel Veillard.  All Rights Reserved.
273
274    Permission is hereby granted, free of charge, to any person obtaining a copy
275    of this software and associated documentation files (the "Software"), to
276    deal in the Software without restriction, including without limitation the
277    rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
278    sell copies of the Software, and to permit persons to whom the Software is
279    fur- nished to do so, subject to the following conditions:
280
281    The above copyright notice and this permission notice shall be included in
282    all copies or substantial portions of the Software.
283
284    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
285    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
286    FIT- NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
287    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
288    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
289    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
290    IN THE SOFTWARE.
291 */
292 static xmlChar*
293 xmlGetPropNodeValueInternal(const xmlAttr *prop)
294 {
295     if (prop == NULL)
296         return(NULL);
297     if (prop->type == XML_ATTRIBUTE_NODE) {
298         /*
299         * Note that we return at least the empty string.
300         *   TODO: Do we really always want that?
301         */
302         if (prop->children != NULL) {
303             if ((prop->children->next == NULL) &&
304                 ((prop->children->type == XML_TEXT_NODE) ||
305                 (prop->children->type == XML_CDATA_SECTION_NODE)))
306             {
307                 /*
308                 * Optimization for the common case: only 1 text node.
309                 */
310                 return(xmlStrdup(prop->children->content));
311             } else {
312                 xmlChar *ret;
313
314                 ret = xmlNodeListGetString(prop->doc, prop->children, 1);
315                 if (ret != NULL)
316                     return(ret);
317             }
318         }
319         return(xmlStrdup((xmlChar *)""));
320     } else if (prop->type == XML_ATTRIBUTE_DECL) {
321         return(xmlStrdup(((xmlAttributePtr)prop)->defaultValue));
322     }
323     return(NULL);
324 }
325
326 static struct spvxml_attribute *
327 find_attribute (struct spvxml_node_context *nctx, const char *name)
328 {
329   /* XXX This is linear search but we could use binary search. */
330   for (struct spvxml_attribute *a = nctx->attrs;
331        a < &nctx->attrs[nctx->n_attrs]; a++)
332     if (!strcmp (a->name, name))
333       return a;
334
335   return NULL;
336 }
337
338 static void
339 format_attribute (struct string *s, const xmlAttr *attr)
340 {
341   const char *name = CHAR_CAST (char *, attr->name);
342   char *value = CHAR_CAST (char *, xmlGetPropNodeValueInternal (attr));
343   ds_put_format (s, "%s=\"%s\"", name, value);
344   free (value);
345 }
346
347 void
348 spvxml_parse_attributes (struct spvxml_node_context *nctx)
349 {
350   for (const xmlAttr *node = nctx->parent->properties; node; node = node->next)
351     {
352       const char *node_name = CHAR_CAST (char *, node->name);
353       struct spvxml_attribute *a = find_attribute (nctx, node_name);
354       if (!a)
355         {
356           if (!strcmp (node_name, "id"))
357             continue;
358
359           struct string unexpected = DS_EMPTY_INITIALIZER;
360           format_attribute (&unexpected, node);
361           int n = 1;
362
363           for (node = node->next; node; node = node->next)
364             {
365               node_name = CHAR_CAST (char *, node->name);
366               if (!find_attribute (nctx, node_name)
367                   && strcmp (node_name, "id"))
368                 {
369                   ds_put_byte (&unexpected, ' ');
370                   format_attribute (&unexpected, node);
371                   n++;
372                 }
373             }
374
375           spvxml_attr_error (nctx, "Node has unexpected attribute%s: %s",
376                              n > 1 ? "s" : "", ds_cstr (&unexpected));
377           ds_destroy (&unexpected);
378           return;
379         }
380       if (a->value)
381         {
382           spvxml_attr_error (nctx, "Duplicate attribute \"%s\".", a->name);
383           return;
384         }
385       a->value = CHAR_CAST (char *, xmlGetPropNodeValueInternal (node));
386     }
387
388   for (struct spvxml_attribute *a = nctx->attrs;
389        a < &nctx->attrs[nctx->n_attrs]; a++)
390     {
391       if (a->required && !a->value)
392         spvxml_attr_error (nctx, "Missing required attribute \"%s\".",
393                            a->name);
394       return;
395     }
396 }
397
398 int
399 spvxml_attr_parse_enum (struct spvxml_node_context *nctx,
400                         const struct spvxml_attribute *a,
401                         const struct spvxml_enum enums[])
402 {
403   if (!a->value)
404     return 0;
405
406   for (const struct spvxml_enum *e = enums; e->name; e++)
407     if (!strcmp (a->value, e->name))
408       return e->value;
409
410   for (const struct spvxml_enum *e = enums; e->name; e++)
411     if (!strcmp (e->name, "OTHER"))
412       return e->value;
413
414   spvxml_attr_error (nctx, "Attribute %s has unexpected value \"%s\".",
415                 a->name, a->value);
416   return 0;
417 }
418
419 int
420 spvxml_attr_parse_bool (struct spvxml_node_context *nctx,
421                         const struct spvxml_attribute *a)
422 {
423   static const struct spvxml_enum bool_enums[] = {
424     { "true", 1 },
425     { "false", 0 },
426     { NULL, 0 },
427   };
428
429   return !a->value ? -1 : spvxml_attr_parse_enum (nctx, a, bool_enums);
430 }
431
432 bool
433 spvxml_attr_parse_fixed (struct spvxml_node_context *nctx,
434                          const struct spvxml_attribute *a,
435                          const char *attr_value)
436 {
437   const struct spvxml_enum fixed_enums[] = {
438     { attr_value, true },
439     { NULL, 0 },
440   };
441
442   return spvxml_attr_parse_enum (nctx, a, fixed_enums);
443 }
444
445 int
446 spvxml_attr_parse_int (struct spvxml_node_context *nctx,
447                        const struct spvxml_attribute *a)
448 {
449   if (!a->value)
450     return INT_MIN;
451
452   char *tail = NULL;
453   int save_errno = errno;
454   errno = 0;
455   long int integer = strtol (a->value, &tail, 10);
456   if (errno || *tail || integer <= INT_MIN || integer > INT_MAX)
457     {
458       spvxml_attr_error (nctx, "Attribute %s has unexpected value "
459                          "\"%s\" expecting small integer.", a->name, a->value);
460       integer = INT_MIN;
461     }
462   errno = save_errno;
463
464   return integer;
465 }
466
467 int
468 spvxml_attr_parse_color (struct spvxml_node_context *nctx,
469                          const struct spvxml_attribute *a)
470 {
471   if (!a->value || !strcmp (a->value, "transparent"))
472     return -1;
473
474   struct cell_color color;
475   if (parse_color__ (a->value, &color))
476     return (color.r << 16) | (color.g << 8) | color.b;
477
478   spvxml_attr_error (nctx, "Attribute %s has unexpected value "
479                      "\"%s\" expecting #rrggbb or rrggbb or web color name.",
480                      a->name, a->value);
481   return 0;
482 }
483
484 static bool
485 try_strtod (char *s, char **tail, double *real)
486 {
487   char *comma = strchr (s, ',');
488   if (comma)
489     *comma = '.';
490
491   int save_errno = errno;
492   errno = 0;
493   *tail = NULL;
494   *real = strtod (s, tail);
495   bool ok = errno == 0;
496   errno = save_errno;
497
498   if (!ok)
499     *real = DBL_MAX;
500   return ok;
501 }
502
503 double
504 spvxml_attr_parse_real (struct spvxml_node_context *nctx,
505                         const struct spvxml_attribute *a)
506 {
507   if (!a->value)
508     return DBL_MAX;
509
510   char *tail;
511   double real;
512   if (!try_strtod (a->value, &tail, &real) || *tail)
513     spvxml_attr_error (nctx, "Attribute %s has unexpected value "
514                        "\"%s\" expecting real number.", a->name, a->value);
515
516   return real;
517 }
518
519 double
520 spvxml_attr_parse_dimension (struct spvxml_node_context *nctx,
521                              const struct spvxml_attribute *a)
522 {
523   if (!a->value)
524     return DBL_MAX;
525
526   char *tail;
527   double real;
528   if (!try_strtod (a->value, &tail, &real))
529     goto error;
530
531   tail += strspn (tail, " \t\r\n");
532
533   struct unit
534     {
535       const char *name;
536       double divisor;
537     };
538   static const struct unit units[] = {
539
540 /* If you add anything to this table, update the table in
541    doc/dev/spv-file-format.texi also.  */
542
543     /* Inches. */
544     { "in", 1.0 },
545     { "인치", 1.0 },
546     { "pol.", 1.0 },
547     { "cala", 1.0 },
548     { "cali", 1.0 },
549
550     /* Device-independent pixels. */
551     { "px", 96.0 },
552
553     /* Points. */
554     { "pt", 72.0 },
555     { "пт", 72.0 },
556     { "", 72.0 },
557
558     /* Centimeters. */
559     { "cm", 2.54 },
560     { "см", 2.54 },
561   };
562
563   for (size_t i = 0; i < sizeof units / sizeof *units; i++)
564     if (!strcmp (units[i].name, tail))
565       return real / units[i].divisor;
566   goto error;
567
568 error:
569   spvxml_attr_error (nctx, "Attribute %s has unexpected value "
570                      "\"%s\" expecting dimension.", a->name, a->value);
571   return DBL_MAX;
572 }
573
574 struct spvxml_node *
575 spvxml_attr_parse_ref (struct spvxml_node_context *nctx UNUSED,
576                        const struct spvxml_attribute *a UNUSED)
577 {
578   return NULL;
579 }
580 \f
581 void PRINTF_FORMAT (3, 4)
582 spvxml_content_error (struct spvxml_node_context *nctx, const xmlNode *node,
583                       const char *format, ...)
584 {
585   if (nctx->up->error)
586     return;
587
588   struct string s = DS_EMPTY_INITIALIZER;
589
590   ds_put_cstr (&s, "error parsing content of ");
591   spvxml_format_node_path (nctx->parent, &s);
592
593   if (node)
594     {
595       ds_put_format (&s, " at %s", xml_element_type_to_string (node->type));
596       if (node->name)
597         ds_put_format (&s, " \"%s\"", node->name);
598     }
599   else
600     ds_put_format (&s, " at end of content");
601
602   va_list args;
603   va_start (args, format);
604   ds_put_cstr (&s, ": ");
605   ds_put_vformat (&s, format, args);
606   va_end (args);
607
608   //puts (ds_cstr (&s));
609
610   nctx->up->error = ds_steal_cstr (&s);
611 }
612
613 bool
614 spvxml_content_parse_element (struct spvxml_node_context *nctx,
615                               xmlNode **nodep,
616                               const char *elem_name, xmlNode **outp)
617 {
618   xmlNode *node = *nodep;
619   while (node)
620     {
621       if (node->type == XML_ELEMENT_NODE
622           && (!strcmp (CHAR_CAST (char *, node->name), elem_name)
623               || !strcmp (elem_name, "any")))
624         {
625           *outp = node;
626           *nodep = node->next;
627           return true;
628         }
629       else if (node->type != XML_COMMENT_NODE)
630         break;
631
632       node = node->next;
633     }
634
635   spvxml_content_error (nctx, node, "\"%s\" element expected.", elem_name);
636   *outp = NULL;
637   return false;
638 }
639
640 bool
641 spvxml_content_parse_text (struct spvxml_node_context *nctx UNUSED, xmlNode **nodep,
642                            char **textp)
643 {
644   struct string text = DS_EMPTY_INITIALIZER;
645
646   xmlNode *node = *nodep;
647   while (node)
648     {
649       if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE)
650         {
651           char *segment = CHAR_CAST (char *, xmlNodeGetContent (node));
652           if (!text.ss.string)
653             {
654               text.ss = ss_cstr (segment);
655               text.capacity = text.ss.length;
656             }
657           else
658             {
659               ds_put_cstr (&text, segment);
660               free (segment);
661             }
662         }
663       else if (node->type != XML_COMMENT_NODE)
664         break;
665
666       node = node->next;
667     }
668   *nodep = node;
669
670   *textp = ds_steal_cstr (&text);
671
672   return true;
673 }
674
675 bool
676 spvxml_content_parse_end (struct spvxml_node_context *nctx, xmlNode *node)
677 {
678   for (;;)
679     {
680       if (!node)
681         return true;
682       else if (node->type != XML_COMMENT_NODE)
683         break;
684
685       node = node->next;
686     }
687
688   struct string s = DS_EMPTY_INITIALIZER;
689
690   for (int i = 0; i < 4 && node; i++, node = node->next)
691     {
692       if (i)
693         ds_put_cstr (&s, ", ");
694       ds_put_cstr (&s, xml_element_type_to_string (node->type));
695       if (node->name)
696         ds_put_format (&s, " \"%s\"", node->name);
697     }
698   if (node)
699     ds_put_format (&s, ", ...");
700
701   spvxml_content_error (nctx, node, "Extra content found expecting end: %s",
702                         ds_cstr (&s));
703   ds_destroy (&s);
704
705   return false;
706 }
707