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