work on adding empty categories
[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     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 static struct spvxml_attribute *
321 find_attribute (struct spvxml_node_context *nctx, const char *name)
322 {
323   /* XXX This is linear search but we could use binary search. */
324   for (struct spvxml_attribute *a = nctx->attrs;
325        a < &nctx->attrs[nctx->n_attrs]; a++)
326     if (!strcmp (a->name, name))
327       return a;
328
329   return NULL;
330 }
331
332 static void
333 format_attribute (struct string *s, const xmlAttr *attr)
334 {
335   const char *name = CHAR_CAST (char *, attr->name);
336   char *value = CHAR_CAST (char *, xmlGetPropNodeValueInternal (attr));
337   ds_put_format (s, "%s=\"%s\"", name, value);
338   free (value);
339 }
340
341 void
342 spvxml_parse_attributes (struct spvxml_node_context *nctx)
343 {
344   for (const xmlAttr *node = nctx->parent->properties; node; node = node->next)
345     {
346       const char *node_name = CHAR_CAST (char *, node->name);
347       struct spvxml_attribute *a = find_attribute (nctx, node_name);
348       if (!a)
349         {
350           if (!strcmp (node_name, "id"))
351             continue;
352
353           struct string unexpected = DS_EMPTY_INITIALIZER;
354           format_attribute (&unexpected, node);
355           int n = 1;
356
357           for (node = node->next; node; node = node->next)
358             {
359               node_name = CHAR_CAST (char *, node->name);
360               if (!find_attribute (nctx, node_name)
361                   && strcmp (node_name, "id"))
362                 {
363                   ds_put_byte (&unexpected, ' ');
364                   format_attribute (&unexpected, node);
365                   n++;
366                 }
367             }
368
369           spvxml_attr_error (nctx, "Node has unexpected attribute%s: %s",
370                              n > 1 ? "s" : "", ds_cstr (&unexpected));
371           ds_destroy (&unexpected);
372           return;
373         }
374       if (a->value)
375         {
376           spvxml_attr_error (nctx, "Duplicate attribute \"%s\".", a->name);
377           return;
378         }
379       a->value = CHAR_CAST (char *, xmlGetPropNodeValueInternal (node));
380     }
381
382   for (struct spvxml_attribute *a = nctx->attrs;
383        a < &nctx->attrs[nctx->n_attrs]; a++)
384     {
385       if (a->required && !a->value)
386         spvxml_attr_error (nctx, "Missing required attribute \"%s\".",
387                            a->name);
388       return;
389     }
390 }
391
392 int
393 spvxml_attr_parse_enum (struct spvxml_node_context *nctx,
394                         const struct spvxml_attribute *a,
395                         const struct spvxml_enum enums[])
396 {
397   if (!a->value)
398     return 0;
399
400   for (const struct spvxml_enum *e = enums; e->name; e++)
401     if (!strcmp (a->value, e->name))
402       return e->value;
403
404   for (const struct spvxml_enum *e = enums; e->name; e++)
405     if (!strcmp (e->name, "OTHER"))
406       return e->value;
407
408   spvxml_attr_error (nctx, "Attribute %s has unexpected value \"%s\".",
409                 a->name, a->value);
410   return 0;
411 }
412
413 int
414 spvxml_attr_parse_bool (struct spvxml_node_context *nctx,
415                         const struct spvxml_attribute *a)
416 {
417   static const struct spvxml_enum bool_enums[] = {
418     { "true", 1 },
419     { "false", 0 },
420     { NULL, 0 },
421   };
422
423   return !a->value ? -1 : spvxml_attr_parse_enum (nctx, a, bool_enums);
424 }
425
426 bool
427 spvxml_attr_parse_fixed (struct spvxml_node_context *nctx,
428                          const struct spvxml_attribute *a,
429                          const char *attr_value)
430 {
431   const struct spvxml_enum fixed_enums[] = {
432     { attr_value, true },
433     { NULL, 0 },
434   };
435
436   return spvxml_attr_parse_enum (nctx, a, fixed_enums);
437 }
438
439 int
440 spvxml_attr_parse_int (struct spvxml_node_context *nctx,
441                        const struct spvxml_attribute *a)
442 {
443   if (!a->value)
444     return INT_MIN;
445
446   char *tail = NULL;
447   int save_errno = errno;
448   errno = 0;
449   long int integer = strtol (a->value, &tail, 10);
450   if (errno || *tail || integer <= INT_MIN || integer > INT_MAX)
451     {
452       spvxml_attr_error (nctx, "Attribute %s has unexpected value "
453                          "\"%s\" expecting small integer.", a->name, a->value);
454       integer = INT_MIN;
455     }
456   errno = save_errno;
457
458   return integer;
459 }
460
461 int
462 spvxml_attr_parse_color (struct spvxml_node_context *nctx,
463                          const struct spvxml_attribute *a)
464 {
465   if (!a->value || !strcmp (a->value, "transparent"))
466     return -1;
467
468   struct cell_color color;
469   if (parse_color__ (a->value, &color))
470     return (color.r << 16) | (color.g << 8) | color.b;
471
472   spvxml_attr_error (nctx, "Attribute %s has unexpected value "
473                      "\"%s\" expecting #rrggbb or rrggbb or web color name.",
474                      a->name, a->value);
475   return 0;
476 }
477
478 static bool
479 try_strtod (char *s, char **tail, double *real)
480 {
481   char *comma = strchr (s, ',');
482   if (comma)
483     *comma = '.';
484
485   int save_errno = errno;
486   errno = 0;
487   *tail = NULL;
488   *real = strtod (s, tail);
489   bool ok = errno == 0;
490   errno = save_errno;
491
492   if (!ok)
493     *real = DBL_MAX;
494   return ok;
495 }
496
497 double
498 spvxml_attr_parse_real (struct spvxml_node_context *nctx,
499                         const struct spvxml_attribute *a)
500 {
501   if (!a->value)
502     return DBL_MAX;
503
504   char *tail;
505   double real;
506   if (!try_strtod (a->value, &tail, &real) || *tail)
507     spvxml_attr_error (nctx, "Attribute %s has unexpected value "
508                        "\"%s\" expecting real number.", a->name, a->value);
509
510   return real;
511 }
512
513 double
514 spvxml_attr_parse_dimension (struct spvxml_node_context *nctx,
515                              const struct spvxml_attribute *a)
516 {
517   if (!a->value)
518     return DBL_MAX;
519
520   char *tail;
521   double real;
522   if (!try_strtod (a->value, &tail, &real))
523     goto error;
524
525   tail += strspn (tail, " \t\r\n");
526
527   struct unit
528     {
529       const char *name;
530       double divisor;
531     };
532   static const struct unit units[] = {
533
534 /* If you add anything to this table, update the table in
535    doc/dev/spv-file-format.texi also.  */
536
537     /* Inches. */
538     { "in", 1.0 },
539     { "인치", 1.0 },
540     { "pol.", 1.0 },
541     { "cala", 1.0 },
542     { "cali", 1.0 },
543
544     /* Device-independent pixels. */
545     { "px", 96.0 },
546
547     /* Points. */
548     { "pt", 72.0 },
549     { "пт", 72.0 },
550     { "", 72.0 },
551
552     /* Centimeters. */
553     { "cm", 2.54 },
554     { "см", 2.54 },
555   };
556
557   for (size_t i = 0; i < sizeof units / sizeof *units; i++)
558     if (!strcmp (units[i].name, tail))
559       return real / units[i].divisor;
560   goto error;
561
562 error:
563   spvxml_attr_error (nctx, "Attribute %s has unexpected value "
564                      "\"%s\" expecting dimension.", a->name, a->value);
565   return DBL_MAX;
566 }
567
568 struct spvxml_node *
569 spvxml_attr_parse_ref (struct spvxml_node_context *nctx UNUSED,
570                        const struct spvxml_attribute *a UNUSED)
571 {
572   return NULL;
573 }
574 \f
575 void PRINTF_FORMAT (3, 4)
576 spvxml_content_error (struct spvxml_node_context *nctx, const xmlNode *node,
577                       const char *format, ...)
578 {
579   if (nctx->up->error)
580     return;
581
582   struct string s = DS_EMPTY_INITIALIZER;
583
584   ds_put_cstr (&s, "error parsing content of ");
585   spvxml_format_node_path (nctx->parent, &s);
586
587   if (node)
588     {
589       ds_put_format (&s, " at %s", xml_element_type_to_string (node->type));
590       if (node->name)
591         ds_put_format (&s, " \"%s\"", node->name);
592     }
593   else
594     ds_put_format (&s, " at end of content");
595
596   va_list args;
597   va_start (args, format);
598   ds_put_cstr (&s, ": ");
599   ds_put_vformat (&s, format, args);
600   va_end (args);
601
602   //puts (ds_cstr (&s));
603
604   nctx->up->error = ds_steal_cstr (&s);
605 }
606
607 bool
608 spvxml_content_parse_element (struct spvxml_node_context *nctx,
609                               xmlNode **nodep,
610                               const char *elem_name, xmlNode **outp)
611 {
612   xmlNode *node = *nodep;
613   while (node)
614     {
615       if (node->type == XML_ELEMENT_NODE
616           && (!strcmp (CHAR_CAST (char *, node->name), elem_name)
617               || !strcmp (elem_name, "any")))
618         {
619           *outp = node;
620           *nodep = node->next;
621           return true;
622         }
623       else if (node->type != XML_COMMENT_NODE)
624         break;
625
626       node = node->next;
627     }
628
629   spvxml_content_error (nctx, node, "\"%s\" element expected.", elem_name);
630   *outp = NULL;
631   return false;
632 }
633
634 bool
635 spvxml_content_parse_text (struct spvxml_node_context *nctx UNUSED, xmlNode **nodep,
636                            char **textp)
637 {
638   struct string text = DS_EMPTY_INITIALIZER;
639
640   xmlNode *node = *nodep;
641   while (node)
642     {
643       if (node->type == XML_TEXT_NODE || node->type == XML_CDATA_SECTION_NODE)
644         {
645           char *segment = CHAR_CAST (char *, xmlNodeGetContent (node));
646           if (!text.ss.string)
647             {
648               text.ss = ss_cstr (segment);
649               text.capacity = text.ss.length;
650             }
651           else
652             {
653               ds_put_cstr (&text, segment);
654               free (segment);
655             }
656         }
657       else if (node->type != XML_COMMENT_NODE)
658         break;
659
660       node = node->next;
661     }
662   *nodep = node;
663
664   *textp = ds_steal_cstr (&text);
665
666   return true;
667 }
668
669 bool
670 spvxml_content_parse_end (struct spvxml_node_context *nctx, xmlNode *node)
671 {
672   for (;;)
673     {
674       if (!node)
675         return true;
676       else if (node->type != XML_COMMENT_NODE)
677         break;
678
679       node = node->next;
680     }
681
682   struct string s = DS_EMPTY_INITIALIZER;
683
684   for (int i = 0; i < 4 && node; i++, node = node->next)
685     {
686       if (i)
687         ds_put_cstr (&s, ", ");
688       ds_put_cstr (&s, xml_element_type_to_string (node->type));
689       if (node->name)
690         ds_put_format (&s, " \"%s\"", node->name);
691     }
692   if (node)
693     ds_put_format (&s, ", ...");
694
695   spvxml_content_error (nctx, node, "Extra content found expecting end: %s",
696                         ds_cstr (&s));
697   ds_destroy (&s);
698
699   return false;
700 }
701