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