83fba9631cc9287f2e3d3cb5b1de99f4493f17fe
[pspp] / src / output / spv / spv.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2017, 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/spv.h"
20
21 #include <assert.h>
22 #include <inttypes.h>
23 #include <libxml/HTMLparser.h>
24 #include <libxml/xmlreader.h>
25 #include <stdarg.h>
26 #include <stdlib.h>
27
28 #include "libpspp/assertion.h"
29 #include "libpspp/cast.h"
30 #include "libpspp/hash-functions.h"
31 #include "libpspp/message.h"
32 #include "libpspp/str.h"
33 #include "libpspp/zip-reader.h"
34 #include "output/page-setup-item.h"
35 #include "output/pivot-table.h"
36 #include "output/spv/detail-xml-parser.h"
37 #include "output/spv/light-binary-parser.h"
38 #include "output/spv/spv-css-parser.h"
39 #include "output/spv/spv-legacy-data.h"
40 #include "output/spv/spv-legacy-decoder.h"
41 #include "output/spv/spv-light-decoder.h"
42 #include "output/spv/structure-xml-parser.h"
43
44 #include "gl/c-ctype.h"
45 #include "gl/intprops.h"
46 #include "gl/minmax.h"
47 #include "gl/xalloc.h"
48 #include "gl/xvasprintf.h"
49 #include "gl/xsize.h"
50
51 #include "gettext.h"
52 #define _(msgid) gettext (msgid)
53 #define N_(msgid) (msgid)
54
55 struct spv_reader
56   {
57     struct string zip_errs;
58     struct zip_reader *zip;
59     struct spv_item *root;
60     struct page_setup *page_setup;
61   };
62
63 const struct page_setup *
64 spv_get_page_setup (const struct spv_reader *spv)
65 {
66   return spv->page_setup;
67 }
68
69 const char *
70 spv_item_type_to_string (enum spv_item_type type)
71 {
72   switch (type)
73     {
74     case SPV_ITEM_HEADING: return "heading";
75     case SPV_ITEM_TEXT: return "text";
76     case SPV_ITEM_TABLE: return "table";
77     case SPV_ITEM_GRAPH: return "graph";
78     case SPV_ITEM_MODEL: return "model";
79     case SPV_ITEM_OBJECT: return "object";
80     default: return "**error**";
81     }
82 }
83
84 const char *
85 spv_item_class_to_string (enum spv_item_class class)
86 {
87   switch (class)
88     {
89 #define SPV_CLASS(ENUM, NAME) case SPV_CLASS_##ENUM: return NAME;
90       SPV_CLASSES
91 #undef SPV_CLASS
92     default: return NULL;
93     }
94 }
95
96 enum spv_item_class
97 spv_item_class_from_string (const char *name)
98 {
99 #define SPV_CLASS(ENUM, NAME) \
100   if (!strcmp (name, NAME)) return SPV_CLASS_##ENUM;
101   SPV_CLASSES
102 #undef SPV_CLASS
103
104   return SPV_N_CLASSES;
105 }
106
107 enum spv_item_type
108 spv_item_get_type (const struct spv_item *item)
109 {
110   return item->type;
111 }
112
113 enum spv_item_class
114 spv_item_get_class (const struct spv_item *item)
115 {
116   const char *label = spv_item_get_label (item);
117   if (!label)
118     label = "";
119
120   switch (item->type)
121     {
122     case SPV_ITEM_HEADING:
123       return SPV_CLASS_HEADINGS;
124
125     case SPV_ITEM_TEXT:
126       return (!strcmp (label, "Title") ? SPV_CLASS_OUTLINEHEADERS
127               : !strcmp (label, "Log") ? SPV_CLASS_LOGS
128               : !strcmp (label, "Page Title") ? SPV_CLASS_PAGETITLE
129               : SPV_CLASS_TEXTS);
130
131     case SPV_ITEM_TABLE:
132       return (!strcmp (label, "Warnings") ? SPV_CLASS_WARNINGS
133               : !strcmp (label, "Notes") ? SPV_CLASS_NOTES
134               : SPV_CLASS_TABLES);
135
136     case SPV_ITEM_GRAPH:
137       return SPV_CLASS_CHARTS;
138
139     case SPV_ITEM_MODEL:
140       return SPV_CLASS_MODELS;
141
142     case SPV_ITEM_OBJECT:
143       return SPV_CLASS_OTHER;
144
145     case SPV_ITEM_TREE:
146       return SPV_CLASS_TREES;
147
148     default:
149       return SPV_CLASS_UNKNOWN;
150     }
151 }
152
153 const char *
154 spv_item_get_label (const struct spv_item *item)
155 {
156   return item->label;
157 }
158
159 bool
160 spv_item_is_heading (const struct spv_item *item)
161 {
162   return item->type == SPV_ITEM_HEADING;
163 }
164
165 size_t
166 spv_item_get_n_children (const struct spv_item *item)
167 {
168   return item->n_children;
169 }
170
171 struct spv_item *
172 spv_item_get_child (const struct spv_item *item, size_t idx)
173 {
174   assert (idx < item->n_children);
175   return item->children[idx];
176 }
177
178 bool
179 spv_item_is_table (const struct spv_item *item)
180 {
181   return item->type == SPV_ITEM_TABLE;
182 }
183
184 bool
185 spv_item_is_text (const struct spv_item *item)
186 {
187   return item->type == SPV_ITEM_TEXT;
188 }
189
190 const struct pivot_value *
191 spv_item_get_text (const struct spv_item *item)
192 {
193   assert (spv_item_is_text (item));
194   return item->text;
195 }
196
197 struct spv_item *
198 spv_item_next (const struct spv_item *item)
199 {
200   if (item->n_children)
201     return item->children[0];
202
203   while (item->parent)
204     {
205       size_t idx = item->parent_idx + 1;
206       item = item->parent;
207       if (idx < item->n_children)
208         return item->children[idx];
209     }
210
211   return NULL;
212 }
213
214 const struct spv_item *
215 spv_item_get_parent (const struct spv_item *item)
216 {
217   return item->parent;
218 }
219
220 size_t
221 spv_item_get_level (const struct spv_item *item)
222 {
223   int level = 0;
224   for (; item->parent; item = item->parent)
225     level++;
226   return level;
227 }
228
229 const char *
230 spv_item_get_command_id (const struct spv_item *item)
231 {
232   return item->command_id;
233 }
234
235 const char *
236 spv_item_get_subtype (const struct spv_item *item)
237 {
238   return item->subtype;
239 }
240
241 bool
242 spv_item_is_visible (const struct spv_item *item)
243 {
244   return item->visible;
245 }
246
247 static void
248 spv_item_destroy (struct spv_item *item)
249 {
250   if (item)
251     {
252       free (item->structure_member);
253
254       free (item->label);
255       free (item->command_id);
256
257       for (size_t i = 0; i < item->n_children; i++)
258         spv_item_destroy (item->children[i]);
259       free (item->children);
260
261       pivot_table_unref (item->table);
262       spv_legacy_properties_destroy (item->legacy_properties);
263       free (item->bin_member);
264       free (item->xml_member);
265       free (item->subtype);
266
267       pivot_value_destroy (item->text);
268
269       free (item->object_type);
270       free (item->uri);
271
272       free (item);
273     }
274 }
275
276 static void
277 spv_heading_add_child (struct spv_item *parent, struct spv_item *child)
278 {
279   assert (parent->type == SPV_ITEM_HEADING);
280   assert (!child->parent);
281
282   child->parent = parent;
283   child->parent_idx = parent->n_children;
284
285   if (parent->n_children >= parent->allocated_children)
286     parent->children = x2nrealloc (parent->children,
287                                    &parent->allocated_children,
288                                    sizeof *parent->children);
289   parent->children[parent->n_children++] = child;
290 }
291
292 static xmlNode *
293 find_xml_child_element (xmlNode *parent, const char *child_name)
294 {
295   for (xmlNode *node = parent->children; node; node = node->next)
296     if (node->type == XML_ELEMENT_NODE
297         && node->name
298         && !strcmp (CHAR_CAST (char *, node->name), child_name))
299       return node;
300
301   return NULL;
302 }
303
304 static char *
305 get_xml_attr (const xmlNode *node, const char *name)
306 {
307   return CHAR_CAST (char *, xmlGetProp (node, CHAR_CAST (xmlChar *, name)));
308 }
309
310 static void
311 put_xml_attr (const char *name, const char *value, struct string *dst)
312 {
313   if (!value)
314     return;
315
316   ds_put_format (dst, " %s=\"", name);
317   for (const char *p = value; *p; p++)
318     {
319       switch (*p)
320         {
321         case '\n':
322           ds_put_cstr (dst, "&#10;");
323           break;
324         case '&':
325           ds_put_cstr (dst, "&amp;");
326           break;
327         case '<':
328           ds_put_cstr (dst, "&lt;");
329           break;
330         case '>':
331           ds_put_cstr (dst, "&gt;");
332           break;
333         case '"':
334           ds_put_cstr (dst, "&quot;");
335           break;
336         default:
337           ds_put_byte (dst, *p);
338           break;
339         }
340     }
341   ds_put_byte (dst, '"');
342 }
343
344 static void
345 extract_html_text (const xmlNode *node, int base_font_size, struct string *s)
346 {
347   if (node->type == XML_ELEMENT_NODE)
348     {
349       const char *name = CHAR_CAST (char *, node->name);
350       if (!strcmp (name, "br"))
351         ds_put_byte (s, '\n');
352       else if (strcmp (name, "style"))
353         {
354           const char *tag = NULL;
355           if (strchr ("biu", name[0]) && name[1] == '\0')
356             {
357               tag = name;
358               ds_put_format (s, "<%s>", tag);
359             }
360           else if (!strcmp (name, "font"))
361             {
362               tag = "span";
363               ds_put_format (s, "<%s", tag);
364
365               char *face = get_xml_attr (node, "face");
366               put_xml_attr ("face", face, s);
367               free (face);
368
369               char *color = get_xml_attr (node, "color");
370               if (color)
371                 {
372                   if (color[0] == '#')
373                     put_xml_attr ("color", color, s);
374                   else
375                     {
376                       uint8_t r, g, b;
377                       if (sscanf (color, "rgb (%"SCNu8", %"SCNu8", %"SCNu8")",
378                                   &r, &g, &b) == 3)
379                         {
380                           char color2[8];
381                           snprintf (color2, sizeof color2,
382                                     "#%02"PRIx8"%02"PRIx8"%02"PRIx8,
383                                     r, g, b);
384                           put_xml_attr ("color", color2, s);
385                         }
386                     }
387                 }
388               free (color);
389
390               char *size_s = get_xml_attr (node, "size");
391               int html_size = size_s ? atoi (size_s) : 0;
392               free (size_s);
393               if (html_size >= 1 && html_size <= 7)
394                 {
395                   static const double scale[7] = {
396                     .444, .556, .667, .778, 1.0, 1.33, 2.0
397                   };
398                   double size = base_font_size * scale[html_size - 1];
399
400                   char size2[INT_BUFSIZE_BOUND (int)];
401                   snprintf (size2, sizeof size2, "%.0f", size * 1024.);
402                   put_xml_attr ("size", size2, s);
403                 }
404
405               ds_put_cstr (s, ">");
406             }
407           for (const xmlNode *child = node->children; child;
408                child = child->next)
409             extract_html_text (child, base_font_size, s);
410           if (tag)
411             ds_put_format (s, "</%s>", tag);
412         }
413     }
414   else if (node->type == XML_TEXT_NODE)
415     {
416       /* U+00A0 NONBREAKING SPACE is really, really common in SPV text and it
417          makes it impossible to break syntax across lines.  Translate it into a
418          regular space.  (Note that U+00A0 is C2 A0 in UTF-8.)
419
420          Do the same for U+2007 FIGURE SPACE, which also crops out weirdly
421          sometimes. */
422       ds_extend (s, ds_length (s) + xmlStrlen (node->content));
423       for (const uint8_t *p = node->content; *p;)
424         {
425           int c;
426           if (p[0] == 0xc2 && p[1] == 0xa0)
427             {
428               c = ' ';
429               p += 2;
430             }
431           else if (p[0] == 0xe2 && p[1] == 0x80 && p[2] == 0x87)
432             {
433               c = ' ';
434               p += 3;
435             }
436           else
437             c = *p++;
438
439           if (c_isspace (c))
440             {
441               int last = ds_last (s);
442               if (last != EOF && !c_isspace (last))
443                 ds_put_byte (s, c);
444             }
445           else if (c == '<')
446             ds_put_cstr (s, "&lt;");
447           else if (c == '>')
448             ds_put_cstr (s, "&gt;");
449           else if (c == '&')
450             ds_put_cstr (s, "&amp;");
451           else
452             ds_put_byte (s, c);
453         }
454     }
455 }
456
457 static xmlDoc *
458 parse_embedded_html (const xmlNode *node)
459 {
460   /* Extract HTML from XML node. */
461   char *html_s = CHAR_CAST (char *, xmlNodeGetContent (node));
462   if (!html_s)
463     xalloc_die ();
464
465   xmlDoc *html_doc = htmlReadMemory (
466     html_s, strlen (html_s),
467     NULL, "UTF-8", (HTML_PARSE_RECOVER | HTML_PARSE_NOERROR
468                     | HTML_PARSE_NOWARNING | HTML_PARSE_NOBLANKS
469                     | HTML_PARSE_NONET));
470   free (html_s);
471
472   return html_doc;
473 }
474
475 /* Given NODE, which should contain HTML content, returns the text within that
476    content as an allocated string.  The caller must eventually free the
477    returned string (with xmlFree()). */
478 static char *
479 decode_embedded_html (const xmlNode *node, struct font_style *font_style)
480 {
481   struct string markup = DS_EMPTY_INITIALIZER;
482   *font_style = (struct font_style) FONT_STYLE_INITIALIZER;
483   font_style->size = 10;
484
485   xmlDoc *html_doc = parse_embedded_html (node);
486   if (html_doc)
487     {
488       xmlNode *root = xmlDocGetRootElement (html_doc);
489       xmlNode *head = root ? find_xml_child_element (root, "head") : NULL;
490       xmlNode *style = head ? find_xml_child_element (head, "style") : NULL;
491       if (style)
492         {
493           uint8_t *style_s = xmlNodeGetContent (style);
494           spv_parse_css_style (CHAR_CAST (char *, style_s), font_style);
495           xmlFree (style_s);
496         }
497
498       if (root)
499         extract_html_text (root, font_style->size, &markup);
500       xmlFreeDoc (html_doc);
501     }
502
503   font_style->markup = true;
504   return ds_steal_cstr (&markup);
505 }
506
507 static char *
508 xstrdup_if_nonempty (const char *s)
509 {
510   return s && s[0] ? xstrdup (s) : NULL;
511 }
512
513 static void
514 decode_container_text (const struct spvsx_container_text *ct,
515                        struct spv_item *item)
516 {
517   item->type = SPV_ITEM_TEXT;
518   item->command_id = xstrdup_if_nonempty (ct->command_name);
519
520   item->text = xzalloc (sizeof *item->text);
521   item->text->type = PIVOT_VALUE_TEXT;
522   item->text->font_style = xmalloc (sizeof *item->text->font_style);
523   item->text->text.local = decode_embedded_html (ct->html->node_.raw,
524                                                  item->text->font_style);
525 }
526
527 static void
528 decode_page_p (const xmlNode *in, struct page_paragraph *out)
529 {
530   char *style = get_xml_attr (in, "style");
531   out->halign = (style && strstr (style, "center") ? TABLE_HALIGN_CENTER
532                  : style && strstr (style, "right") ? TABLE_HALIGN_RIGHT
533                  : TABLE_HALIGN_LEFT);
534   free (style);
535
536   struct font_style font_style;
537   out->markup = decode_embedded_html (in, &font_style);
538   font_style_uninit (&font_style);
539 }
540
541 static void
542 decode_page_paragraph (const struct spvsx_page_paragraph *page_paragraph,
543                        struct page_heading *ph)
544 {
545   memset (ph, 0, sizeof *ph);
546
547   const struct spvsx_page_paragraph_text *page_paragraph_text
548     = page_paragraph->page_paragraph_text;
549   if (!page_paragraph_text)
550     return;
551
552   xmlDoc *html_doc = parse_embedded_html (page_paragraph_text->node_.raw);
553   if (!html_doc)
554     return;
555
556   xmlNode *root = xmlDocGetRootElement (html_doc);
557   xmlNode *body = find_xml_child_element (root, "body");
558   if (body)
559     for (const xmlNode *node = body->children; node; node = node->next)
560       if (node->type == XML_ELEMENT_NODE
561           && !strcmp (CHAR_CAST (const char *, node->name), "p"))
562         {
563           ph->paragraphs = xrealloc (ph->paragraphs,
564                                      (ph->n + 1) * sizeof *ph->paragraphs);
565           decode_page_p (node, &ph->paragraphs[ph->n++]);
566         }
567   xmlFreeDoc (html_doc);
568 }
569
570 void
571 spv_item_load (const struct spv_item *item)
572 {
573   if (spv_item_is_table (item))
574     spv_item_get_table (item);
575 }
576
577 bool
578 spv_item_is_light_table (const struct spv_item *item)
579 {
580   return item->type == SPV_ITEM_TABLE && !item->xml_member;
581 }
582
583 char * WARN_UNUSED_RESULT
584 spv_item_get_raw_light_table (const struct spv_item *item,
585                               void **data, size_t *size)
586 {
587   return zip_member_read_all (item->spv->zip, item->bin_member, data, size);
588 }
589
590 char * WARN_UNUSED_RESULT
591 spv_item_get_light_table (const struct spv_item *item,
592                           struct spvlb_table **tablep)
593 {
594   *tablep = NULL;
595
596   if (!spv_item_is_light_table (item))
597     return xstrdup ("not a light binary table object");
598
599   void *data;
600   size_t size;
601   char *error = spv_item_get_raw_light_table (item, &data, &size);
602   if (error)
603     return error;
604
605   struct spvbin_input input;
606   spvbin_input_init (&input, data, size);
607
608   struct spvlb_table *table = NULL;
609   error = (!size
610            ? xasprintf ("light table member is empty")
611            : !spvlb_parse_table (&input, &table)
612            ? spvbin_input_to_error (&input, NULL)
613            : input.ofs != input.size
614            ? xasprintf ("expected end of file at offset %#zx", input.ofs)
615            : NULL);
616   if (error)
617     {
618       struct string s = DS_EMPTY_INITIALIZER;
619       spv_item_format_path (item, &s);
620       ds_put_format (&s, " (%s): %s", item->bin_member, error);
621
622       free (error);
623       error = ds_steal_cstr (&s);
624     }
625   free (data);
626   if (!error)
627     *tablep = table;
628   return error;
629 }
630
631 static char *
632 pivot_table_open_light (struct spv_item *item)
633 {
634   assert (spv_item_is_light_table (item));
635
636   struct spvlb_table *raw_table;
637   char *error = spv_item_get_light_table (item, &raw_table);
638   if (!error)
639     error = decode_spvlb_table (raw_table, &item->table);
640   spvlb_free_table (raw_table);
641
642   return error;
643 }
644
645 bool
646 spv_item_is_legacy_table (const struct spv_item *item)
647 {
648   return item->type == SPV_ITEM_TABLE && item->xml_member;
649 }
650
651 char * WARN_UNUSED_RESULT
652 spv_item_get_raw_legacy_data (const struct spv_item *item,
653                               void **data, size_t *size)
654 {
655   if (!spv_item_is_legacy_table (item))
656     return xstrdup ("not a legacy table object");
657
658   return zip_member_read_all (item->spv->zip, item->bin_member, data, size);
659 }
660
661 char * WARN_UNUSED_RESULT
662 spv_item_get_legacy_data (const struct spv_item *item, struct spv_data *data)
663 {
664   void *raw;
665   size_t size;
666   char *error = spv_item_get_raw_legacy_data (item, &raw, &size);
667   if (!error)
668     {
669       error = spv_legacy_data_decode (raw, size, data);
670       free (raw);
671     }
672
673   return error;
674 }
675
676 static char * WARN_UNUSED_RESULT
677 spv_read_xml_member (struct spv_reader *spv, const char *member_name,
678                      bool keep_blanks, const char *root_element_name,
679                      xmlDoc **docp)
680 {
681   *docp = NULL;
682
683   struct zip_member *zm = zip_member_open (spv->zip, member_name);
684   if (!zm)
685     return ds_steal_cstr (&spv->zip_errs);
686
687   xmlParserCtxt *parser;
688   xmlKeepBlanksDefault (keep_blanks);
689   parser = xmlCreatePushParserCtxt(NULL, NULL, NULL, 0, NULL);
690   if (!parser)
691     {
692       zip_member_finish (zm);
693       return xasprintf (_("%s: Failed to create XML parser"), member_name);
694     }
695
696   int retval;
697   char buf[4096];
698   while ((retval = zip_member_read (zm, buf, sizeof buf)) > 0)
699     xmlParseChunk (parser, buf, retval, false);
700   xmlParseChunk (parser, NULL, 0, true);
701
702   xmlDoc *doc = parser->myDoc;
703   bool well_formed = parser->wellFormed;
704   xmlFreeParserCtxt (parser);
705
706   if (retval < 0)
707     {
708       char *error = ds_steal_cstr (&spv->zip_errs);
709       zip_member_finish (zm);
710       xmlFreeDoc (doc);
711       return error;
712     }
713   zip_member_finish (zm);
714
715   if (!well_formed)
716     {
717       xmlFreeDoc (doc);
718       return xasprintf(_("%s: document is not well-formed"), member_name);
719     }
720
721   const xmlNode *root_node = xmlDocGetRootElement (doc);
722   assert (root_node->type == XML_ELEMENT_NODE);
723   if (strcmp (CHAR_CAST (char *, root_node->name), root_element_name))
724     {
725       xmlFreeDoc (doc);
726       return xasprintf(_("%s: root node is \"%s\" but \"%s\" was expected"),
727                        member_name,
728                        CHAR_CAST (char *, root_node->name), root_element_name);
729     }
730
731   *docp = doc;
732   return NULL;
733 }
734
735 char * WARN_UNUSED_RESULT
736 spv_item_get_legacy_table (const struct spv_item *item, xmlDoc **docp)
737 {
738   assert (spv_item_is_legacy_table (item));
739
740   return spv_read_xml_member (item->spv, item->xml_member, false,
741                               "visualization", docp);
742 }
743
744 char * WARN_UNUSED_RESULT
745 spv_item_get_structure (const struct spv_item *item, struct _xmlDoc **docp)
746 {
747   return spv_read_xml_member (item->spv, item->structure_member, false,
748                               "heading", docp);
749 }
750
751 static const char *
752 identify_item (const struct spv_item *item)
753 {
754   return (item->label ? item->label
755           : item->command_id ? item->command_id
756           : spv_item_type_to_string (item->type));
757 }
758
759 void
760 spv_item_format_path (const struct spv_item *item, struct string *s)
761 {
762   enum { MAX_STACK = 32 };
763   const struct spv_item *stack[MAX_STACK];
764   size_t n = 0;
765
766   while (item != NULL && item->parent && n < MAX_STACK)
767     {
768       stack[n++] = item;
769       item = item->parent;
770     }
771
772   while (n > 0)
773     {
774       item = stack[--n];
775       ds_put_byte (s, '/');
776
777       const char *name = identify_item (item);
778       ds_put_cstr (s, name);
779
780       if (item->parent)
781         {
782           size_t total = 1;
783           size_t index = 1;
784           for (size_t i = 0; i < item->parent->n_children; i++)
785             {
786               const struct spv_item *sibling = item->parent->children[i];
787               if (sibling == item)
788                 index = total;
789               else if (!strcmp (name, identify_item (sibling)))
790                 total++;
791             }
792           if (total > 1)
793             ds_put_format (s, "[%zu]", index);
794         }
795     }
796 }
797
798 static char * WARN_UNUSED_RESULT
799 pivot_table_open_legacy (struct spv_item *item)
800 {
801   assert (spv_item_is_legacy_table (item));
802
803   struct spv_data data;
804   char *error = spv_item_get_legacy_data (item, &data);
805   if (error)
806     {
807       struct string s = DS_EMPTY_INITIALIZER;
808       spv_item_format_path (item, &s);
809       ds_put_format (&s, " (%s): %s", item->bin_member, error);
810
811       free (error);
812       return ds_steal_cstr (&s);
813     }
814
815   xmlDoc *doc;
816   error = spv_read_xml_member (item->spv, item->xml_member, false,
817                                "visualization", &doc);
818   if (error)
819     {
820       spv_data_uninit (&data);
821       return error;
822     }
823
824   struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
825   struct spvdx_visualization *v;
826   spvdx_parse_visualization (&ctx, xmlDocGetRootElement (doc), &v);
827   error = spvxml_context_finish (&ctx, &v->node_);
828
829   if (!error)
830     error = decode_spvdx_table (v, item->subtype, item->legacy_properties,
831                                 &data, &item->table);
832
833   if (error)
834     {
835       struct string s = DS_EMPTY_INITIALIZER;
836       spv_item_format_path (item, &s);
837       ds_put_format (&s, " (%s): %s", item->xml_member, error);
838
839       free (error);
840       error = ds_steal_cstr (&s);
841     }
842
843   spv_data_uninit (&data);
844   spvdx_free_visualization (v);
845   if (doc)
846     xmlFreeDoc (doc);
847
848   return error;
849 }
850
851 struct pivot_table *
852 spv_item_get_table (const struct spv_item *item_)
853 {
854   struct spv_item *item = CONST_CAST (struct spv_item *, item_);
855
856   assert (spv_item_is_table (item));
857   if (!item->table)
858     {
859       char *error = (item->xml_member
860                      ? pivot_table_open_legacy (item)
861                      : pivot_table_open_light (item));
862       if (error)
863         {
864           item->error = true;
865           msg (ME, "%s", error);
866           item->table = pivot_table_create_for_text (
867             pivot_value_new_text (N_("Error")),
868             pivot_value_new_user_text (error, -1));
869           free (error);
870         }
871     }
872
873   return item->table;
874 }
875
876 /* Constructs a new spv_item from XML and stores it in *ITEMP.  Returns NULL if
877    successful, otherwise an error message for the caller to use and free (with
878    free()).
879
880    XML should be a 'heading' or 'container' element. */
881 static char * WARN_UNUSED_RESULT
882 spv_decode_container (const struct spvsx_container *c,
883                       const char *structure_member,
884                       struct spv_item *parent)
885 {
886   struct spv_item *item = xzalloc (sizeof *item);
887   item->spv = parent->spv;
888   item->label = xstrdup (c->label->text);
889   item->visible = c->visibility == SPVSX_VISIBILITY_VISIBLE;
890   item->structure_member = xstrdup (structure_member);
891
892   assert (c->n_seq == 1);
893   struct spvxml_node *content = c->seq[0];
894   if (spvsx_is_container_text (content))
895     decode_container_text (spvsx_cast_container_text (content), item);
896   else if (spvsx_is_table (content))
897     {
898       item->type = SPV_ITEM_TABLE;
899
900       struct spvsx_table *table = spvsx_cast_table (content);
901       const struct spvsx_table_structure *ts = table->table_structure;
902       item->bin_member = xstrdup (ts->data_path->text);
903       item->command_id = xstrdup_if_nonempty (table->command_name);
904       item->subtype = xstrdup_if_nonempty (table->sub_type);
905       if (ts->path)
906         {
907           item->xml_member = ts->path ? xstrdup (ts->path->text) : NULL;
908           char *error = decode_spvsx_legacy_properties (
909             table->table_properties, &item->legacy_properties);
910           if (error)
911             {
912               spv_item_destroy (item);
913               return error;
914             }
915         }
916     }
917   else if (spvsx_is_graph (content))
918     {
919       struct spvsx_graph *graph = spvsx_cast_graph (content);
920       item->type = SPV_ITEM_GRAPH;
921       item->command_id = xstrdup_if_nonempty (graph->command_name);
922       /* XXX */
923     }
924   else if (spvsx_is_model (content))
925     {
926       struct spvsx_model *model = spvsx_cast_model (content);
927       item->type = SPV_ITEM_MODEL;
928       item->command_id = xstrdup_if_nonempty (model->command_name);
929       /* XXX */
930     }
931   else if (spvsx_is_object (content))
932     {
933       struct spvsx_object *object = spvsx_cast_object (content);
934       item->type = SPV_ITEM_OBJECT;
935       item->object_type = xstrdup (object->type);
936       item->uri = xstrdup (object->uri);
937     }
938   else if (spvsx_is_image (content))
939     {
940       struct spvsx_image *image = spvsx_cast_image (content);
941       item->type = SPV_ITEM_OBJECT;
942       item->object_type = xstrdup ("image");
943       item->uri = xstrdup (image->data_path->text);
944     }
945   else if (spvsx_is_tree (content))
946     {
947       struct spvsx_tree *tree = spvsx_cast_tree (content);
948       item->type = SPV_ITEM_TREE;
949       item->object_type = xstrdup ("tree");
950       item->uri = xstrdup (tree->data_path->text);
951     }
952   else
953     NOT_REACHED ();
954
955   spv_heading_add_child (parent, item);
956   return NULL;
957 }
958
959 static char * WARN_UNUSED_RESULT
960 spv_decode_children (struct spv_reader *spv, const char *structure_member,
961                      struct spvxml_node **seq, size_t n_seq,
962                      struct spv_item *parent)
963 {
964   for (size_t i = 0; i < n_seq; i++)
965     {
966       const struct spvxml_node *node = seq[i];
967
968       char *error = NULL;
969       if (spvsx_is_container (node))
970         {
971           const struct spvsx_container *container
972             = spvsx_cast_container (node);
973           error = spv_decode_container (container, structure_member, parent);
974         }
975       else if (spvsx_is_heading (node))
976         {
977           const struct spvsx_heading *subheading = spvsx_cast_heading (node);
978           struct spv_item *subitem = xzalloc (sizeof *subitem);
979           subitem->structure_member = xstrdup (structure_member);
980           subitem->spv = parent->spv;
981           subitem->type = SPV_ITEM_HEADING;
982           subitem->label = xstrdup (subheading->label->text);
983           if (subheading->command_name)
984             subitem->command_id = xstrdup (subheading->command_name);
985           subitem->visible = !subheading->heading_visibility_present;
986           spv_heading_add_child (parent, subitem);
987
988           error = spv_decode_children (spv, structure_member,
989                                        subheading->seq, subheading->n_seq,
990                                        subitem);
991         }
992       else
993         NOT_REACHED ();
994
995       if (error)
996         return error;
997     }
998
999   return NULL;
1000 }
1001
1002 static struct page_setup *
1003 decode_page_setup (const struct spvsx_page_setup *in, const char *file_name)
1004 {
1005   struct page_setup *out = xmalloc (sizeof *out);
1006   *out = (struct page_setup) PAGE_SETUP_INITIALIZER;
1007
1008   out->initial_page_number = in->initial_page_number;
1009
1010   if (in->paper_width != DBL_MAX)
1011     out->paper[TABLE_HORZ] = in->paper_width;
1012   if (in->paper_height != DBL_MAX)
1013     out->paper[TABLE_VERT] = in->paper_height;
1014
1015   if (in->margin_left != DBL_MAX)
1016     out->margins[TABLE_HORZ][0] = in->margin_left;
1017   if (in->margin_right != DBL_MAX)
1018     out->margins[TABLE_HORZ][1] = in->margin_right;
1019   if (in->margin_top != DBL_MAX)
1020     out->margins[TABLE_VERT][0] = in->margin_top;
1021   if (in->margin_bottom != DBL_MAX)
1022     out->margins[TABLE_VERT][1] = in->margin_bottom;
1023
1024   if (in->space_after != DBL_MAX)
1025     out->object_spacing = in->space_after;
1026
1027   if (in->chart_size)
1028     out->chart_size = (in->chart_size == SPVSX_CHART_SIZE_FULL_HEIGHT
1029                        ? PAGE_CHART_FULL_HEIGHT
1030                        : in->chart_size == SPVSX_CHART_SIZE_HALF_HEIGHT
1031                        ? PAGE_CHART_HALF_HEIGHT
1032                        : in->chart_size == SPVSX_CHART_SIZE_QUARTER_HEIGHT
1033                        ? PAGE_CHART_QUARTER_HEIGHT
1034                        : PAGE_CHART_AS_IS);
1035
1036   decode_page_paragraph (in->page_header->page_paragraph, &out->headings[0]);
1037   decode_page_paragraph (in->page_footer->page_paragraph, &out->headings[1]);
1038
1039   out->file_name = xstrdup (file_name);
1040
1041   return out;
1042 }
1043
1044 static char * WARN_UNUSED_RESULT
1045 spv_heading_read (struct spv_reader *spv,
1046                   const char *file_name, const char *member_name)
1047 {
1048   xmlDoc *doc;
1049   char *error = spv_read_xml_member (spv, member_name, true, "heading", &doc);
1050   if (error)
1051     return error;
1052
1053   struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
1054   struct spvsx_root_heading *root;
1055   spvsx_parse_root_heading (&ctx, xmlDocGetRootElement (doc), &root);
1056   error = spvxml_context_finish (&ctx, &root->node_);
1057
1058   if (!error && root->page_setup)
1059     spv->page_setup = decode_page_setup (root->page_setup, file_name);
1060
1061   for (size_t i = 0; !error && i < root->n_seq; i++)
1062     error = spv_decode_children (spv, member_name, root->seq, root->n_seq,
1063                                  spv->root);
1064
1065   if (error)
1066     {
1067       char *s = xasprintf ("%s: %s", member_name, error);
1068       free (error);
1069       error = s;
1070     }
1071
1072   spvsx_free_root_heading (root);
1073   xmlFreeDoc (doc);
1074
1075   return error;
1076 }
1077
1078 struct spv_item *
1079 spv_get_root (const struct spv_reader *spv)
1080 {
1081   return spv->root;
1082 }
1083
1084 static int
1085 spv_detect__ (struct zip_reader *zip, char **errorp)
1086 {
1087   *errorp = NULL;
1088
1089   const char *member = "META-INF/MANIFEST.MF";
1090   if (!zip_reader_contains_member (zip, member))
1091     return 0;
1092
1093   void *data;
1094   size_t size;
1095   *errorp = zip_member_read_all (zip, "META-INF/MANIFEST.MF",
1096                                  &data, &size);
1097   if (*errorp)
1098     return -1;
1099
1100   const char *magic = "allowPivoting=true";
1101   bool is_spv = size == strlen (magic) && !memcmp (magic, data, size);
1102   free (data);
1103
1104   return is_spv;
1105 }
1106
1107 /* Returns NULL if FILENAME is an SPV file, otherwise an error string that the
1108    caller must eventually free(). */
1109 char * WARN_UNUSED_RESULT
1110 spv_detect (const char *filename)
1111 {
1112   struct string zip_error;
1113   struct zip_reader *zip = zip_reader_create (filename, &zip_error);
1114   if (!zip)
1115     return ds_steal_cstr (&zip_error);
1116
1117   char *error;
1118   if (spv_detect__ (zip, &error) <= 0 && !error)
1119     error = xasprintf("%s: not an SPV file", filename);
1120   zip_reader_destroy (zip);
1121   ds_destroy (&zip_error);
1122   return error;
1123 }
1124
1125 char * WARN_UNUSED_RESULT
1126 spv_open (const char *filename, struct spv_reader **spvp)
1127 {
1128   *spvp = NULL;
1129
1130   struct spv_reader *spv = xzalloc (sizeof *spv);
1131   ds_init_empty (&spv->zip_errs);
1132   spv->zip = zip_reader_create (filename, &spv->zip_errs);
1133   if (!spv->zip)
1134     {
1135       char *error = ds_steal_cstr (&spv->zip_errs);
1136       spv_close (spv);
1137       return error;
1138     }
1139
1140   char *error;
1141   int detect = spv_detect__ (spv->zip, &error);
1142   if (detect <= 0)
1143     {
1144       spv_close (spv);
1145       return error ? error : xasprintf("%s: not an SPV file", filename);
1146     }
1147
1148   spv->root = xzalloc (sizeof *spv->root);
1149   spv->root->spv = spv;
1150   spv->root->type = SPV_ITEM_HEADING;
1151   for (size_t i = 0; ; i++)
1152     {
1153       const char *member_name = zip_reader_get_member_name (spv->zip, i);
1154       if (!member_name)
1155         break;
1156
1157       struct substring member_name_ss = ss_cstr (member_name);
1158       if (ss_starts_with (member_name_ss, ss_cstr ("outputViewer"))
1159           && ss_ends_with (member_name_ss, ss_cstr (".xml")))
1160         {
1161           char *error = spv_heading_read (spv, filename, member_name);
1162           if (error)
1163             {
1164               spv_close (spv);
1165               return error;
1166             }
1167         }
1168     }
1169
1170   *spvp = spv;
1171   return NULL;
1172 }
1173
1174 void
1175 spv_close (struct spv_reader *spv)
1176 {
1177   if (spv)
1178     {
1179       ds_destroy (&spv->zip_errs);
1180       zip_reader_destroy (spv->zip);
1181       spv_item_destroy (spv->root);
1182       page_setup_destroy (spv->page_setup);
1183       free (spv);
1184     }
1185 }
1186
1187 char * WARN_UNUSED_RESULT
1188 spv_decode_fmt_spec (uint32_t u32, struct fmt_spec *out)
1189 {
1190   if (!u32
1191       || (u32 == 0x10000 || u32 == 1 /* both used as string formats */))
1192     {
1193       *out = fmt_for_output (FMT_F, 40, 2);
1194       return NULL;
1195     }
1196
1197   uint8_t raw_type = u32 >> 16;
1198   uint8_t w = u32 >> 8;
1199   uint8_t d = u32;
1200
1201   msg_disable ();
1202   *out = (struct fmt_spec) { .type = FMT_F, .w = w, .d = d };
1203   bool ok = raw_type >= 40 || fmt_from_io (raw_type, &out->type);
1204   if (ok)
1205     {
1206       fmt_fix_output (out);
1207       ok = fmt_check_width_compat (out, 0);
1208     }
1209   msg_enable ();
1210
1211   if (!ok)
1212     {
1213       *out = fmt_for_output (FMT_F, 40, 2);
1214       return xasprintf ("bad format %#"PRIx32, u32);
1215     }
1216
1217   return NULL;
1218 }