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