spv: Add support for page breaks.
[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 <cairo.h>
23 #include <inttypes.h>
24 #include <libxml/HTMLparser.h>
25 #include <libxml/xmlreader.h>
26 #include <stdarg.h>
27 #include <stdlib.h>
28
29 #include "libpspp/assertion.h"
30 #include "libpspp/cast.h"
31 #include "libpspp/hash-functions.h"
32 #include "libpspp/message.h"
33 #include "libpspp/str.h"
34 #include "libpspp/zip-reader.h"
35 #include "output/output-item.h"
36 #include "output/page-setup.h"
37 #include "output/pivot-table.h"
38 #include "output/spv/detail-xml-parser.h"
39 #include "output/spv/light-binary-parser.h"
40 #include "output/spv/spv-css-parser.h"
41 #include "output/spv/spv-legacy-data.h"
42 #include "output/spv/spv-legacy-decoder.h"
43 #include "output/spv/spv-light-decoder.h"
44 #include "output/spv/spv-table-look.h"
45 #include "output/spv/structure-xml-parser.h"
46
47 #include "gl/c-ctype.h"
48 #include "gl/intprops.h"
49 #include "gl/minmax.h"
50 #include "gl/xalloc.h"
51 #include "gl/xvasprintf.h"
52 #include "gl/xsize.h"
53
54 #include "gettext.h"
55 #define _(msgid) gettext (msgid)
56 #define N_(msgid) (msgid)
57
58 struct spv_reader
59   {
60     struct zip_reader *zip;
61     struct spv_item *root;
62     struct page_setup *page_setup;
63   };
64
65 static xmlNode *
66 find_xml_child_element (xmlNode *parent, const char *child_name)
67 {
68   for (xmlNode *node = parent->children; node; node = node->next)
69     if (node->type == XML_ELEMENT_NODE
70         && node->name
71         && !strcmp (CHAR_CAST (char *, node->name), child_name))
72       return node;
73
74   return NULL;
75 }
76
77 static char *
78 get_xml_attr (const xmlNode *node, const char *name)
79 {
80   return CHAR_CAST (char *, xmlGetProp (node, CHAR_CAST (xmlChar *, name)));
81 }
82
83 static void
84 put_xml_attr (const char *name, const char *value, struct string *dst)
85 {
86   if (!value)
87     return;
88
89   ds_put_format (dst, " %s=\"", name);
90   for (const char *p = value; *p; p++)
91     {
92       switch (*p)
93         {
94         case '\n':
95           ds_put_cstr (dst, "&#10;");
96           break;
97         case '&':
98           ds_put_cstr (dst, "&amp;");
99           break;
100         case '<':
101           ds_put_cstr (dst, "&lt;");
102           break;
103         case '>':
104           ds_put_cstr (dst, "&gt;");
105           break;
106         case '"':
107           ds_put_cstr (dst, "&quot;");
108           break;
109         default:
110           ds_put_byte (dst, *p);
111           break;
112         }
113     }
114   ds_put_byte (dst, '"');
115 }
116
117 static void
118 extract_html_text (const xmlNode *node, int base_font_size, struct string *s)
119 {
120   if (node->type == XML_ELEMENT_NODE)
121     {
122       const char *name = CHAR_CAST (char *, node->name);
123       if (!strcmp (name, "br"))
124         ds_put_byte (s, '\n');
125       else if (strcmp (name, "style"))
126         {
127           const char *tag = NULL;
128           if (strchr ("biu", name[0]) && name[1] == '\0')
129             {
130               tag = name;
131               ds_put_format (s, "<%s>", tag);
132             }
133           else if (!strcmp (name, "font"))
134             {
135               tag = "span";
136               ds_put_format (s, "<%s", tag);
137
138               char *face = get_xml_attr (node, "face");
139               put_xml_attr ("face", face, s);
140               free (face);
141
142               char *color = get_xml_attr (node, "color");
143               if (color)
144                 {
145                   if (color[0] == '#')
146                     put_xml_attr ("color", color, s);
147                   else
148                     {
149                       uint8_t r, g, b;
150                       if (sscanf (color, "rgb (%"SCNu8", %"SCNu8", %"SCNu8")",
151                                   &r, &g, &b) == 3)
152                         {
153                           char color2[8];
154                           snprintf (color2, sizeof color2,
155                                     "#%02"PRIx8"%02"PRIx8"%02"PRIx8,
156                                     r, g, b);
157                           put_xml_attr ("color", color2, s);
158                         }
159                     }
160                 }
161               free (color);
162
163               char *size_s = get_xml_attr (node, "size");
164               int html_size = size_s ? atoi (size_s) : 0;
165               free (size_s);
166               if (html_size >= 1 && html_size <= 7)
167                 {
168                   static const double scale[7] = {
169                     .444, .556, .667, .778, 1.0, 1.33, 2.0
170                   };
171                   double size = base_font_size * scale[html_size - 1];
172
173                   char size2[INT_BUFSIZE_BOUND (int)];
174                   snprintf (size2, sizeof size2, "%.0f", size * 1024.);
175                   put_xml_attr ("size", size2, s);
176                 }
177
178               ds_put_cstr (s, ">");
179             }
180           for (const xmlNode *child = node->children; child;
181                child = child->next)
182             extract_html_text (child, base_font_size, s);
183           if (tag)
184             ds_put_format (s, "</%s>", tag);
185         }
186     }
187   else if (node->type == XML_TEXT_NODE)
188     {
189       /* U+00A0 NONBREAKING SPACE is really, really common in SPV text and it
190          makes it impossible to break syntax across lines.  Translate it into a
191          regular space.  (Note that U+00A0 is C2 A0 in UTF-8.)
192
193          Do the same for U+2007 FIGURE SPACE, which also crops out weirdly
194          sometimes. */
195       ds_extend (s, ds_length (s) + xmlStrlen (node->content));
196       for (const uint8_t *p = node->content; *p;)
197         {
198           int c;
199           if (p[0] == 0xc2 && p[1] == 0xa0)
200             {
201               c = ' ';
202               p += 2;
203             }
204           else if (p[0] == 0xe2 && p[1] == 0x80 && p[2] == 0x87)
205             {
206               c = ' ';
207               p += 3;
208             }
209           else
210             c = *p++;
211
212           if (c_isspace (c))
213             {
214               int last = ds_last (s);
215               if (last != EOF && !c_isspace (last))
216                 ds_put_byte (s, c);
217             }
218           else if (c == '<')
219             ds_put_cstr (s, "&lt;");
220           else if (c == '>')
221             ds_put_cstr (s, "&gt;");
222           else if (c == '&')
223             ds_put_cstr (s, "&amp;");
224           else
225             ds_put_byte (s, c);
226         }
227     }
228 }
229
230 static xmlDoc *
231 parse_embedded_html (const xmlNode *node)
232 {
233   /* Extract HTML from XML node. */
234   char *html_s = CHAR_CAST (char *, xmlNodeGetContent (node));
235   if (!html_s)
236     xalloc_die ();
237
238   xmlDoc *html_doc = htmlReadMemory (
239     html_s, strlen (html_s),
240     NULL, "UTF-8", (HTML_PARSE_RECOVER | HTML_PARSE_NOERROR
241                     | HTML_PARSE_NOWARNING | HTML_PARSE_NOBLANKS
242                     | HTML_PARSE_NONET));
243   free (html_s);
244
245   return html_doc;
246 }
247
248 /* Given NODE, which should contain HTML content, returns the text within that
249    content as an allocated string.  The caller must eventually free the
250    returned string (with xmlFree()). */
251 static char *
252 decode_embedded_html (const xmlNode *node, struct font_style *font_style)
253 {
254   struct string markup = DS_EMPTY_INITIALIZER;
255   *font_style = (struct font_style) FONT_STYLE_INITIALIZER;
256   font_style->size = 10;
257
258   xmlDoc *html_doc = parse_embedded_html (node);
259   if (html_doc)
260     {
261       xmlNode *root = xmlDocGetRootElement (html_doc);
262       xmlNode *head = root ? find_xml_child_element (root, "head") : NULL;
263       xmlNode *style = head ? find_xml_child_element (head, "style") : NULL;
264       if (style)
265         {
266           uint8_t *style_s = xmlNodeGetContent (style);
267           spv_parse_css_style (CHAR_CAST (char *, style_s), font_style);
268           xmlFree (style_s);
269         }
270
271       if (root)
272         extract_html_text (root, font_style->size, &markup);
273       xmlFreeDoc (html_doc);
274     }
275
276   font_style->markup = true;
277   return ds_steal_cstr (&markup);
278 }
279
280 static struct output_item *
281 decode_container_text (const struct spvsx_container_text *ct)
282 {
283   struct font_style *font_style = xmalloc (sizeof *font_style);
284   char *text = decode_embedded_html (ct->html->node_.raw, font_style);
285   struct pivot_value *value = xmalloc (sizeof *value);
286   *value = (struct pivot_value) {
287     .font_style = font_style,
288     .type = PIVOT_VALUE_TEXT,
289     .text = {
290       .local = text,
291       .c = text,
292       .id = text,
293       .user_provided = true,
294     },
295   };
296
297   struct output_item *item = text_item_create_value (TEXT_ITEM_LOG,
298                                                      value, NULL);
299   output_item_set_command_name (item, ct->command_name);
300   return item;
301 }
302
303 static void
304 decode_page_p (const xmlNode *in, struct page_paragraph *out)
305 {
306   char *style = get_xml_attr (in, "style");
307   out->halign = (style && strstr (style, "center") ? TABLE_HALIGN_CENTER
308                  : style && strstr (style, "right") ? TABLE_HALIGN_RIGHT
309                  : TABLE_HALIGN_LEFT);
310   free (style);
311
312   struct font_style font_style;
313   out->markup = decode_embedded_html (in, &font_style);
314   font_style_uninit (&font_style);
315 }
316
317 static void
318 decode_page_paragraph (const struct spvsx_page_paragraph *page_paragraph,
319                        struct page_heading *ph)
320 {
321   memset (ph, 0, sizeof *ph);
322
323   const struct spvsx_page_paragraph_text *page_paragraph_text
324     = page_paragraph->page_paragraph_text;
325   if (!page_paragraph_text)
326     return;
327
328   xmlDoc *html_doc = parse_embedded_html (page_paragraph_text->node_.raw);
329   if (!html_doc)
330     return;
331
332   xmlNode *root = xmlDocGetRootElement (html_doc);
333   xmlNode *body = find_xml_child_element (root, "body");
334   if (body)
335     for (const xmlNode *node = body->children; node; node = node->next)
336       if (node->type == XML_ELEMENT_NODE
337           && !strcmp (CHAR_CAST (const char *, node->name), "p"))
338         {
339           ph->paragraphs = xrealloc (ph->paragraphs,
340                                      (ph->n + 1) * sizeof *ph->paragraphs);
341           decode_page_p (node, &ph->paragraphs[ph->n++]);
342         }
343   xmlFreeDoc (html_doc);
344 }
345
346 char * WARN_UNUSED_RESULT
347 spv_read_light_table (struct zip_reader *zip, const char *bin_member,
348                       struct spvlb_table **tablep)
349 {
350   *tablep = NULL;
351
352   void *data;
353   size_t size;
354   char *error = zip_member_read_all (zip, bin_member, &data, &size);
355   if (error)
356     return error;
357
358   struct spvbin_input input;
359   spvbin_input_init (&input, data, size);
360
361   struct spvlb_table *table = NULL;
362   error = (!size
363            ? xasprintf ("light table member is empty")
364            : !spvlb_parse_table (&input, &table)
365            ? spvbin_input_to_error (&input, NULL)
366            : input.ofs != input.size
367            ? xasprintf ("expected end of file at offset %#zx", input.ofs)
368            : NULL);
369   free (data);
370   if (!error)
371     *tablep = table;
372   return error;
373 }
374
375 static char * WARN_UNUSED_RESULT
376 pivot_table_open_light (struct zip_reader *zip, const char *bin_member,
377                         struct pivot_table **tablep)
378 {
379   *tablep = NULL;
380
381   struct spvlb_table *raw_table;
382   char *error = spv_read_light_table (zip, bin_member, &raw_table);
383   if (!error)
384     error = decode_spvlb_table (raw_table, tablep);
385   spvlb_free_table (raw_table);
386
387   return error;
388 }
389
390 char * WARN_UNUSED_RESULT
391 spv_read_legacy_data (struct zip_reader *zip, const char *bin_member,
392                       struct spv_data *data)
393 {
394   void *raw;
395   size_t size;
396   char *error = zip_member_read_all (zip, bin_member, &raw, &size);
397   if (!error)
398     {
399       error = spv_legacy_data_decode (raw, size, data);
400       free (raw);
401     }
402
403   return error;
404 }
405
406 char * WARN_UNUSED_RESULT
407 spv_read_xml_member (struct zip_reader *zip, const char *xml_member,
408                      bool keep_blanks, const char *root_element_name,
409                      xmlDoc **docp)
410 {
411   *docp = NULL;
412
413   struct zip_member *zm;
414   char *error = zip_member_open (zip, xml_member, &zm);
415   if (error)
416     return error;
417
418   xmlParserCtxt *parser;
419   xmlKeepBlanksDefault (keep_blanks);
420   parser = xmlCreatePushParserCtxt(NULL, NULL, NULL, 0, NULL);
421   if (!parser)
422     {
423       zip_member_finish (zm);
424       return xasprintf (_("%s: Failed to create XML parser"), xml_member);
425     }
426
427   int retval;
428   char buf[4096];
429   while ((retval = zip_member_read (zm, buf, sizeof buf)) > 0)
430     xmlParseChunk (parser, buf, retval, false);
431   xmlParseChunk (parser, NULL, 0, true);
432
433   xmlDoc *doc = parser->myDoc;
434   bool well_formed = parser->wellFormed;
435   xmlFreeParserCtxt (parser);
436
437   if (retval < 0)
438     {
439       char *error = zip_member_steal_error (zm);
440       zip_member_finish (zm);
441       xmlFreeDoc (doc);
442       return error;
443     }
444   zip_member_finish (zm);
445
446   if (!well_formed)
447     {
448       xmlFreeDoc (doc);
449       return xasprintf(_("%s: document is not well-formed"), xml_member);
450     }
451
452   const xmlNode *root_node = xmlDocGetRootElement (doc);
453   assert (root_node->type == XML_ELEMENT_NODE);
454   if (strcmp (CHAR_CAST (char *, root_node->name), root_element_name))
455     {
456       xmlFreeDoc (doc);
457       return xasprintf(_("%s: root node is \"%s\" but \"%s\" was expected"),
458                        xml_member,
459                        CHAR_CAST (char *, root_node->name), root_element_name);
460     }
461
462   *docp = doc;
463   return NULL;
464 }
465
466 static char * WARN_UNUSED_RESULT
467 pivot_table_open_legacy (struct zip_reader *zip, const char *bin_member,
468                          const char *xml_member, const char *subtype,
469                          const struct pivot_table_look *look,
470                          struct pivot_table **tablep)
471 {
472   *tablep = NULL;
473
474   struct spv_data data = SPV_DATA_INITIALIZER;
475   char *error = spv_read_legacy_data (zip, bin_member, &data);
476   if (error)
477     goto exit;
478
479   xmlDoc *doc;
480   error = spv_read_xml_member (zip, xml_member, false,
481                                "visualization", &doc);
482   if (error)
483     goto exit_free_data;
484
485   struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
486   struct spvdx_visualization *v;
487   spvdx_parse_visualization (&ctx, xmlDocGetRootElement (doc), &v);
488   error = spvxml_context_finish (&ctx, &v->node_);
489   if (error)
490     goto exit_free_doc;
491
492   error = decode_spvdx_table (v, subtype, look, &data, tablep);
493
494   spvdx_free_visualization (v);
495 exit_free_doc:
496   if (doc)
497     xmlFreeDoc (doc);
498 exit_free_data:
499   spv_data_uninit (&data);
500 exit:
501   return error;
502 }
503
504 static struct output_item *
505 spv_read_table_item (struct zip_reader *zip,
506                      const struct spvsx_table *table)
507 {
508   const struct spvsx_table_structure *ts = table->table_structure;
509   const char *bin_member = ts->data_path->text;
510   const char *xml_member = ts->path ? ts->path->text : NULL;
511
512   struct pivot_table *pt = NULL;
513   char *error;
514   if (xml_member)
515     {
516       struct pivot_table_look *look;
517       error = (table->table_properties
518                ? spv_table_look_decode (table->table_properties, &look)
519                : xstrdup ("Legacy table lacks tableProperties"));
520       if (!error)
521         {
522           error = pivot_table_open_legacy (zip, bin_member, xml_member,
523                                            table->sub_type, look, &pt);
524           pivot_table_look_unref (look);
525         }
526     }
527   else
528     error = pivot_table_open_light (zip, bin_member, &pt);
529   if (error)
530     pt = pivot_table_create_for_text (
531       pivot_value_new_text (N_("Error")),
532       pivot_value_new_user_text_nocopy (error));
533
534   struct output_item *item = table_item_create (pt);
535   output_item_set_command_name (item, table->command_name);
536   output_item_add_spv_info (item);
537   item->spv_info->error = error != NULL;
538   item->spv_info->zip_reader = zip_reader_ref (zip);
539   item->spv_info->bin_member = xstrdup (bin_member);
540   item->spv_info->xml_member = xstrdup_if_nonnull (xml_member);
541   return item;
542 }
543
544 static cairo_status_t
545 read_from_zip_member (void *zm_, unsigned char *data, unsigned int length)
546 {
547   struct zip_member *zm = zm_;
548   if (!zm)
549     return CAIRO_STATUS_READ_ERROR;
550
551   while (length > 0)
552     {
553       int n = zip_member_read (zm, data, length);
554       if (n <= 0)
555         return CAIRO_STATUS_READ_ERROR;
556
557       data += n;
558       length -= n;
559     }
560
561   return CAIRO_STATUS_SUCCESS;
562 }
563
564 static char * WARN_UNUSED_RESULT
565 spv_read_image (struct zip_reader *zip, const char *png_member,
566                 const char *command_name, struct output_item **itemp)
567 {
568   struct zip_member *zm;
569   char *error = zip_member_open (zip, png_member, &zm);
570   if (error)
571     return error;
572
573   cairo_surface_t *surface = cairo_image_surface_create_from_png_stream (
574     read_from_zip_member, zm);
575   if (zm)
576     zip_member_finish (zm);
577
578   if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)
579     return xstrdup ("reading image failed");
580
581   struct output_item *item = image_item_create (surface);
582   output_item_set_command_name (item, command_name);
583   output_item_add_spv_info (item);
584   item->spv_info->zip_reader = zip_reader_ref (zip);
585   item->spv_info->png_member = xstrdup (png_member);
586   *itemp = item;
587   return NULL;
588 }
589
590 static struct output_item *
591 error_item_create (char *s)
592 {
593   struct output_item *item = text_item_create_nocopy (TEXT_ITEM_LOG, s,
594                                                       xstrdup ("Error"));
595   output_item_add_spv_info (item);
596   item->spv_info->error = true;
597   return item;
598 }
599
600 static struct output_item *
601 spv_decode_container (struct zip_reader *zip,
602                       const struct spvsx_container *c)
603 {
604   assert (c->n_seq == 1);
605   struct spvxml_node *content = c->seq[0];
606
607   struct output_item *item = NULL;
608   char *error;
609   if (spvsx_is_container_text (content))
610     {
611       item = decode_container_text (spvsx_cast_container_text (content));
612       error = NULL;
613     }
614   else if (spvsx_is_table (content))
615     {
616       item = spv_read_table_item (zip, spvsx_cast_table (content));
617       error = NULL;
618     }
619   else if (spvsx_is_object (content))
620     {
621       struct spvsx_object *object = spvsx_cast_object (content);
622       error = spv_read_image (zip, object->uri, object->command_name, &item);
623     }
624   else if (spvsx_is_image (content))
625     {
626       struct spvsx_image *image = spvsx_cast_image (content);
627       error = spv_read_image (zip, image->data_path->text, image->command_name,
628                               &item);
629     }
630   else if (spvsx_is_graph (content))
631     error = xstrdup ("graphs not yet implemented");
632   else if (spvsx_is_model (content))
633     error = xstrdup ("models not yet implemented");
634   else if (spvsx_is_tree (content))
635     error = xstrdup ("trees not yet implemented");
636   else
637     NOT_REACHED ();
638
639   if (error)
640     item = error_item_create (error);
641   else
642     output_item_set_label (item, c->label->text);
643   item->show = c->visibility == SPVSX_VISIBILITY_VISIBLE;
644
645   return item;
646 }
647
648 static void
649 set_structure_member (struct output_item *item, struct zip_reader *zip,
650                       const char *structure_member)
651 {
652   if (structure_member)
653     {
654       output_item_add_spv_info (item);
655       if (!item->spv_info->zip_reader)
656         item->spv_info->zip_reader = zip_reader_ref (zip);
657       if (!item->spv_info->structure_member)
658         item->spv_info->structure_member = xstrdup (structure_member);
659     }
660 }
661
662 static void
663 spv_decode_children (struct zip_reader *zip, const char *structure_member,
664                      struct spvxml_node **seq, size_t n_seq,
665                      struct output_item *parent)
666 {
667   for (size_t i = 0; i < n_seq; i++)
668     {
669       const struct spvxml_node *node = seq[i];
670
671       struct output_item *child;
672       if (spvsx_is_container (node))
673         {
674           const struct spvsx_container *container
675             = spvsx_cast_container (node);
676
677           if (container->page_break_before_present)
678             group_item_add_child (parent, page_break_item_create ());
679
680           child = spv_decode_container (zip, container);
681         }
682       else if (spvsx_is_heading (node))
683         {
684           const struct spvsx_heading *subheading = spvsx_cast_heading (node);
685
686           child = group_item_create (subheading->command_name,
687                                      subheading->label->text);
688           child->show = !subheading->heading_visibility_present;
689
690           /* Pass NULL for 'structure_member' so that only top-level items get
691              tagged that way.  Lower-level items are always in the same
692              structure member as their parent anyway. */
693            spv_decode_children (zip, NULL, subheading->seq,
694                                 subheading->n_seq, child);
695         }
696       else
697         NOT_REACHED ();
698
699       set_structure_member (child, zip, structure_member);
700       group_item_add_child (parent, child);
701     }
702 }
703
704 static struct page_setup *
705 decode_page_setup (const struct spvsx_page_setup *in, const char *file_name)
706 {
707   struct page_setup *out = xmalloc (sizeof *out);
708   *out = (struct page_setup) PAGE_SETUP_INITIALIZER;
709
710   out->initial_page_number = in->initial_page_number;
711
712   if (in->paper_width != DBL_MAX)
713     out->paper[TABLE_HORZ] = in->paper_width;
714   if (in->paper_height != DBL_MAX)
715     out->paper[TABLE_VERT] = in->paper_height;
716
717   if (in->margin_left != DBL_MAX)
718     out->margins[TABLE_HORZ][0] = in->margin_left;
719   if (in->margin_right != DBL_MAX)
720     out->margins[TABLE_HORZ][1] = in->margin_right;
721   if (in->margin_top != DBL_MAX)
722     out->margins[TABLE_VERT][0] = in->margin_top;
723   if (in->margin_bottom != DBL_MAX)
724     out->margins[TABLE_VERT][1] = in->margin_bottom;
725
726   if (in->space_after != DBL_MAX)
727     out->object_spacing = in->space_after;
728
729   if (in->chart_size)
730     out->chart_size = (in->chart_size == SPVSX_CHART_SIZE_FULL_HEIGHT
731                        ? PAGE_CHART_FULL_HEIGHT
732                        : in->chart_size == SPVSX_CHART_SIZE_HALF_HEIGHT
733                        ? PAGE_CHART_HALF_HEIGHT
734                        : in->chart_size == SPVSX_CHART_SIZE_QUARTER_HEIGHT
735                        ? PAGE_CHART_QUARTER_HEIGHT
736                        : PAGE_CHART_AS_IS);
737
738   decode_page_paragraph (in->page_header->page_paragraph, &out->headings[0]);
739   decode_page_paragraph (in->page_footer->page_paragraph, &out->headings[1]);
740
741   out->file_name = xstrdup (file_name);
742
743   return out;
744 }
745
746 static void
747 spv_add_error_heading (struct output_item *root_item,
748                        struct zip_reader *zip, const char *structure_member,
749                        char *error)
750 {
751   struct output_item *item = error_item_create (
752     xasprintf ("%s: %s", structure_member, error));
753   free (error);
754   set_structure_member (item, zip, structure_member);
755   group_item_add_child (root_item, item);
756 }
757
758 static void
759 spv_heading_read (struct zip_reader *zip, struct output_item *root_item,
760                   struct page_setup **psp, const char *file_name,
761                   const char *structure_member)
762 {
763   xmlDoc *doc;
764   char *error = spv_read_xml_member (zip, structure_member, true,
765                                      "heading", &doc);
766   if (error)
767     {
768       spv_add_error_heading (root_item, zip, structure_member, error);
769       return;
770     }
771
772   struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
773   struct spvsx_root_heading *root;
774   spvsx_parse_root_heading (&ctx, xmlDocGetRootElement (doc), &root);
775   error = spvxml_context_finish (&ctx, &root->node_);
776   if (error)
777     {
778       xmlFreeDoc (doc);
779       spv_add_error_heading (root_item, zip, structure_member, error);
780       return;
781     }
782
783   if (root->page_setup && psp && !*psp)
784     *psp = decode_page_setup (root->page_setup, file_name);
785
786   for (size_t i = 0; i < root->n_seq; i++)
787     spv_decode_children (zip, structure_member, root->seq, root->n_seq,
788                          root_item);
789
790   spvsx_free_root_heading (root);
791   xmlFreeDoc (doc);
792 }
793
794 static int
795 spv_detect__ (struct zip_reader *zip, char **errorp)
796 {
797   *errorp = NULL;
798
799   const char *member = "META-INF/MANIFEST.MF";
800   if (!zip_reader_contains_member (zip, member))
801     return 0;
802
803   void *data;
804   size_t size;
805   *errorp = zip_member_read_all (zip, "META-INF/MANIFEST.MF",
806                                  &data, &size);
807   if (*errorp)
808     return -1;
809
810   const char *magic = "allowPivoting=true";
811   bool is_spv = size == strlen (magic) && !memcmp (magic, data, size);
812   free (data);
813
814   return is_spv;
815 }
816
817 /* Returns NULL if FILENAME is an SPV file, otherwise an error string that the
818    caller must eventually free(). */
819 char * WARN_UNUSED_RESULT
820 spv_detect (const char *filename)
821 {
822   struct zip_reader *zip;
823   char *error = zip_reader_create (filename, &zip);
824   if (error)
825     return error;
826
827   if (spv_detect__ (zip, &error) <= 0 && !error)
828     error = xasprintf("%s: not an SPV file", filename);
829   zip_reader_unref (zip);
830   return error;
831 }
832
833 char * WARN_UNUSED_RESULT
834 spv_read (const char *filename, struct output_item **outp,
835           struct page_setup **psp)
836 {
837   *outp = NULL;
838   if (psp)
839     *psp = NULL;
840
841   struct spv_reader *spv = xzalloc (sizeof *spv);
842   struct zip_reader *zip;
843   char *error = zip_reader_create (filename, &zip);
844   if (error)
845     return error;
846
847   int detect = spv_detect__ (zip, &error);
848   if (detect <= 0)
849     {
850       zip_reader_unref (zip);
851       return error ? error : xasprintf ("%s: not an SPV file", filename);
852     }
853
854   *outp = root_item_create ();
855   for (size_t i = 0; ; i++)
856     {
857       const char *structure_member = zip_reader_get_member_name (zip, i);
858       if (!structure_member)
859         break;
860
861       struct substring structure_member_ss = ss_cstr (structure_member);
862       if (ss_starts_with (structure_member_ss, ss_cstr ("outputViewer"))
863           && ss_ends_with (structure_member_ss, ss_cstr (".xml")))
864         spv_heading_read (zip, *outp, psp, filename, structure_member);
865     }
866
867   zip_reader_unref (zip);
868   return NULL;
869 }
870
871 char * WARN_UNUSED_RESULT
872 spv_decode_fmt_spec (uint32_t u32, struct fmt_spec *out)
873 {
874   if (!u32
875       || (u32 == 0x10000 || u32 == 1 /* both used as string formats */))
876     {
877       *out = fmt_for_output (FMT_F, 40, 2);
878       return NULL;
879     }
880
881   uint8_t raw_type = u32 >> 16;
882   uint8_t w = u32 >> 8;
883   uint8_t d = u32;
884
885   msg_disable ();
886   *out = (struct fmt_spec) { .type = FMT_F, .w = w, .d = d };
887   bool ok = raw_type >= 40 || fmt_from_io (raw_type, &out->type);
888   if (ok)
889     {
890       fmt_fix_output (out);
891       ok = fmt_check_width_compat (out, 0);
892     }
893   msg_enable ();
894
895   if (!ok)
896     {
897       *out = fmt_for_output (FMT_F, 40, 2);
898       return xasprintf ("bad format %#"PRIx32, u32);
899     }
900
901   return NULL;
902 }