1 /* PSPP - a program for statistical analysis.
2 Copyright (C) 2017, 2018 Free Software Foundation, Inc.
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.
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.
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/>. */
19 #include "output/spv/spv.h"
23 #include <libxml/HTMLparser.h>
24 #include <libxml/xmlreader.h>
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/structure-xml-parser.h"
43 #include "output/spv/vizml-parser.h"
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"
53 #define _(msgid) gettext (msgid)
54 #define N_(msgid) (msgid)
58 struct string zip_errs;
59 struct zip_reader *zip;
60 struct spv_item *root;
61 struct page_setup *page_setup;
64 const struct page_setup *
65 spv_get_page_setup (const struct spv_reader *spv)
67 return spv->page_setup;
71 spv_item_type_to_string (enum spv_item_type type)
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_OBJECT: return "object";
81 default: return "**error**";
86 spv_item_class_to_string (enum spv_item_class class)
90 #define SPV_CLASS(ENUM, NAME) case SPV_CLASS_##ENUM: return NAME;
98 spv_item_class_from_string (const char *name)
100 #define SPV_CLASS(ENUM, NAME) \
101 if (!strcmp (name, NAME)) return SPV_CLASS_##ENUM;
105 return SPV_N_CLASSES;
109 spv_item_get_type (const struct spv_item *item)
115 spv_item_get_class (const struct spv_item *item)
117 const char *label = spv_item_get_label (item);
123 case SPV_ITEM_HEADING:
124 return SPV_CLASS_HEADINGS;
127 return (!strcmp (label, "Title") ? SPV_CLASS_OUTLINEHEADERS
128 : !strcmp (label, "Log") ? SPV_CLASS_LOGS
129 : !strcmp (label, "Page Title") ? SPV_CLASS_PAGETITLE
133 return (!strcmp (label, "Warnings") ? SPV_CLASS_WARNINGS
134 : !strcmp (label, "Notes") ? SPV_CLASS_NOTES
138 return SPV_CLASS_CHARTS;
141 return SPV_CLASS_MODELS;
143 case SPV_ITEM_OBJECT:
144 return SPV_CLASS_OTHER;
147 return SPV_CLASS_TREES;
150 return SPV_CLASS_UNKNOWN;
155 spv_item_get_label (const struct spv_item *item)
161 spv_item_is_heading (const struct spv_item *item)
163 return item->type == SPV_ITEM_HEADING;
167 spv_item_get_n_children (const struct spv_item *item)
169 return item->n_children;
173 spv_item_get_child (const struct spv_item *item, size_t idx)
175 assert (idx < item->n_children);
176 return item->children[idx];
180 spv_item_is_table (const struct spv_item *item)
182 return item->type == SPV_ITEM_TABLE;
186 spv_item_is_graph (const struct spv_item *item)
188 return item->type == SPV_ITEM_GRAPH;
192 spv_item_is_text (const struct spv_item *item)
194 return item->type == SPV_ITEM_TEXT;
197 const struct pivot_value *
198 spv_item_get_text (const struct spv_item *item)
200 assert (spv_item_is_text (item));
205 spv_item_next (const struct spv_item *item)
207 if (item->n_children)
208 return item->children[0];
212 size_t idx = item->parent_idx + 1;
214 if (idx < item->n_children)
215 return item->children[idx];
221 const struct spv_item *
222 spv_item_get_parent (const struct spv_item *item)
228 spv_item_get_level (const struct spv_item *item)
231 for (; item->parent; item = item->parent)
237 spv_item_get_command_id (const struct spv_item *item)
239 return item->command_id;
243 spv_item_get_subtype (const struct spv_item *item)
245 return item->subtype;
249 spv_item_is_visible (const struct spv_item *item)
251 return item->visible;
255 spv_item_destroy (struct spv_item *item)
259 free (item->structure_member);
262 free (item->command_id);
264 for (size_t i = 0; i < item->n_children; i++)
265 spv_item_destroy (item->children[i]);
266 free (item->children);
268 pivot_table_unref (item->table);
269 spv_legacy_properties_destroy (item->legacy_properties);
270 free (item->bin_member);
271 free (item->xml_member);
272 free (item->subtype);
274 pivot_value_destroy (item->text);
276 free (item->object_type);
284 spv_heading_add_child (struct spv_item *parent, struct spv_item *child)
286 assert (parent->type == SPV_ITEM_HEADING);
287 assert (!child->parent);
289 child->parent = parent;
290 child->parent_idx = parent->n_children;
292 if (parent->n_children >= parent->allocated_children)
293 parent->children = x2nrealloc (parent->children,
294 &parent->allocated_children,
295 sizeof *parent->children);
296 parent->children[parent->n_children++] = child;
300 find_xml_child_element (xmlNode *parent, const char *child_name)
302 for (xmlNode *node = parent->children; node; node = node->next)
303 if (node->type == XML_ELEMENT_NODE
305 && !strcmp (CHAR_CAST (char *, node->name), child_name))
312 get_xml_attr (const xmlNode *node, const char *name)
314 return CHAR_CAST (char *, xmlGetProp (node, CHAR_CAST (xmlChar *, name)));
318 put_xml_attr (const char *name, const char *value, struct string *dst)
323 ds_put_format (dst, " %s=\"", name);
324 for (const char *p = value; *p; p++)
329 ds_put_cstr (dst, " ");
332 ds_put_cstr (dst, "&");
335 ds_put_cstr (dst, "<");
338 ds_put_cstr (dst, ">");
341 ds_put_cstr (dst, """);
344 ds_put_byte (dst, *p);
348 ds_put_byte (dst, '"');
352 extract_html_text (const xmlNode *node, int base_font_size, struct string *s)
354 if (node->type == XML_ELEMENT_NODE)
356 const char *name = CHAR_CAST (char *, node->name);
357 if (!strcmp (name, "br"))
358 ds_put_byte (s, '\n');
359 else if (strcmp (name, "style"))
361 const char *tag = NULL;
362 if (strchr ("biu", name[0]) && name[1] == '\0')
365 ds_put_format (s, "<%s>", tag);
367 else if (!strcmp (name, "font"))
370 ds_put_format (s, "<%s", tag);
372 char *face = get_xml_attr (node, "face");
373 put_xml_attr ("face", face, s);
376 char *color = get_xml_attr (node, "color");
380 put_xml_attr ("color", color, s);
384 if (sscanf (color, "rgb (%"SCNu8", %"SCNu8", %"SCNu8" )",
388 snprintf (color2, sizeof color2,
389 "#%02"PRIx8"%02"PRIx8"%02"PRIx8,
391 put_xml_attr ("color", color2, s);
397 char *size_s = get_xml_attr (node, "size");
398 int html_size = size_s ? atoi (size_s) : 0;
400 if (html_size >= 1 && html_size <= 7)
402 static const double scale[7] = {
403 .444, .556, .667, .778, 1.0, 1.33, 2.0
405 double size = base_font_size * scale[html_size - 1];
407 char size2[INT_BUFSIZE_BOUND (int)];
408 snprintf (size2, sizeof size2, "%.0f", size * 1024.);
409 put_xml_attr ("size", size2, s);
412 ds_put_cstr (s, ">");
414 for (const xmlNode *child = node->children; child;
416 extract_html_text (child, base_font_size, s);
418 ds_put_format (s, "</%s>", tag);
421 else if (node->type == XML_TEXT_NODE)
423 /* U+00A0 NONBREAKING SPACE is really, really common in SPV text and it
424 makes it impossible to break syntax across lines. Translate it into a
425 regular space. (Note that U+00A0 is C2 A0 in UTF-8.)
427 Do the same for U+2007 FIGURE SPACE, which also crops out weirdly
429 ds_extend (s, ds_length (s) + xmlStrlen (node->content));
430 for (const uint8_t *p = node->content; *p; )
433 if (p[0] == 0xc2 && p[1] == 0xa0)
438 else if (p[0] == 0xe2 && p[1] == 0x80 && p[2] == 0x87)
448 int last = ds_last (s);
449 if (last != EOF && !c_isspace (last))
453 ds_put_cstr (s, "<");
455 ds_put_cstr (s, ">");
457 ds_put_cstr (s, "&");
465 parse_embedded_html (const xmlNode *node)
467 /* Extract HTML from XML node. */
468 char *html_s = CHAR_CAST (char *, xmlNodeGetContent (node));
472 xmlDoc *html_doc = htmlReadMemory (
473 html_s, strlen (html_s),
474 NULL, "UTF-8", (HTML_PARSE_RECOVER | HTML_PARSE_NOERROR
475 | HTML_PARSE_NOWARNING | HTML_PARSE_NOBLANKS
476 | HTML_PARSE_NONET));
482 /* Given NODE, which should contain HTML content, returns the text within that
483 content as an allocated string. The caller must eventually free the
484 returned string (with xmlFree()). */
486 decode_embedded_html (const xmlNode *node, struct font_style *font_style)
488 struct string markup = DS_EMPTY_INITIALIZER;
489 *font_style = (struct font_style) FONT_STYLE_INITIALIZER;
490 font_style->size = 10;
492 xmlDoc *html_doc = parse_embedded_html (node);
495 xmlNode *root = xmlDocGetRootElement (html_doc);
496 xmlNode *head = root ? find_xml_child_element (root, "head") : NULL;
497 xmlNode *style = head ? find_xml_child_element (head, "style") : NULL;
500 uint8_t *style_s = xmlNodeGetContent (style);
501 spv_parse_css_style (CHAR_CAST (char *, style_s), font_style);
506 extract_html_text (root, font_style->size, &markup);
507 xmlFreeDoc (html_doc);
510 font_style->markup = true;
511 return ds_steal_cstr (&markup);
515 xstrdup_if_nonempty (const char *s)
517 return s && s[0] ? xstrdup (s) : NULL;
521 decode_container_text (const struct spvsx_container_text *ct,
522 struct spv_item *item)
524 item->type = SPV_ITEM_TEXT;
525 item->command_id = xstrdup_if_nonempty (ct->command_name);
527 item->text = xzalloc (sizeof *item->text);
528 item->text->type = PIVOT_VALUE_TEXT;
529 item->text->font_style = xmalloc (sizeof *item->text->font_style);
530 item->text->text.local = decode_embedded_html (ct->html->node_.raw,
531 item->text->font_style);
535 decode_page_p (const xmlNode *in, struct page_paragraph *out)
537 char *style = get_xml_attr (in, "style");
538 out->halign = (style && strstr (style, "center") ? TABLE_HALIGN_CENTER
539 : style && strstr (style, "right") ? TABLE_HALIGN_RIGHT
540 : TABLE_HALIGN_LEFT);
543 struct font_style font_style;
544 out->markup = decode_embedded_html (in, &font_style);
545 font_style_uninit (&font_style);
549 decode_page_paragraph (const struct spvsx_page_paragraph *page_paragraph,
550 struct page_heading *ph)
552 memset (ph, 0, sizeof *ph);
554 const struct spvsx_page_paragraph_text *page_paragraph_text
555 = page_paragraph->page_paragraph_text;
556 if (!page_paragraph_text)
559 xmlDoc *html_doc = parse_embedded_html (page_paragraph_text->node_.raw);
563 xmlNode *root = xmlDocGetRootElement (html_doc);
564 xmlNode *body = find_xml_child_element (root, "body");
566 for (const xmlNode *node = body->children; node; node = node->next)
567 if (node->type == XML_ELEMENT_NODE
568 && !strcmp (CHAR_CAST (const char *, node->name), "p"))
570 ph->paragraphs = xrealloc (ph->paragraphs,
571 (ph->n + 1) * sizeof *ph->paragraphs);
572 decode_page_p (node, &ph->paragraphs[ph->n++]);
574 xmlFreeDoc (html_doc);
578 spv_item_load (const struct spv_item *item)
580 if (spv_item_is_table (item))
581 spv_item_get_table (item);
582 else if (spv_item_is_graph (item))
583 spv_item_get_graph (item);
587 spv_item_is_light_table (const struct spv_item *item)
589 return item->type == SPV_ITEM_TABLE && !item->xml_member;
592 char * WARN_UNUSED_RESULT
593 spv_item_get_raw_light_table (const struct spv_item *item,
594 void **data, size_t *size)
596 return zip_member_read_all (item->spv->zip, item->bin_member, data, size);
599 char * WARN_UNUSED_RESULT
600 spv_item_get_light_table (const struct spv_item *item,
601 struct spvlb_table **tablep)
605 if (!spv_item_is_light_table (item))
606 return xstrdup ("not a light binary table object");
610 char *error = spv_item_get_raw_light_table (item, &data, &size);
614 struct spvbin_input input;
615 spvbin_input_init (&input, data, size);
617 struct spvlb_table *table;
619 ? xasprintf ("light table member is empty")
620 : !spvlb_parse_table (&input, &table)
621 ? spvbin_input_to_error (&input, NULL)
622 : input.ofs != input.size
623 ? xasprintf ("expected end of file at offset %#zx", input.ofs)
627 struct string s = DS_EMPTY_INITIALIZER;
628 spv_item_format_path (item, &s);
629 ds_put_format (&s, " (%s): %s", item->bin_member, error);
632 error = ds_steal_cstr (&s);
641 pivot_table_open_light (struct spv_item *item)
643 assert (spv_item_is_light_table (item));
645 struct spvlb_table *raw_table;
646 char *error = spv_item_get_light_table (item, &raw_table);
648 error = decode_spvlb_table (raw_table, &item->table);
649 spvlb_free_table (raw_table);
655 spv_item_is_legacy_table (const struct spv_item *item)
657 return item->type == SPV_ITEM_TABLE && item->xml_member;
660 char * WARN_UNUSED_RESULT
661 spv_item_get_raw_legacy_data (const struct spv_item *item,
662 void **data, size_t *size)
664 if (!spv_item_is_legacy_table (item) && !spv_item_is_graph (item))
665 return xstrdup ("not a graph or legacy table object");
667 if (!item->bin_member)
668 return xstrdup ("graph or legacy table lacks legacy data");
670 return zip_member_read_all (item->spv->zip, item->bin_member, data, size);
673 char * WARN_UNUSED_RESULT
674 spv_item_get_legacy_data (const struct spv_item *item, struct spv_data *data)
678 char *error = spv_item_get_raw_legacy_data (item, &raw, &size);
681 error = spv_legacy_data_decode (raw, size, data);
688 static char * WARN_UNUSED_RESULT
689 spv_read_xml_member (struct spv_reader *spv, const char *member_name,
690 bool keep_blanks, const char *root_element_name,
695 struct zip_member *zm = zip_member_open (spv->zip, member_name);
697 return ds_steal_cstr (&spv->zip_errs);
699 xmlParserCtxt *parser;
700 xmlKeepBlanksDefault (keep_blanks);
701 parser = xmlCreatePushParserCtxt(NULL, NULL, NULL, 0, NULL);
704 zip_member_finish (zm);
705 return xasprintf (_("%s: Failed to create XML parser"), member_name);
710 while ((retval = zip_member_read (zm, buf, sizeof buf)) > 0)
711 xmlParseChunk (parser, buf, retval, false);
712 xmlParseChunk (parser, NULL, 0, true);
714 xmlDoc *doc = parser->myDoc;
715 bool well_formed = parser->wellFormed;
716 xmlFreeParserCtxt (parser);
720 char *error = ds_steal_cstr (&spv->zip_errs);
721 zip_member_finish (zm);
725 zip_member_finish (zm);
730 return xasprintf(_("%s: document is not well-formed"), member_name);
733 const xmlNode *root_node = xmlDocGetRootElement (doc);
734 assert (root_node->type == XML_ELEMENT_NODE);
735 if (strcmp (CHAR_CAST (char *, root_node->name), root_element_name))
738 return xasprintf(_("%s: root node is \"%s\" but \"%s\" was expected"),
740 CHAR_CAST (char *, root_node->name), root_element_name);
747 char * WARN_UNUSED_RESULT
748 spv_item_get_legacy_table (const struct spv_item *item, xmlDoc **docp)
750 assert (spv_item_is_legacy_table (item));
752 return spv_read_xml_member (item->spv, item->xml_member, false,
753 "visualization", docp);
756 char * WARN_UNUSED_RESULT
757 spv_item_get_structure (const struct spv_item *item, struct _xmlDoc **docp)
759 return spv_read_xml_member (item->spv, item->structure_member, false,
764 identify_item (const struct spv_item *item)
766 return (item->label ? item->label
767 : item->command_id ? item->command_id
768 : spv_item_type_to_string (item->type));
772 spv_item_format_path (const struct spv_item *item, struct string *s)
774 enum { MAX_STACK = 32 };
775 const struct spv_item *stack[MAX_STACK];
778 while (item != NULL && item->parent && n < MAX_STACK)
787 ds_put_byte (s, '/');
789 const char *name = identify_item (item);
790 ds_put_cstr (s, name);
796 for (size_t i = 0; i < item->parent->n_children; i++)
798 const struct spv_item *sibling = item->parent->children[i];
801 else if (!strcmp (name, identify_item (sibling)))
805 ds_put_format (s, "[%zu]", index);
810 static char * WARN_UNUSED_RESULT
811 pivot_table_open_legacy (struct spv_item *item)
813 assert (spv_item_is_legacy_table (item));
815 struct spv_data data;
816 char *error = spv_item_get_legacy_data (item, &data);
819 struct string s = DS_EMPTY_INITIALIZER;
820 spv_item_format_path (item, &s);
821 ds_put_format (&s, " (%s): %s", item->bin_member, error);
824 return ds_steal_cstr (&s);
828 error = spv_read_xml_member (item->spv, item->xml_member, false,
829 "visualization", &doc);
832 spv_data_uninit (&data);
836 struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
837 struct spvdx_visualization *v;
838 spvdx_parse_visualization (&ctx, xmlDocGetRootElement (doc), &v);
839 error = spvxml_context_finish (&ctx, &v->node_);
842 error = decode_spvdx_table (v, item->subtype, item->legacy_properties,
843 &data, &item->table);
847 struct string s = DS_EMPTY_INITIALIZER;
848 spv_item_format_path (item, &s);
849 ds_put_format (&s, " (%s): %s", item->xml_member, error);
852 error = ds_steal_cstr (&s);
855 spv_data_uninit (&data);
856 spvdx_free_visualization (v);
864 spv_item_get_table (const struct spv_item *item_)
866 struct spv_item *item = CONST_CAST (struct spv_item *, item_);
868 assert (spv_item_is_table (item));
871 char *error = (item->xml_member
872 ? pivot_table_open_legacy (item)
873 : pivot_table_open_light (item));
877 msg (ME, "%s", error);
878 item->table = pivot_table_create_for_text (
879 pivot_value_new_text (N_("Error")),
880 pivot_value_new_user_text (error, -1));
888 static char * WARN_UNUSED_RESULT
889 spv_open_graph (struct spv_item *item)
891 assert (spv_item_is_graph (item));
893 struct spv_data data;
894 char *error = spv_item_get_legacy_data (item, &data);
897 struct string s = DS_EMPTY_INITIALIZER;
898 spv_item_format_path (item, &s);
899 ds_put_format (&s, " (%s): %s", item->bin_member, error);
902 return ds_steal_cstr (&s);
906 error = spv_read_xml_member (item->spv, item->xml_member, false,
907 "visualization", &doc);
910 spv_data_uninit (&data);
914 struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
915 struct vizml_visualization *v;
916 vizml_parse_visualization (&ctx, xmlDocGetRootElement (doc), &v);
917 error = spvxml_context_finish (&ctx, &v->node_);
921 struct string s = DS_EMPTY_INITIALIZER;
922 spv_item_format_path (item, &s);
923 ds_put_format (&s, " (%s): %s", item->xml_member, error);
926 error = ds_steal_cstr (&s);
929 spv_data_uninit (&data);
930 vizml_free_visualization (v);
938 spv_item_get_graph (const struct spv_item *item_)
940 struct spv_item *item = CONST_CAST (struct spv_item *, item_);
942 assert (spv_item_is_graph (item));
946 char *error = spv_open_graph (item);
950 msg (ME, "%s", error);
956 /* Constructs a new spv_item from XML and stores it in *ITEMP. Returns NULL if
957 successful, otherwise an error message for the caller to use and free (with
960 XML should be a 'heading' or 'container' element. */
961 static char * WARN_UNUSED_RESULT
962 spv_decode_container (const struct spvsx_container *c,
963 const char *structure_member,
964 struct spv_item *parent)
966 struct spv_item *item = xzalloc (sizeof *item);
967 item->spv = parent->spv;
968 item->label = xstrdup (c->label->text);
969 item->visible = c->visibility == SPVSX_VISIBILITY_VISIBLE;
970 item->structure_member = xstrdup (structure_member);
972 assert (c->n_seq == 1);
973 struct spvxml_node *content = c->seq[0];
974 if (spvsx_is_container_text (content))
975 decode_container_text (spvsx_cast_container_text (content), item);
976 else if (spvsx_is_table (content))
978 item->type = SPV_ITEM_TABLE;
980 struct spvsx_table *table = spvsx_cast_table (content);
981 const struct spvsx_table_structure *ts = table->table_structure;
982 item->bin_member = xstrdup (ts->data_path->text);
983 item->command_id = xstrdup_if_nonempty (table->command_name);
984 item->subtype = xstrdup_if_nonempty (table->sub_type);
987 item->xml_member = xstrdup_if_nonempty (ts->path->text);
988 char *error = decode_spvsx_legacy_properties (
989 table->table_properties, &item->legacy_properties);
992 spv_item_destroy (item);
997 else if (spvsx_is_graph (content))
999 item->type = SPV_ITEM_GRAPH;
1001 struct spvsx_graph *graph = spvsx_cast_graph (content);
1002 item->bin_member = xstrdup_if_nonempty (graph->data_path->text);
1003 item->command_id = xstrdup_if_nonempty (graph->command_name);
1004 item->xml_member = xstrdup_if_nonempty (graph->path->text);
1006 else if (spvsx_is_model (content))
1008 struct spvsx_model *model = spvsx_cast_model (content);
1009 item->type = SPV_ITEM_MODEL;
1010 item->command_id = xstrdup_if_nonempty (model->command_name);
1013 else if (spvsx_is_object (content))
1015 struct spvsx_object *object = spvsx_cast_object (content);
1016 item->type = SPV_ITEM_OBJECT;
1017 item->object_type = xstrdup (object->type);
1018 item->uri = xstrdup (object->uri);
1020 else if (spvsx_is_image (content))
1022 struct spvsx_image *image = spvsx_cast_image (content);
1023 item->type = SPV_ITEM_OBJECT;
1024 item->object_type = xstrdup ("image");
1025 item->uri = xstrdup (image->data_path->text);
1027 else if (spvsx_is_tree (content))
1029 struct spvsx_tree *tree = spvsx_cast_tree (content);
1030 item->type = SPV_ITEM_TREE;
1031 item->object_type = xstrdup ("tree");
1032 item->uri = xstrdup (tree->data_path->text);
1037 spv_heading_add_child (parent, item);
1041 static char * WARN_UNUSED_RESULT
1042 spv_decode_children (struct spv_reader *spv, const char *structure_member,
1043 struct spvxml_node **seq, size_t n_seq,
1044 struct spv_item *parent)
1046 for (size_t i = 0; i < n_seq; i++)
1048 const struct spvxml_node *node = seq[i];
1051 if (spvsx_is_container (node))
1053 const struct spvsx_container *container
1054 = spvsx_cast_container (node);
1055 error = spv_decode_container (container, structure_member, parent);
1057 else if (spvsx_is_heading (node))
1059 const struct spvsx_heading *subheading = spvsx_cast_heading (node);
1060 struct spv_item *subitem = xzalloc (sizeof *subitem);
1061 subitem->structure_member = xstrdup (structure_member);
1062 subitem->spv = parent->spv;
1063 subitem->type = SPV_ITEM_HEADING;
1064 subitem->label = xstrdup (subheading->label->text);
1065 if (subheading->command_name)
1066 subitem->command_id = xstrdup (subheading->command_name);
1067 subitem->visible = !subheading->heading_visibility_present;
1068 spv_heading_add_child (parent, subitem);
1070 error = spv_decode_children (spv, structure_member,
1071 subheading->seq, subheading->n_seq,
1084 static struct page_setup *
1085 decode_page_setup (const struct spvsx_page_setup *in, const char *file_name)
1087 struct page_setup *out = xmalloc (sizeof *out);
1088 *out = (struct page_setup) PAGE_SETUP_INITIALIZER;
1090 out->initial_page_number = in->initial_page_number;
1092 if (in->paper_width != DBL_MAX)
1093 out->paper[TABLE_HORZ] = in->paper_width;
1094 if (in->paper_height != DBL_MAX)
1095 out->paper[TABLE_VERT] = in->paper_height;
1097 if (in->margin_left != DBL_MAX)
1098 out->margins[TABLE_HORZ][0] = in->margin_left;
1099 if (in->margin_right != DBL_MAX)
1100 out->margins[TABLE_HORZ][1] = in->margin_right;
1101 if (in->margin_top != DBL_MAX)
1102 out->margins[TABLE_VERT][0] = in->margin_top;
1103 if (in->margin_bottom != DBL_MAX)
1104 out->margins[TABLE_VERT][1] = in->margin_bottom;
1106 if (in->space_after != DBL_MAX)
1107 out->object_spacing = in->space_after;
1110 out->chart_size = (in->chart_size == SPVSX_CHART_SIZE_FULL_HEIGHT
1111 ? PAGE_CHART_FULL_HEIGHT
1112 : in->chart_size == SPVSX_CHART_SIZE_HALF_HEIGHT
1113 ? PAGE_CHART_HALF_HEIGHT
1114 : in->chart_size == SPVSX_CHART_SIZE_QUARTER_HEIGHT
1115 ? PAGE_CHART_QUARTER_HEIGHT
1116 : PAGE_CHART_AS_IS);
1118 decode_page_paragraph (in->page_header->page_paragraph, &out->headings[0]);
1119 decode_page_paragraph (in->page_footer->page_paragraph, &out->headings[1]);
1121 out->file_name = xstrdup (file_name);
1126 static char * WARN_UNUSED_RESULT
1127 spv_heading_read (struct spv_reader *spv,
1128 const char *file_name, const char *member_name)
1131 char *error = spv_read_xml_member (spv, member_name, true, "heading", &doc);
1135 struct spvxml_context ctx = SPVXML_CONTEXT_INIT (ctx);
1136 struct spvsx_root_heading *root;
1137 spvsx_parse_root_heading (&ctx, xmlDocGetRootElement (doc), &root);
1138 error = spvxml_context_finish (&ctx, &root->node_);
1140 if (!error && root->page_setup)
1141 spv->page_setup = decode_page_setup (root->page_setup, file_name);
1143 for (size_t i = 0; !error && i < root->n_seq; i++)
1144 error = spv_decode_children (spv, member_name, root->seq, root->n_seq,
1149 char *s = xasprintf ("%s: %s", member_name, error);
1154 spvsx_free_root_heading (root);
1161 spv_get_root (const struct spv_reader *spv)
1167 spv_detect__ (struct zip_reader *zip, char **errorp)
1171 const char *member = "META-INF/MANIFEST.MF";
1172 if (!zip_reader_contains_member (zip, member))
1177 *errorp = zip_member_read_all (zip, "META-INF/MANIFEST.MF",
1182 const char *magic = "allowPivoting=true";
1183 bool is_spv = size == strlen (magic) && !memcmp (magic, data, size);
1189 /* Returns NULL if FILENAME is an SPV file, otherwise an error string that the
1190 caller must eventually free(). */
1191 char * WARN_UNUSED_RESULT
1192 spv_detect (const char *filename)
1194 struct string zip_error;
1195 struct zip_reader *zip = zip_reader_create (filename, &zip_error);
1197 return ds_steal_cstr (&zip_error);
1200 if (spv_detect__ (zip, &error) <= 0 && !error)
1201 error = xasprintf("%s: not an SPV file", filename);
1202 zip_reader_destroy (zip);
1203 ds_destroy (&zip_error);
1207 char * WARN_UNUSED_RESULT
1208 spv_open (const char *filename, struct spv_reader **spvp)
1212 struct spv_reader *spv = xzalloc (sizeof *spv);
1213 ds_init_empty (&spv->zip_errs);
1214 spv->zip = zip_reader_create (filename, &spv->zip_errs);
1217 char *error = ds_steal_cstr (&spv->zip_errs);
1223 int detect = spv_detect__ (spv->zip, &error);
1227 return error ? error : xasprintf("%s: not an SPV file", filename);
1230 spv->root = xzalloc (sizeof *spv->root);
1231 spv->root->spv = spv;
1232 spv->root->type = SPV_ITEM_HEADING;
1233 for (size_t i = 0; ; i++)
1235 const char *member_name = zip_reader_get_member_name (spv->zip, i);
1239 struct substring member_name_ss = ss_cstr (member_name);
1240 if (ss_starts_with (member_name_ss, ss_cstr ("outputViewer"))
1241 && ss_ends_with (member_name_ss, ss_cstr (".xml")))
1243 char *error = spv_heading_read (spv, filename, member_name);
1257 spv_close (struct spv_reader *spv)
1261 ds_destroy (&spv->zip_errs);
1262 zip_reader_destroy (spv->zip);
1263 spv_item_destroy (spv->root);
1264 page_setup_destroy (spv->page_setup);
1269 char * WARN_UNUSED_RESULT
1270 spv_decode_fmt_spec (uint32_t u32, struct fmt_spec *out)
1273 || (u32 == 0x10000 || u32 == 1 /* both used as string formats */))
1275 *out = fmt_for_output (FMT_F, 40, 2);
1279 uint8_t raw_type = u32 >> 16;
1280 uint8_t w = u32 >> 8;
1284 *out = (struct fmt_spec) { .type = FMT_F, .w = w, .d = d };
1285 bool ok = raw_type >= 40 || fmt_from_io (raw_type, &out->type);
1288 fmt_fix_output (out);
1289 ok = fmt_check_width_compat (out, 0);
1295 *out = fmt_for_output (FMT_F, 40, 2);
1296 return xasprintf ("bad format %#"PRIx32, u32);