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