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