output: Make groups contain their subitems, and get rid of spv_item.
[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 #if 0
467 static const char *
468 identify_item (const struct output_item *item)
469 {
470   return (item->label ? item->label
471           : item->command_id ? item->command_id
472           : spv_item_type_to_string (item->type));
473 }
474
475 void
476 spv_item_format_path (const struct spv_item *item, struct string *s)
477 {
478   enum { MAX_STACK = 32 };
479   const struct spv_item *stack[MAX_STACK];
480   size_t n = 0;
481
482   while (item != NULL && item->parent && n < MAX_STACK)
483     {
484       stack[n++] = item;
485       item = item->parent;
486     }
487
488   while (n > 0)
489     {
490       item = stack[--n];
491       ds_put_byte (s, '/');
492
493       const char *name = identify_item (item);
494       ds_put_cstr (s, name);
495
496       if (item->parent)
497         {
498           size_t total = 1;
499           size_t index = 1;
500           for (size_t i = 0; i < item->parent->n_children; i++)
501             {
502               const struct spv_item *sibling = item->parent->children[i];
503               if (sibling == item)
504                 index = total;
505               else if (!strcmp (name, identify_item (sibling)))
506                 total++;
507             }
508           if (total > 1)
509             ds_put_format (s, "[%zu]", index);
510         }
511     }
512 }
513 #endif
514
515 static char * WARN_UNUSED_RESULT
516 pivot_table_open_legacy (struct zip_reader *zip, const char *bin_member,
517                          const char *xml_member, const char *subtype,
518                          const struct pivot_table_look *look,
519                          struct pivot_table **tablep)
520 {
521   *tablep = NULL;
522
523   struct spv_data data = SPV_DATA_INITIALIZER;
524   char *error = spv_read_legacy_data (zip, bin_member, &data);
525   if (error)
526     goto exit;
527
528   xmlDoc *doc;
529   error = spv_read_xml_member (zip, xml_member, false,
530                                "visualization", &doc);
531   if (error)
532     goto exit_free_data;
533
534   struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
535   struct spvdx_visualization *v;
536   spvdx_parse_visualization (&ctx, xmlDocGetRootElement (doc), &v);
537   error = spvxml_context_finish (&ctx, &v->node_);
538   if (error)
539     goto exit_free_doc;
540
541   error = decode_spvdx_table (v, subtype, look, &data, tablep);
542
543   spvdx_free_visualization (v);
544 exit_free_doc:
545   if (doc)
546     xmlFreeDoc (doc);
547 exit_free_data:
548   spv_data_uninit (&data);
549 exit:
550   return error;
551 }
552
553 static struct output_item *
554 spv_read_table_item (struct zip_reader *zip,
555                      const struct spvsx_table *table)
556 {
557   const struct spvsx_table_structure *ts = table->table_structure;
558   const char *bin_member = ts->data_path->text;
559   const char *xml_member = ts->path ? ts->path->text : NULL;
560
561   struct pivot_table *pt = NULL;
562   char *error;
563   if (xml_member)
564     {
565       struct pivot_table_look *look;
566       error = (table->table_properties
567                ? spv_table_look_decode (table->table_properties, &look)
568                : xstrdup ("Legacy table lacks tableProperties"));
569       if (!error)
570         {
571           error = pivot_table_open_legacy (zip, bin_member, xml_member,
572                                            table->sub_type, look, &pt);
573           pivot_table_look_unref (look);
574         }
575     }
576   else
577     error = pivot_table_open_light (zip, bin_member, &pt);
578   if (error)
579     pt = pivot_table_create_for_text (
580       pivot_value_new_text (N_("Error")),
581       pivot_value_new_user_text_nocopy (error));
582
583   struct output_item *item = table_item_create (pt);
584   output_item_set_command_name (item, table->command_name);
585   output_item_add_spv_info (item);
586   item->spv_info->error = error != NULL;
587   item->spv_info->zip_reader = zip_reader_ref (zip);
588   item->spv_info->bin_member = xstrdup (bin_member);
589   item->spv_info->xml_member = xstrdup_if_nonnull (xml_member);
590   return item;
591 }
592
593 static cairo_status_t
594 read_from_zip_member (void *zm_, unsigned char *data, unsigned int length)
595 {
596   struct zip_member *zm = zm_;
597   if (!zm)
598     return CAIRO_STATUS_READ_ERROR;
599
600   while (length > 0)
601     {
602       int n = zip_member_read (zm, data, length);
603       if (n <= 0)
604         return CAIRO_STATUS_READ_ERROR;
605
606       data += n;
607       length -= n;
608     }
609
610   return CAIRO_STATUS_SUCCESS;
611 }
612
613 static char * WARN_UNUSED_RESULT
614 spv_read_image (struct zip_reader *zip, const char *png_member,
615                 const char *command_name, struct output_item **itemp)
616 {
617   struct zip_member *zm;
618   char *error = zip_member_open (zip, png_member, &zm);
619   if (error)
620     return error;
621
622   cairo_surface_t *surface = cairo_image_surface_create_from_png_stream (
623     read_from_zip_member, zm);
624   if (zm)
625     zip_member_finish (zm);
626
627   if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)
628     return xstrdup ("reading image failed");
629
630   struct output_item *item = image_item_create (surface);
631   output_item_set_command_name (item, command_name);
632   output_item_add_spv_info (item);
633   item->spv_info->zip_reader = zip_reader_ref (zip);
634   item->spv_info->png_member = xstrdup (png_member);
635   *itemp = item;
636   return NULL;
637 }
638
639 static struct output_item *
640 error_item_create (char *s)
641 {
642   struct output_item *item = text_item_create_nocopy (TEXT_ITEM_LOG, s,
643                                                       xstrdup ("Error"));
644   output_item_add_spv_info (item);
645   item->spv_info->error = true;
646   return item;
647 }
648
649 static struct output_item *
650 spv_decode_container (struct zip_reader *zip,
651                       const struct spvsx_container *c)
652 {
653   assert (c->n_seq == 1);
654   struct spvxml_node *content = c->seq[0];
655
656   struct output_item *item = NULL;
657   char *error;
658   if (spvsx_is_container_text (content))
659     {
660       item = decode_container_text (spvsx_cast_container_text (content));
661       error = NULL;
662     }
663   else if (spvsx_is_table (content))
664     {
665       item = spv_read_table_item (zip, spvsx_cast_table (content));
666       error = NULL;
667     }
668   else if (spvsx_is_object (content))
669     {
670       struct spvsx_object *object = spvsx_cast_object (content);
671       error = spv_read_image (zip, object->uri, object->command_name, &item);
672     }
673   else if (spvsx_is_image (content))
674     {
675       struct spvsx_image *image = spvsx_cast_image (content);
676       error = spv_read_image (zip, image->data_path->text, image->command_name,
677                               &item);
678     }
679   else if (spvsx_is_graph (content))
680     error = xstrdup ("graphs not yet implemented");
681   else if (spvsx_is_model (content))
682     error = xstrdup ("models not yet implemented");
683   else if (spvsx_is_tree (content))
684     error = xstrdup ("trees not yet implemented");
685   else
686     NOT_REACHED ();
687
688   if (error)
689     item = error_item_create (error);
690   else
691     output_item_set_label (item, c->label->text);
692   item->show = c->visibility == SPVSX_VISIBILITY_VISIBLE;
693
694   return item;
695 }
696
697 static void
698 set_structure_member (struct output_item *item, struct zip_reader *zip,
699                       const char *structure_member)
700 {
701   if (structure_member)
702     {
703       output_item_add_spv_info (item);
704       if (!item->spv_info->zip_reader)
705         item->spv_info->zip_reader = zip_reader_ref (zip);
706       if (!item->spv_info->structure_member)
707         item->spv_info->structure_member = xstrdup (structure_member);
708     }
709 }
710
711 static void
712 spv_decode_children (struct zip_reader *zip, const char *structure_member,
713                      struct spvxml_node **seq, size_t n_seq,
714                      struct output_item *parent)
715 {
716   for (size_t i = 0; i < n_seq; i++)
717     {
718       const struct spvxml_node *node = seq[i];
719
720       struct output_item *child;
721       if (spvsx_is_container (node))
722         {
723           const struct spvsx_container *container
724             = spvsx_cast_container (node);
725           child = spv_decode_container (zip, container);
726         }
727       else if (spvsx_is_heading (node))
728         {
729           const struct spvsx_heading *subheading = spvsx_cast_heading (node);
730
731           child = group_item_create (subheading->command_name,
732                                      subheading->label->text);
733           child->show = !subheading->heading_visibility_present;
734
735           /* Pass NULL for 'structure_member' so that only top-level items get
736              tagged that way.  Lower-level items are always in the same
737              structure member as their parent anyway. */
738            spv_decode_children (zip, NULL, subheading->seq,
739                                 subheading->n_seq, child);
740         }
741       else
742         NOT_REACHED ();
743
744       set_structure_member (child, zip, structure_member);
745       group_item_add_child (parent, child);
746     }
747 }
748
749 static struct page_setup *
750 decode_page_setup (const struct spvsx_page_setup *in, const char *file_name)
751 {
752   struct page_setup *out = xmalloc (sizeof *out);
753   *out = (struct page_setup) PAGE_SETUP_INITIALIZER;
754
755   out->initial_page_number = in->initial_page_number;
756
757   if (in->paper_width != DBL_MAX)
758     out->paper[TABLE_HORZ] = in->paper_width;
759   if (in->paper_height != DBL_MAX)
760     out->paper[TABLE_VERT] = in->paper_height;
761
762   if (in->margin_left != DBL_MAX)
763     out->margins[TABLE_HORZ][0] = in->margin_left;
764   if (in->margin_right != DBL_MAX)
765     out->margins[TABLE_HORZ][1] = in->margin_right;
766   if (in->margin_top != DBL_MAX)
767     out->margins[TABLE_VERT][0] = in->margin_top;
768   if (in->margin_bottom != DBL_MAX)
769     out->margins[TABLE_VERT][1] = in->margin_bottom;
770
771   if (in->space_after != DBL_MAX)
772     out->object_spacing = in->space_after;
773
774   if (in->chart_size)
775     out->chart_size = (in->chart_size == SPVSX_CHART_SIZE_FULL_HEIGHT
776                        ? PAGE_CHART_FULL_HEIGHT
777                        : in->chart_size == SPVSX_CHART_SIZE_HALF_HEIGHT
778                        ? PAGE_CHART_HALF_HEIGHT
779                        : in->chart_size == SPVSX_CHART_SIZE_QUARTER_HEIGHT
780                        ? PAGE_CHART_QUARTER_HEIGHT
781                        : PAGE_CHART_AS_IS);
782
783   decode_page_paragraph (in->page_header->page_paragraph, &out->headings[0]);
784   decode_page_paragraph (in->page_footer->page_paragraph, &out->headings[1]);
785
786   out->file_name = xstrdup (file_name);
787
788   return out;
789 }
790
791 static void
792 spv_add_error_heading (struct output_item *root_item,
793                        struct zip_reader *zip, const char *structure_member,
794                        char *error)
795 {
796   struct output_item *item = error_item_create (
797     xasprintf ("%s: %s", structure_member, error));
798   free (error);
799   set_structure_member (item, zip, structure_member);
800   group_item_add_child (root_item, item);
801 }
802
803 static void
804 spv_heading_read (struct zip_reader *zip, struct output_item *root_item,
805                   struct page_setup **psp, const char *file_name,
806                   const char *structure_member)
807 {
808   xmlDoc *doc;
809   char *error = spv_read_xml_member (zip, structure_member, true,
810                                      "heading", &doc);
811   if (error)
812     {
813       spv_add_error_heading (root_item, zip, structure_member, error);
814       return;
815     }
816
817   struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
818   struct spvsx_root_heading *root;
819   spvsx_parse_root_heading (&ctx, xmlDocGetRootElement (doc), &root);
820   error = spvxml_context_finish (&ctx, &root->node_);
821   if (error)
822     {
823       xmlFreeDoc (doc);
824       spv_add_error_heading (root_item, zip, structure_member, error);
825       return;
826     }
827
828   if (root->page_setup && psp && !*psp)
829     *psp = decode_page_setup (root->page_setup, file_name);
830
831   for (size_t i = 0; i < root->n_seq; i++)
832     spv_decode_children (zip, structure_member, root->seq, root->n_seq,
833                          root_item);
834
835   spvsx_free_root_heading (root);
836   xmlFreeDoc (doc);
837 }
838
839 static int
840 spv_detect__ (struct zip_reader *zip, char **errorp)
841 {
842   *errorp = NULL;
843
844   const char *member = "META-INF/MANIFEST.MF";
845   if (!zip_reader_contains_member (zip, member))
846     return 0;
847
848   void *data;
849   size_t size;
850   *errorp = zip_member_read_all (zip, "META-INF/MANIFEST.MF",
851                                  &data, &size);
852   if (*errorp)
853     return -1;
854
855   const char *magic = "allowPivoting=true";
856   bool is_spv = size == strlen (magic) && !memcmp (magic, data, size);
857   free (data);
858
859   return is_spv;
860 }
861
862 /* Returns NULL if FILENAME is an SPV file, otherwise an error string that the
863    caller must eventually free(). */
864 char * WARN_UNUSED_RESULT
865 spv_detect (const char *filename)
866 {
867   struct zip_reader *zip;
868   char *error = zip_reader_create (filename, &zip);
869   if (error)
870     return error;
871
872   if (spv_detect__ (zip, &error) <= 0 && !error)
873     error = xasprintf("%s: not an SPV file", filename);
874   zip_reader_unref (zip);
875   return error;
876 }
877
878 char * WARN_UNUSED_RESULT
879 spv_read (const char *filename, struct output_item **outp,
880           struct page_setup **psp)
881 {
882   *outp = NULL;
883   if (psp)
884     *psp = NULL;
885
886   struct spv_reader *spv = xzalloc (sizeof *spv);
887   struct zip_reader *zip;
888   char *error = zip_reader_create (filename, &zip);
889   if (error)
890     return error;
891
892   int detect = spv_detect__ (zip, &error);
893   if (detect <= 0)
894     {
895       zip_reader_unref (zip);
896       return error ? error : xasprintf ("%s: not an SPV file", filename);
897     }
898
899   *outp = root_item_create ();
900   for (size_t i = 0; ; i++)
901     {
902       const char *structure_member = zip_reader_get_member_name (zip, i);
903       if (!structure_member)
904         break;
905
906       struct substring structure_member_ss = ss_cstr (structure_member);
907       if (ss_starts_with (structure_member_ss, ss_cstr ("outputViewer"))
908           && ss_ends_with (structure_member_ss, ss_cstr (".xml")))
909         spv_heading_read (zip, *outp, psp, filename, structure_member);
910     }
911
912   zip_reader_unref (zip);
913   return NULL;
914 }
915
916 char * WARN_UNUSED_RESULT
917 spv_decode_fmt_spec (uint32_t u32, struct fmt_spec *out)
918 {
919   if (!u32
920       || (u32 == 0x10000 || u32 == 1 /* both used as string formats */))
921     {
922       *out = fmt_for_output (FMT_F, 40, 2);
923       return NULL;
924     }
925
926   uint8_t raw_type = u32 >> 16;
927   uint8_t w = u32 >> 8;
928   uint8_t d = u32;
929
930   msg_disable ();
931   *out = (struct fmt_spec) { .type = FMT_F, .w = w, .d = d };
932   bool ok = raw_type >= 40 || fmt_from_io (raw_type, &out->type);
933   if (ok)
934     {
935       fmt_fix_output (out);
936       ok = fmt_check_width_compat (out, 0);
937     }
938   msg_enable ();
939
940   if (!ok)
941     {
942       *out = fmt_for_output (FMT_F, 40, 2);
943       return xasprintf ("bad format %#"PRIx32, u32);
944     }
945
946   return NULL;
947 }