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