fdafb5aba7c96c3ef3cb0ff9a815b2360257a96a
[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 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_IMAGE: return "image";
81     default: return "**error**";
82     }
83 }
84
85 const char *
86 spv_item_class_to_string (enum spv_item_class class)
87 {
88   switch (class)
89     {
90 #define SPV_CLASS(ENUM, NAME) case SPV_CLASS_##ENUM: return NAME;
91       SPV_CLASSES
92 #undef SPV_CLASS
93     default: return NULL;
94     }
95 }
96
97 enum spv_item_class
98 spv_item_class_from_string (const char *name)
99 {
100 #define SPV_CLASS(ENUM, NAME) \
101   if (!strcmp (name, NAME)) return SPV_CLASS_##ENUM;
102   SPV_CLASSES
103 #undef SPV_CLASS
104
105   return (enum spv_item_class) SPV_N_CLASSES;
106 }
107
108 enum spv_item_type
109 spv_item_get_type (const struct spv_item *item)
110 {
111   return item->type;
112 }
113
114 enum spv_item_class
115 spv_item_get_class (const struct spv_item *item)
116 {
117   const char *label = spv_item_get_label (item);
118   if (!label)
119     label = "";
120
121   switch (item->type)
122     {
123     case SPV_ITEM_HEADING:
124       return SPV_CLASS_HEADINGS;
125
126     case SPV_ITEM_TEXT:
127       return (!strcmp (label, "Title") ? SPV_CLASS_OUTLINEHEADERS
128               : !strcmp (label, "Log") ? SPV_CLASS_LOGS
129               : !strcmp (label, "Page Title") ? SPV_CLASS_PAGETITLE
130               : SPV_CLASS_TEXTS);
131
132     case SPV_ITEM_TABLE:
133       return (!strcmp (label, "Warnings") ? SPV_CLASS_WARNINGS
134               : !strcmp (label, "Notes") ? SPV_CLASS_NOTES
135               : SPV_CLASS_TABLES);
136
137     case SPV_ITEM_GRAPH:
138       return SPV_CLASS_CHARTS;
139
140     case SPV_ITEM_MODEL:
141       return SPV_CLASS_MODELS;
142
143     case SPV_ITEM_IMAGE:
144       return SPV_CLASS_OTHER;
145
146     case SPV_ITEM_TREE:
147       return SPV_CLASS_TREES;
148
149     default:
150       return SPV_CLASS_UNKNOWN;
151     }
152 }
153
154 const char *
155 spv_item_get_label (const struct spv_item *item)
156 {
157   return item->label;
158 }
159
160 bool
161 spv_item_is_heading (const struct spv_item *item)
162 {
163   return item->type == SPV_ITEM_HEADING;
164 }
165
166 size_t
167 spv_item_get_n_children (const struct spv_item *item)
168 {
169   return item->n_children;
170 }
171
172 struct spv_item *
173 spv_item_get_child (const struct spv_item *item, size_t idx)
174 {
175   assert (idx < item->n_children);
176   return item->children[idx];
177 }
178
179 bool
180 spv_item_is_table (const struct spv_item *item)
181 {
182   return item->type == SPV_ITEM_TABLE;
183 }
184
185 bool
186 spv_item_is_text (const struct spv_item *item)
187 {
188   return item->type == SPV_ITEM_TEXT;
189 }
190
191 const struct pivot_value *
192 spv_item_get_text (const struct spv_item *item)
193 {
194   assert (spv_item_is_text (item));
195   return item->text;
196 }
197
198 bool
199 spv_item_is_image (const struct spv_item *item)
200 {
201   return item->type == SPV_ITEM_IMAGE;
202 }
203
204 static cairo_status_t
205 read_from_zip_member (void *zm_, unsigned char *data, unsigned int length)
206 {
207   struct zip_member *zm = zm_;
208   if (!zm)
209     return CAIRO_STATUS_READ_ERROR;
210
211   while (length > 0)
212     {
213       int n = zip_member_read (zm, data, length);
214       if (n <= 0)
215         return CAIRO_STATUS_READ_ERROR;
216
217       data += n;
218       length -= n;
219     }
220
221   return CAIRO_STATUS_SUCCESS;
222 }
223
224 cairo_surface_t *
225 spv_item_get_image (const struct spv_item *item_)
226 {
227   struct spv_item *item = CONST_CAST (struct spv_item *, item_);
228   assert (spv_item_is_image (item));
229
230   if (!item->image)
231     {
232       struct zip_member *zm = zip_member_open (item->spv->zip,
233                                                item->png_member);
234       item->image = cairo_image_surface_create_from_png_stream (
235         read_from_zip_member, zm);
236       if (zm)
237         zip_member_finish (zm);
238     }
239
240   return item->image;
241 }
242
243 struct spv_item *
244 spv_item_next (const struct spv_item *item)
245 {
246   if (item->n_children)
247     return item->children[0];
248
249   while (item->parent)
250     {
251       size_t idx = item->parent_idx + 1;
252       item = item->parent;
253       if (idx < item->n_children)
254         return item->children[idx];
255     }
256
257   return NULL;
258 }
259
260 const struct spv_item *
261 spv_item_get_parent (const struct spv_item *item)
262 {
263   return item->parent;
264 }
265
266 size_t
267 spv_item_get_level (const struct spv_item *item)
268 {
269   int level = 0;
270   for (; item->parent; item = item->parent)
271     level++;
272   return level;
273 }
274
275 const char *
276 spv_item_get_command_id (const struct spv_item *item)
277 {
278   return item->command_id;
279 }
280
281 const char *
282 spv_item_get_subtype (const struct spv_item *item)
283 {
284   return item->subtype;
285 }
286
287 bool
288 spv_item_is_visible (const struct spv_item *item)
289 {
290   return item->visible;
291 }
292
293 static void
294 spv_item_destroy (struct spv_item *item)
295 {
296   if (item)
297     {
298       free (item->structure_member);
299
300       free (item->label);
301       free (item->command_id);
302
303       for (size_t i = 0; i < item->n_children; i++)
304         spv_item_destroy (item->children[i]);
305       free (item->children);
306
307       pivot_table_unref (item->table);
308       pivot_table_look_unref (item->table_look);
309       free (item->bin_member);
310       free (item->xml_member);
311       free (item->subtype);
312
313       pivot_value_destroy (item->text);
314
315       free (item->png_member);
316       if (item->image)
317         cairo_surface_destroy (item->image);
318
319       free (item);
320     }
321 }
322
323 static void
324 spv_heading_add_child (struct spv_item *parent, struct spv_item *child)
325 {
326   assert (parent->type == SPV_ITEM_HEADING);
327   assert (!child->parent);
328
329   child->parent = parent;
330   child->parent_idx = parent->n_children;
331
332   if (parent->n_children >= parent->allocated_children)
333     parent->children = x2nrealloc (parent->children,
334                                    &parent->allocated_children,
335                                    sizeof *parent->children);
336   parent->children[parent->n_children++] = child;
337 }
338
339 static xmlNode *
340 find_xml_child_element (xmlNode *parent, const char *child_name)
341 {
342   for (xmlNode *node = parent->children; node; node = node->next)
343     if (node->type == XML_ELEMENT_NODE
344         && node->name
345         && !strcmp (CHAR_CAST (char *, node->name), child_name))
346       return node;
347
348   return NULL;
349 }
350
351 static char *
352 get_xml_attr (const xmlNode *node, const char *name)
353 {
354   return CHAR_CAST (char *, xmlGetProp (node, CHAR_CAST (xmlChar *, name)));
355 }
356
357 static void
358 put_xml_attr (const char *name, const char *value, struct string *dst)
359 {
360   if (!value)
361     return;
362
363   ds_put_format (dst, " %s=\"", name);
364   for (const char *p = value; *p; p++)
365     {
366       switch (*p)
367         {
368         case '\n':
369           ds_put_cstr (dst, "&#10;");
370           break;
371         case '&':
372           ds_put_cstr (dst, "&amp;");
373           break;
374         case '<':
375           ds_put_cstr (dst, "&lt;");
376           break;
377         case '>':
378           ds_put_cstr (dst, "&gt;");
379           break;
380         case '"':
381           ds_put_cstr (dst, "&quot;");
382           break;
383         default:
384           ds_put_byte (dst, *p);
385           break;
386         }
387     }
388   ds_put_byte (dst, '"');
389 }
390
391 static void
392 extract_html_text (const xmlNode *node, int base_font_size, struct string *s)
393 {
394   if (node->type == XML_ELEMENT_NODE)
395     {
396       const char *name = CHAR_CAST (char *, node->name);
397       if (!strcmp (name, "br"))
398         ds_put_byte (s, '\n');
399       else if (strcmp (name, "style"))
400         {
401           const char *tag = NULL;
402           if (strchr ("biu", name[0]) && name[1] == '\0')
403             {
404               tag = name;
405               ds_put_format (s, "<%s>", tag);
406             }
407           else if (!strcmp (name, "font"))
408             {
409               tag = "span";
410               ds_put_format (s, "<%s", tag);
411
412               char *face = get_xml_attr (node, "face");
413               put_xml_attr ("face", face, s);
414               free (face);
415
416               char *color = get_xml_attr (node, "color");
417               if (color)
418                 {
419                   if (color[0] == '#')
420                     put_xml_attr ("color", color, s);
421                   else
422                     {
423                       uint8_t r, g, b;
424                       if (sscanf (color, "rgb (%"SCNu8", %"SCNu8", %"SCNu8")",
425                                   &r, &g, &b) == 3)
426                         {
427                           char color2[8];
428                           snprintf (color2, sizeof color2,
429                                     "#%02"PRIx8"%02"PRIx8"%02"PRIx8,
430                                     r, g, b);
431                           put_xml_attr ("color", color2, s);
432                         }
433                     }
434                 }
435               free (color);
436
437               char *size_s = get_xml_attr (node, "size");
438               int html_size = size_s ? atoi (size_s) : 0;
439               free (size_s);
440               if (html_size >= 1 && html_size <= 7)
441                 {
442                   static const double scale[7] = {
443                     .444, .556, .667, .778, 1.0, 1.33, 2.0
444                   };
445                   double size = base_font_size * scale[html_size - 1];
446
447                   char size2[INT_BUFSIZE_BOUND (int)];
448                   snprintf (size2, sizeof size2, "%.0f", size * 1024.);
449                   put_xml_attr ("size", size2, s);
450                 }
451
452               ds_put_cstr (s, ">");
453             }
454           for (const xmlNode *child = node->children; child;
455                child = child->next)
456             extract_html_text (child, base_font_size, s);
457           if (tag)
458             ds_put_format (s, "</%s>", tag);
459         }
460     }
461   else if (node->type == XML_TEXT_NODE)
462     {
463       /* U+00A0 NONBREAKING SPACE is really, really common in SPV text and it
464          makes it impossible to break syntax across lines.  Translate it into a
465          regular space.  (Note that U+00A0 is C2 A0 in UTF-8.)
466
467          Do the same for U+2007 FIGURE SPACE, which also crops out weirdly
468          sometimes. */
469       ds_extend (s, ds_length (s) + xmlStrlen (node->content));
470       for (const uint8_t *p = node->content; *p;)
471         {
472           int c;
473           if (p[0] == 0xc2 && p[1] == 0xa0)
474             {
475               c = ' ';
476               p += 2;
477             }
478           else if (p[0] == 0xe2 && p[1] == 0x80 && p[2] == 0x87)
479             {
480               c = ' ';
481               p += 3;
482             }
483           else
484             c = *p++;
485
486           if (c_isspace (c))
487             {
488               int last = ds_last (s);
489               if (last != EOF && !c_isspace (last))
490                 ds_put_byte (s, c);
491             }
492           else if (c == '<')
493             ds_put_cstr (s, "&lt;");
494           else if (c == '>')
495             ds_put_cstr (s, "&gt;");
496           else if (c == '&')
497             ds_put_cstr (s, "&amp;");
498           else
499             ds_put_byte (s, c);
500         }
501     }
502 }
503
504 static xmlDoc *
505 parse_embedded_html (const xmlNode *node)
506 {
507   /* Extract HTML from XML node. */
508   char *html_s = CHAR_CAST (char *, xmlNodeGetContent (node));
509   if (!html_s)
510     xalloc_die ();
511
512   xmlDoc *html_doc = htmlReadMemory (
513     html_s, strlen (html_s),
514     NULL, "UTF-8", (HTML_PARSE_RECOVER | HTML_PARSE_NOERROR
515                     | HTML_PARSE_NOWARNING | HTML_PARSE_NOBLANKS
516                     | HTML_PARSE_NONET));
517   free (html_s);
518
519   return html_doc;
520 }
521
522 /* Given NODE, which should contain HTML content, returns the text within that
523    content as an allocated string.  The caller must eventually free the
524    returned string (with xmlFree()). */
525 static char *
526 decode_embedded_html (const xmlNode *node, struct font_style *font_style)
527 {
528   struct string markup = DS_EMPTY_INITIALIZER;
529   *font_style = (struct font_style) FONT_STYLE_INITIALIZER;
530   font_style->size = 10;
531
532   xmlDoc *html_doc = parse_embedded_html (node);
533   if (html_doc)
534     {
535       xmlNode *root = xmlDocGetRootElement (html_doc);
536       xmlNode *head = root ? find_xml_child_element (root, "head") : NULL;
537       xmlNode *style = head ? find_xml_child_element (head, "style") : NULL;
538       if (style)
539         {
540           uint8_t *style_s = xmlNodeGetContent (style);
541           spv_parse_css_style (CHAR_CAST (char *, style_s), font_style);
542           xmlFree (style_s);
543         }
544
545       if (root)
546         extract_html_text (root, font_style->size, &markup);
547       xmlFreeDoc (html_doc);
548     }
549
550   font_style->markup = true;
551   return ds_steal_cstr (&markup);
552 }
553
554 static char *
555 xstrdup_if_nonempty (const char *s)
556 {
557   return s && s[0] ? xstrdup (s) : NULL;
558 }
559
560 static void
561 decode_container_text (const struct spvsx_container_text *ct,
562                        struct spv_item *item)
563 {
564   item->type = SPV_ITEM_TEXT;
565   item->command_id = xstrdup_if_nonempty (ct->command_name);
566
567   item->text = xzalloc (sizeof *item->text);
568   item->text->type = PIVOT_VALUE_TEXT;
569   item->text->font_style = xmalloc (sizeof *item->text->font_style);
570   item->text->text.local = decode_embedded_html (ct->html->node_.raw,
571                                                  item->text->font_style);
572 }
573
574 static void
575 decode_page_p (const xmlNode *in, struct page_paragraph *out)
576 {
577   char *style = get_xml_attr (in, "style");
578   out->halign = (style && strstr (style, "center") ? TABLE_HALIGN_CENTER
579                  : style && strstr (style, "right") ? TABLE_HALIGN_RIGHT
580                  : TABLE_HALIGN_LEFT);
581   free (style);
582
583   struct font_style font_style;
584   out->markup = decode_embedded_html (in, &font_style);
585   font_style_uninit (&font_style);
586 }
587
588 static void
589 decode_page_paragraph (const struct spvsx_page_paragraph *page_paragraph,
590                        struct page_heading *ph)
591 {
592   memset (ph, 0, sizeof *ph);
593
594   const struct spvsx_page_paragraph_text *page_paragraph_text
595     = page_paragraph->page_paragraph_text;
596   if (!page_paragraph_text)
597     return;
598
599   xmlDoc *html_doc = parse_embedded_html (page_paragraph_text->node_.raw);
600   if (!html_doc)
601     return;
602
603   xmlNode *root = xmlDocGetRootElement (html_doc);
604   xmlNode *body = find_xml_child_element (root, "body");
605   if (body)
606     for (const xmlNode *node = body->children; node; node = node->next)
607       if (node->type == XML_ELEMENT_NODE
608           && !strcmp (CHAR_CAST (const char *, node->name), "p"))
609         {
610           ph->paragraphs = xrealloc (ph->paragraphs,
611                                      (ph->n + 1) * sizeof *ph->paragraphs);
612           decode_page_p (node, &ph->paragraphs[ph->n++]);
613         }
614   xmlFreeDoc (html_doc);
615 }
616
617 void
618 spv_item_load (const struct spv_item *item)
619 {
620   if (spv_item_is_table (item))
621     spv_item_get_table (item);
622   else if (spv_item_is_image (item))
623     spv_item_get_image (item);
624 }
625
626 bool
627 spv_item_is_light_table (const struct spv_item *item)
628 {
629   return item->type == SPV_ITEM_TABLE && !item->xml_member;
630 }
631
632 char * WARN_UNUSED_RESULT
633 spv_item_get_raw_light_table (const struct spv_item *item,
634                               void **data, size_t *size)
635 {
636   return zip_member_read_all (item->spv->zip, item->bin_member, data, size);
637 }
638
639 char * WARN_UNUSED_RESULT
640 spv_item_get_light_table (const struct spv_item *item,
641                           struct spvlb_table **tablep)
642 {
643   *tablep = NULL;
644
645   if (!spv_item_is_light_table (item))
646     return xstrdup ("not a light binary table object");
647
648   void *data;
649   size_t size;
650   char *error = spv_item_get_raw_light_table (item, &data, &size);
651   if (error)
652     return error;
653
654   struct spvbin_input input;
655   spvbin_input_init (&input, data, size);
656
657   struct spvlb_table *table = NULL;
658   error = (!size
659            ? xasprintf ("light table member is empty")
660            : !spvlb_parse_table (&input, &table)
661            ? spvbin_input_to_error (&input, NULL)
662            : input.ofs != input.size
663            ? xasprintf ("expected end of file at offset %#zx", input.ofs)
664            : NULL);
665   if (error)
666     {
667       struct string s = DS_EMPTY_INITIALIZER;
668       spv_item_format_path (item, &s);
669       ds_put_format (&s, " (%s): %s", item->bin_member, error);
670
671       free (error);
672       error = ds_steal_cstr (&s);
673     }
674   free (data);
675   if (!error)
676     *tablep = table;
677   return error;
678 }
679
680 static char *
681 pivot_table_open_light (struct spv_item *item)
682 {
683   assert (spv_item_is_light_table (item));
684
685   struct spvlb_table *raw_table;
686   char *error = spv_item_get_light_table (item, &raw_table);
687   if (!error)
688     error = decode_spvlb_table (raw_table, &item->table);
689   spvlb_free_table (raw_table);
690
691   return error;
692 }
693
694 bool
695 spv_item_is_legacy_table (const struct spv_item *item)
696 {
697   return item->type == SPV_ITEM_TABLE && item->xml_member;
698 }
699
700 char * WARN_UNUSED_RESULT
701 spv_item_get_raw_legacy_data (const struct spv_item *item,
702                               void **data, size_t *size)
703 {
704   if (!spv_item_is_legacy_table (item))
705     return xstrdup ("not a legacy table object");
706
707   return zip_member_read_all (item->spv->zip, item->bin_member, data, size);
708 }
709
710 char * WARN_UNUSED_RESULT
711 spv_item_get_legacy_data (const struct spv_item *item, struct spv_data *data)
712 {
713   void *raw;
714   size_t size;
715   char *error = spv_item_get_raw_legacy_data (item, &raw, &size);
716   if (!error)
717     {
718       error = spv_legacy_data_decode (raw, size, data);
719       free (raw);
720     }
721
722   return error;
723 }
724
725 static char * WARN_UNUSED_RESULT
726 spv_read_xml_member (struct spv_reader *spv, const char *member_name,
727                      bool keep_blanks, const char *root_element_name,
728                      xmlDoc **docp)
729 {
730   *docp = NULL;
731
732   struct zip_member *zm = zip_member_open (spv->zip, member_name);
733   if (!zm)
734     return ds_steal_cstr (&spv->zip_errs);
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 = ds_steal_cstr (&spv->zip_errs);
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 string zip_error;
1157   struct zip_reader *zip = zip_reader_create (filename, &zip_error);
1158   if (!zip)
1159     return ds_steal_cstr (&zip_error);
1160
1161   char *error;
1162   if (spv_detect__ (zip, &error) <= 0 && !error)
1163     error = xasprintf("%s: not an SPV file", filename);
1164   zip_reader_destroy (zip);
1165   ds_destroy (&zip_error);
1166   return error;
1167 }
1168
1169 char * WARN_UNUSED_RESULT
1170 spv_open (const char *filename, struct spv_reader **spvp)
1171 {
1172   *spvp = NULL;
1173
1174   struct spv_reader *spv = xzalloc (sizeof *spv);
1175   ds_init_empty (&spv->zip_errs);
1176   spv->zip = zip_reader_create (filename, &spv->zip_errs);
1177   if (!spv->zip)
1178     {
1179       char *error = ds_steal_cstr (&spv->zip_errs);
1180       spv_close (spv);
1181       return error;
1182     }
1183
1184   char *error;
1185   int detect = spv_detect__ (spv->zip, &error);
1186   if (detect <= 0)
1187     {
1188       spv_close (spv);
1189       return error ? error : xasprintf("%s: not an SPV file", filename);
1190     }
1191
1192   spv->root = xzalloc (sizeof *spv->root);
1193   spv->root->spv = spv;
1194   spv->root->type = SPV_ITEM_HEADING;
1195   for (size_t i = 0; ; i++)
1196     {
1197       const char *member_name = zip_reader_get_member_name (spv->zip, i);
1198       if (!member_name)
1199         break;
1200
1201       struct substring member_name_ss = ss_cstr (member_name);
1202       if (ss_starts_with (member_name_ss, ss_cstr ("outputViewer"))
1203           && ss_ends_with (member_name_ss, ss_cstr (".xml")))
1204         {
1205           char *error = spv_heading_read (spv, filename, member_name);
1206           if (error)
1207             {
1208               spv_close (spv);
1209               return error;
1210             }
1211         }
1212     }
1213
1214   *spvp = spv;
1215   return NULL;
1216 }
1217
1218 void
1219 spv_close (struct spv_reader *spv)
1220 {
1221   if (spv)
1222     {
1223       ds_destroy (&spv->zip_errs);
1224       zip_reader_destroy (spv->zip);
1225       spv_item_destroy (spv->root);
1226       page_setup_destroy (spv->page_setup);
1227       free (spv);
1228     }
1229 }
1230
1231 void
1232 spv_item_set_table_look (struct spv_item *item,
1233                          const struct pivot_table_look *look)
1234 {
1235   /* If this is a table, install the table look in it.
1236
1237      (We can't just set item->table_look because light tables ignore it and
1238      legacy tables sometimes override it.) */
1239   if (spv_item_is_table (item))
1240     {
1241       spv_item_load (item);
1242       pivot_table_set_look (item->table, look);
1243     }
1244
1245   for (size_t i = 0; i < item->n_children; i++)
1246     spv_item_set_table_look (item->children[i], look);
1247 }
1248
1249 char * WARN_UNUSED_RESULT
1250 spv_decode_fmt_spec (uint32_t u32, struct fmt_spec *out)
1251 {
1252   if (!u32
1253       || (u32 == 0x10000 || u32 == 1 /* both used as string formats */))
1254     {
1255       *out = fmt_for_output (FMT_F, 40, 2);
1256       return NULL;
1257     }
1258
1259   uint8_t raw_type = u32 >> 16;
1260   uint8_t w = u32 >> 8;
1261   uint8_t d = u32;
1262
1263   msg_disable ();
1264   *out = (struct fmt_spec) { .type = FMT_F, .w = w, .d = d };
1265   bool ok = raw_type >= 40 || fmt_from_io (raw_type, &out->type);
1266   if (ok)
1267     {
1268       fmt_fix_output (out);
1269       ok = fmt_check_width_compat (out, 0);
1270     }
1271   msg_enable ();
1272
1273   if (!ok)
1274     {
1275       *out = fmt_for_output (FMT_F, 40, 2);
1276       return xasprintf ("bad format %#"PRIx32, u32);
1277     }
1278
1279   return NULL;
1280 }