5bfd1254134629be3d92bc437cbcffa6623f77c8
[pspp] / src / output / output-item.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 2011 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/output-item.h"
20
21 #include <assert.h>
22 #include <stdlib.h>
23
24 #include "libpspp/assertion.h"
25 #include "libpspp/cast.h"
26 #include "libpspp/message.h"
27 #include "libpspp/str.h"
28 #include "libpspp/zip-reader.h"
29 #include "output/chart.h"
30 #include "output/driver.h"
31 #include "output/page-setup.h"
32 #include "output/pivot-table.h"
33
34 #include "gl/xalloc.h"
35
36 #include "gettext.h"
37 #define _(msgid) gettext (msgid)
38 #define N_(msgid) msgid
39 \f
40 const char *
41 output_item_type_to_string (enum output_item_type type)
42 {
43   switch (type)
44     {
45     case OUTPUT_ITEM_CHART: return "chart";
46     case OUTPUT_ITEM_GROUP: return "group";
47     case OUTPUT_ITEM_IMAGE: return "image";
48     case OUTPUT_ITEM_MESSAGE: return "message";
49     case OUTPUT_ITEM_PAGE_BREAK: return "page break";
50     case OUTPUT_ITEM_PAGE_SETUP: return "page setup";
51     case OUTPUT_ITEM_TABLE: return "table";
52     case OUTPUT_ITEM_TEXT: return "text";
53     }
54
55   NOT_REACHED ();
56 }
57 \f
58 #define OUTPUT_ITEM_INITIALIZER(TYPE) .type = TYPE, .ref_cnt = 1, .show = true
59
60 /* Increases ITEM's reference count, indicating that it has an additional
61    owner.  An output item that is shared among multiple owners must not be
62    modified. */
63 struct output_item *
64 output_item_ref (const struct output_item *item_)
65 {
66   struct output_item *item = CONST_CAST (struct output_item *, item_);
67   assert (item->ref_cnt > 0);
68   item->ref_cnt++;
69   return item;
70 }
71
72 /* Decreases ITEM's reference count, indicating that it has one fewer owner.
73    If ITEM no longer has any owners, it is freed. */
74 void
75 output_item_unref (struct output_item *item)
76 {
77   if (item != NULL)
78     {
79       assert (item->ref_cnt > 0);
80       if (--item->ref_cnt == 0)
81         {
82           switch (item->type)
83             {
84             case OUTPUT_ITEM_CHART:
85               chart_unref (item->chart);
86               break;
87
88             case OUTPUT_ITEM_GROUP:
89               for (size_t i = 0; i < item->group.n_children; i++)
90                 output_item_unref (item->group.children[i]);
91               break;
92
93             case OUTPUT_ITEM_IMAGE:
94               cairo_surface_destroy (item->image);
95               break;
96
97             case OUTPUT_ITEM_MESSAGE:
98               msg_destroy (item->message);
99               break;
100
101             case OUTPUT_ITEM_PAGE_BREAK:
102               break;
103
104             case OUTPUT_ITEM_PAGE_SETUP:
105               page_setup_destroy (item->page_setup);
106               break;
107
108             case OUTPUT_ITEM_TABLE:
109               pivot_table_unref (item->table);
110               break;
111
112             case OUTPUT_ITEM_TEXT:
113               pivot_value_destroy (item->text.content);
114               break;
115             }
116
117           free (item->label);
118           free (item->command_name);
119           free (item->cached_label);
120           spv_info_destroy (item->spv_info);
121           free (item);
122         }
123     }
124 }
125
126 /* Returns true if ITEM has more than one owner.  An output item that is shared
127    among multiple owners must not be modified. */
128 bool
129 output_item_is_shared (const struct output_item *item)
130 {
131   return item->ref_cnt > 1;
132 }
133
134 /* Returns a clone of OLD, without initializing type-specific fields.  */
135 static struct output_item *
136 output_item_clone_common (const struct output_item *old)
137 {
138   struct output_item *new = xmalloc (sizeof *new);
139   *new = (struct output_item) {
140     .ref_cnt = 1,
141     .label = xstrdup_if_nonnull (old->label),
142     .command_name = xstrdup_if_nonnull (old->command_name),
143     .type = old->type,
144     .show = old->show,
145     .spv_info = spv_info_clone (old->spv_info),
146   };
147   return new;
148 }
149
150 struct output_item *
151 output_item_unshare (struct output_item *old)
152 {
153   assert (old->ref_cnt > 0);
154   if (!output_item_is_shared (old))
155     return old;
156   output_item_unref (old);
157
158   struct output_item *new = output_item_clone_common (old);
159   switch (old->type)
160     {
161     case OUTPUT_ITEM_CHART:
162       new->chart = chart_ref (old->chart);
163       break;
164
165     case OUTPUT_ITEM_GROUP:
166       new->group.children = xmemdup (
167         old->group.children,
168         old->group.n_children * sizeof *old->group.children);
169       new->group.n_children = new->group.allocated_children
170         = old->group.n_children;
171
172       for (size_t i = 0; i < new->group.n_children; i++)
173         output_item_ref (new->group.children[i]);
174       break;
175
176     case OUTPUT_ITEM_IMAGE:
177       new->image = cairo_surface_reference (old->image);
178       break;
179
180     case OUTPUT_ITEM_MESSAGE:
181       new->message = msg_dup (old->message);
182       break;
183
184     case OUTPUT_ITEM_PAGE_BREAK:
185       break;
186
187     case OUTPUT_ITEM_PAGE_SETUP:
188       new->page_setup = page_setup_clone (old->page_setup);
189       break;
190
191     case OUTPUT_ITEM_TABLE:
192       new->table = pivot_table_ref (old->table);
193       break;
194
195     case OUTPUT_ITEM_TEXT:
196       new->text.subtype = old->text.subtype;
197       new->text.content = pivot_value_clone (old->text.content);
198       break;
199     }
200   return new;
201 }
202
203 void
204 output_item_submit (struct output_item *item)
205 {
206   output_submit (item);
207 }
208
209 /* If ROOT is a group item, submits each of its children, but not ROOT itself.
210    This is useful if ROOT is being used as a container for output items but it
211    has no significance itself.
212
213    If ROOT is not a group, submits it the normal way.
214
215    Takes ownership of ROOT. */
216 void
217 output_item_submit_children (struct output_item *root)
218 {
219   assert (!output_item_is_shared (root));
220   if (root->type == OUTPUT_ITEM_GROUP)
221     {
222       for (size_t i = 0; i < root->group.n_children; i++)
223         output_submit (root->group.children[i]);
224       root->group.n_children = 0;
225       output_item_unref (root);
226     }
227   else
228     output_submit (root);
229 }
230
231 /* Returns the label for ITEM, which the caller must not modify or free. */
232 const char *
233 output_item_get_label (const struct output_item *item)
234 {
235   if (item->label)
236     return item->label;
237
238   switch (item->type)
239     {
240     case OUTPUT_ITEM_CHART:
241       return item->chart->title ? item->chart->title : _("Chart");
242
243     case OUTPUT_ITEM_GROUP:
244       return item->command_name ? item->command_name : _("Group");
245
246     case OUTPUT_ITEM_IMAGE:
247       return "Image";
248
249     case OUTPUT_ITEM_MESSAGE:
250       return (item->message->severity == MSG_S_ERROR ? _("Error")
251               : item->message->severity == MSG_S_WARNING ? _("Warning")
252               : _("Note"));
253
254     case OUTPUT_ITEM_PAGE_BREAK:
255       return _("Page Break");
256
257     case OUTPUT_ITEM_PAGE_SETUP:
258       /* Not marked for translation: user should never see it. */
259       return "Page Setup";
260
261     case OUTPUT_ITEM_TABLE:
262       if (!item->cached_label)
263         {
264           if (!item->table->title)
265             return _("Table");
266
267           struct output_item *item_rw = CONST_CAST (struct output_item *, item);
268           item_rw->cached_label = pivot_value_to_string (item->table->title,
269                                                          item->table);
270         }
271       return item->cached_label;
272
273     case OUTPUT_ITEM_TEXT:
274       return text_item_subtype_to_string (item->text.subtype);
275     }
276
277   NOT_REACHED ();
278 }
279
280 /* Sets the label for ITEM to LABEL.  The caller retains ownership of LABEL.
281    If LABEL is nonnull, it overrides any previously set label and the default
282    label.  If LABEL is null, ITEM will now use its default label.
283
284    ITEM must not be shared. */
285 void
286 output_item_set_label (struct output_item *item, const char *label)
287 {
288   output_item_set_label_nocopy (item, xstrdup_if_nonnull (label));
289 }
290
291 /* Sets the label for ITEM to LABEL, transferring ownership of LABEL to ITEM.
292    If LABEL is nonnull, it overrides any previously set label and the default
293    label.  If LABEL is null, ITEM will now use its default label.
294
295    ITEM must not be shared. */
296 void
297 output_item_set_label_nocopy (struct output_item *item, char *label)
298 {
299   assert (!output_item_is_shared (item));
300   free (item->label);
301   item->label = label;
302 }
303
304 void
305 output_item_set_command_name (struct output_item *item, const char *name)
306 {
307   output_item_set_command_name_nocopy (item, xstrdup_if_nonnull (name));
308 }
309
310 void
311 output_item_set_command_name_nocopy (struct output_item *item, char *name)
312 {
313   free (item->command_name);
314   item->command_name = name;
315 }
316
317 char *
318 output_item_get_subtype (const struct output_item *item)
319 {
320   return (item->type == OUTPUT_ITEM_TABLE
321           ? pivot_value_to_string (item->table->subtype, item->table)
322           : NULL);
323 }
324
325 void
326 output_item_add_spv_info (struct output_item *item)
327 {
328   assert (!output_item_is_shared (item));
329   if (!item->spv_info)
330     item->spv_info = xzalloc (sizeof *item->spv_info);
331 }
332 \f
333 static void
334 indent (int indentation)
335 {
336   for (int i = 0; i < indentation * 2; i++)
337     putchar (' ');
338 }
339
340 void
341 output_item_dump (const struct output_item *item, int indentation)
342 {
343   indent (indentation);
344   if (item->label)
345     printf ("label=\"%s\" ", item->label);
346   if (item->command_name)
347     printf ("command=\"%s\" ", item->command_name);
348   if (!item->show)
349     printf ("(%s) ", item->type == OUTPUT_ITEM_GROUP ? "collapsed" : "hidden");
350
351   switch (item->type)
352     {
353     case OUTPUT_ITEM_CHART:
354       printf ("chart \"%s\"\n", item->chart->title ? item->chart->title : "");
355       break;
356
357     case OUTPUT_ITEM_GROUP:
358       printf ("group\n");
359       for (size_t i = 0; i < item->group.n_children; i++)
360         output_item_dump (item->group.children[i], indentation + 1);
361       break;
362
363     case OUTPUT_ITEM_IMAGE:
364       printf ("image\n");
365       break;
366
367     case OUTPUT_ITEM_MESSAGE:
368       printf ("message\n");
369       break;
370
371     case OUTPUT_ITEM_PAGE_BREAK:
372       printf ("page break\n");
373       break;
374
375     case OUTPUT_ITEM_PAGE_SETUP:
376       printf ("page setup\n");
377       break;
378
379     case OUTPUT_ITEM_TABLE:
380       pivot_table_dump (item->table, indentation + 1);
381       break;
382
383     case OUTPUT_ITEM_TEXT:
384       printf ("text %s \"%s\"\n",
385               text_item_subtype_to_string (item->text.subtype),
386               pivot_value_to_string_defaults (item->text.content));
387       break;
388     }
389 }
390 \f
391 void
392 output_iterator_init (struct output_iterator *iter,
393                       const struct output_item *item)
394 {
395   *iter = (struct output_iterator) OUTPUT_ITERATOR_INIT (item);
396 }
397
398 void
399 output_iterator_destroy (struct output_iterator *iter)
400 {
401   if (iter)
402     {
403       free (iter->nodes);
404       iter->nodes = NULL;
405       iter->n = iter->allocated = 0;
406     }
407 }
408
409 void
410 output_iterator_next (struct output_iterator *iter)
411 {
412   const struct output_item *cur = iter->cur;
413   if (cur)
414     {
415       if (cur->type == OUTPUT_ITEM_GROUP && cur->group.n_children > 0)
416         {
417           if (iter->n >= iter->allocated)
418             iter->nodes = x2nrealloc (iter->nodes, &iter->allocated,
419                                       sizeof *iter->nodes);
420           iter->nodes[iter->n++] = (struct output_iterator_node) {
421             .group = cur,
422             .idx = 0,
423           };
424           iter->cur = cur->group.children[0];
425           return;
426         }
427
428       for (; iter->n > 0; iter->n--)
429         {
430           struct output_iterator_node *node = &iter->nodes[iter->n - 1];
431           if (++node->idx < node->group->group.n_children)
432             {
433               iter->cur = node->group->group.children[node->idx];
434               return;
435             }
436         }
437
438       iter->cur = NULL;
439       output_iterator_destroy (iter);
440     }
441 }
442 \f
443 struct output_item *
444 chart_item_create (struct chart *chart)
445 {
446   struct output_item *item = xmalloc (sizeof *item);
447   *item = (struct output_item) {
448     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_CHART),
449     .chart = chart,
450   };
451   return item;
452 }
453 \f
454 struct output_item *
455 group_item_create (const char *command_name, const char *label)
456 {
457   return group_item_create_nocopy (
458     xstrdup_if_nonnull (command_name),
459     xstrdup_if_nonnull (label));
460 }
461
462 struct output_item *
463 group_item_create_nocopy (char *command_name, char *label)
464 {
465   struct output_item *item = xmalloc (sizeof *item);
466   *item = (struct output_item) {
467     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_GROUP),
468     .label = label,
469     .command_name = command_name,
470   };
471   return item;
472 }
473
474 /* Returns a new group item suitable as the root node of an output document.  A
475    root node is a group whose own properties are mostly disregarded.  Instead
476    of having root nodes, it would make just as much sense to just keep around
477    arrays of nodes that would serve as the top level of an output document, but
478    we'd need more special cases instead of just using the existing support for
479    group items. */
480 struct output_item *
481 root_item_create (void)
482 {
483   return group_item_create ("", _("Output"));
484 }
485
486 /* Returns a clone of OLD but without any of its children. */
487 struct output_item *
488 group_item_clone_empty (const struct output_item *old)
489 {
490   return output_item_clone_common (old);
491 }
492
493 /* Adds CHILD as a child of group item PARENT. */
494 void
495 group_item_add_child (struct output_item *parent, struct output_item *child)
496 {
497   assert (parent->type == OUTPUT_ITEM_GROUP);
498   assert (!output_item_is_shared (parent));
499   if (parent->group.n_children >= parent->group.allocated_children)
500     parent->group.children = x2nrealloc (parent->group.children,
501                                          &parent->group.allocated_children,
502                                          sizeof *parent->group.children);
503   parent->group.children[parent->group.n_children++] = child;
504 }
505 \f
506 /* Creates and returns a new output item containing IMAGE.  Takes ownership of
507    IMAGE. */
508 struct output_item *
509 image_item_create (cairo_surface_t *image)
510 {
511   struct output_item *item = xmalloc (sizeof *item);
512   *item = (struct output_item) {
513     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_IMAGE),
514     .image = image,
515   };
516   return item;
517 }
518 \f
519 struct output_item *
520 message_item_create (const struct msg *msg)
521 {
522   struct output_item *item = xmalloc (sizeof *item);
523   *item = (struct output_item) {
524     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_MESSAGE),
525     .message = msg_dup (msg),
526   };
527   return item;
528 }
529
530 const struct msg *
531 message_item_get_msg (const struct output_item *item)
532 {
533   assert (item->type == OUTPUT_ITEM_MESSAGE);
534   return item->message;
535 }
536
537 struct output_item *
538 message_item_to_text_item (struct output_item *message_item)
539 {
540   assert (message_item->type == OUTPUT_ITEM_MESSAGE);
541   struct output_item *text_item = text_item_create_nocopy (
542     TEXT_ITEM_LOG,
543     msg_to_string (message_item->message),
544     xstrdup (output_item_get_label (message_item)));
545   output_item_unref (message_item);
546   return text_item;
547 }
548 \f
549 struct output_item *
550 page_break_item_create (void)
551 {
552   struct output_item *item = xmalloc (sizeof *item);
553   *item = (struct output_item) {
554     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_PAGE_BREAK),
555   };
556   return item;
557 }
558 \f
559 struct output_item *
560 page_setup_item_create (const struct page_setup *ps)
561 {
562   struct output_item *item = xmalloc (sizeof *item);
563   *item = (struct output_item) {
564     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_PAGE_SETUP),
565     .page_setup = page_setup_clone (ps),
566   };
567   return item;
568 }
569 \f
570 /* Returns a new output_item for rendering TABLE.  Takes ownership of
571    TABLE. */
572 struct output_item *
573 table_item_create (struct pivot_table *table)
574 {
575   pivot_table_assign_label_depth (table);
576
577   struct output_item *item = xmalloc (sizeof *item);
578   *item = (struct output_item) {
579     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_TABLE),
580     .command_name = xstrdup_if_nonnull (table->command_c),
581     .table = table,
582   };
583   return item;
584 }
585 \f
586 /* Creates and returns a new text item containing TEXT and the specified
587    SUBTYPE and LABEL.  The new text item takes ownership of TEXT and LABEL.  If
588    LABEL is NULL, uses the default label for SUBTYPE. */
589 struct output_item *
590 text_item_create_nocopy (enum text_item_subtype subtype,
591                          char *text, char *label)
592 {
593   return text_item_create_value (subtype,
594                                  pivot_value_new_user_text_nocopy (text),
595                                  label);
596 }
597
598 /* Creates and returns a new text item containing a copy of TEXT and the
599    specified SUBTYPE and LABEL.  The caller retains ownership of TEXT and
600    LABEL.  If LABEL is null, uses a default label for SUBTYPE. */
601 struct output_item *
602 text_item_create (enum text_item_subtype subtype, const char *text,
603                   const char *label)
604 {
605   return text_item_create_nocopy (subtype, xstrdup (text),
606                                   xstrdup_if_nonnull (label));
607 }
608
609 /* Creates and returns a new text item containing VALUE, SUBTYPE, and LABEL.
610    Takes ownership of VALUE and LABEL.  If LABEL is null, uses a default label
611    for SUBTYPE. */
612 struct output_item *
613 text_item_create_value (enum text_item_subtype subtype,
614                         struct pivot_value *value, char *label)
615 {
616   if (subtype == TEXT_ITEM_SYNTAX || subtype == TEXT_ITEM_LOG)
617     {
618       if (!value->font_style)
619         {
620           value->font_style = xmalloc (sizeof *value->font_style);
621           *value->font_style = (struct font_style) FONT_STYLE_INITIALIZER;
622         }
623
624       free (value->font_style->typeface);
625       value->font_style->typeface = xstrdup ("Monospaced");
626     }
627
628   struct output_item *item = xzalloc (sizeof *item);
629   *item = (struct output_item) {
630     OUTPUT_ITEM_INITIALIZER (OUTPUT_ITEM_TEXT),
631     .command_name = xstrdup_if_nonnull (output_get_command_name ()),
632     .label = label,
633     .text = { .subtype = subtype, .content = value },
634   };
635   return item;
636 }
637
638 /* Returns ITEM's subtype. */
639 enum text_item_subtype
640 text_item_get_subtype (const struct output_item *item)
641 {
642   assert (item->type == OUTPUT_ITEM_TEXT);
643   return item->text.subtype;
644 }
645
646 /* Returns ITEM's text, which the caller must eventually free. */
647 char *
648 text_item_get_plain_text (const struct output_item *item)
649 {
650   assert (item->type == OUTPUT_ITEM_TEXT);
651   return pivot_value_to_string_defaults (item->text.content);
652 }
653
654 static bool
655 nullable_font_style_equal (const struct font_style *a,
656                            const struct font_style *b)
657 {
658   return a && b ? font_style_equal (a, b) : !a && !b;
659 }
660
661 /* Attempts to append the text in SRC to DST.  If successful, returns true,
662    otherwise false.
663
664    Only TEXT_ITEM_SYNTAX and TEXT_ITEM_LOG items can be combined, and not with
665    each other.
666
667    DST must not be shared. */
668 bool
669 text_item_append (struct output_item *dst, const struct output_item *src)
670 {
671   assert (dst->type == OUTPUT_ITEM_TEXT);
672   assert (src->type == OUTPUT_ITEM_TEXT);
673   assert (!output_item_is_shared (dst));
674
675   enum text_item_subtype ds = dst->text.subtype;
676   enum text_item_subtype ss = src->text.subtype;
677
678   struct pivot_value *dc = dst->text.content;
679   const struct pivot_value *sc = src->text.content;
680
681   if (ds != ss
682       || (ds != TEXT_ITEM_SYNTAX && ds != TEXT_ITEM_LOG)
683       || strcmp (output_item_get_label (dst), output_item_get_label (src))
684       || !nullable_font_style_equal (dc->font_style, sc->font_style)
685       || (dc->font_style && dc->font_style->markup)
686       || sc->type != PIVOT_VALUE_TEXT
687       || dc->type != PIVOT_VALUE_TEXT)
688     return false;
689   else
690     {
691       /* Calculate new text. */
692       char *new_text = xasprintf ("%s\n%s", dc->text.local, sc->text.local);
693
694       /* Free the old text. */
695       free (dc->text.local);
696       if (dc->text.c != dc->text.local)
697         free (dc->text.c);
698       if (dc->text.id != dc->text.local && dc->text.id != dc->text.c)
699         free (dc->text.id);
700
701       /* Put in new text. */
702       dc->text.local = new_text;
703       dc->text.c = new_text;
704       dc->text.id = new_text;
705
706       return true;
707     }
708 }
709
710 static const struct pivot_table_look *
711 text_item_table_look (void)
712 {
713   static struct pivot_table_look *look;
714   if (!look)
715     {
716       look = pivot_table_look_new_builtin_default ();
717
718       for (int a = 0; a < PIVOT_N_AREAS; a++)
719         memset (look->areas[a].cell_style.margin, 0,
720                 sizeof look->areas[a].cell_style.margin);
721       for (int b = 0; b < PIVOT_N_BORDERS; b++)
722         look->borders[b].stroke = TABLE_STROKE_NONE;
723     }
724   return look;
725 }
726
727 struct output_item *
728 text_item_to_table_item (struct output_item *text_item)
729 {
730   assert (text_item->type == OUTPUT_ITEM_TEXT);
731
732   /* Create a new table whose contents come from TEXT_ITEM. */
733   struct pivot_table *table = pivot_table_create__ (NULL, "Text");
734   pivot_table_set_look (table, text_item_table_look ());
735
736   struct pivot_dimension *d = pivot_dimension_create (
737     table, PIVOT_AXIS_ROW, N_("Text"));
738   d->hide_all_labels = true;
739   pivot_category_create_leaf (d->root, pivot_value_new_text ("null"));
740
741   pivot_table_put1 (table, 0, pivot_value_clone (text_item->text.content));
742
743   /* Free TEXT_ITEM. */
744   output_item_unref (text_item);
745
746   /* Return a new output item. */
747   return table_item_create (table);
748 }
749
750 const char *
751 text_item_subtype_to_string (enum text_item_subtype subtype)
752 {
753   switch (subtype)
754     {
755     case TEXT_ITEM_PAGE_TITLE:
756       return _("Page Title");
757
758     case TEXT_ITEM_TITLE:
759       return _("Title");
760
761     case TEXT_ITEM_SYNTAX:
762     case TEXT_ITEM_LOG:
763       return _("Log");
764
765     default:
766       return _("Text");
767     }
768 }
769 \f
770 void
771 spv_info_destroy (struct spv_info *spv_info)
772 {
773   if (spv_info)
774     {
775       zip_reader_unref (spv_info->zip_reader);
776       free (spv_info->structure_member);
777       free (spv_info->xml_member);
778       free (spv_info->bin_member);
779       free (spv_info->png_member);
780       free (spv_info);
781     }
782 }
783
784 struct spv_info *
785 spv_info_clone (const struct spv_info *old)
786 {
787   if (!old)
788     return NULL;
789
790   struct spv_info *new = xmalloc (sizeof *new);
791   *new = (struct spv_info) {
792     .zip_reader = old->zip_reader ? zip_reader_ref (old->zip_reader) : NULL,
793     .error = old->error,
794     .structure_member = xstrdup_if_nonnull (old->structure_member),
795     .xml_member = xstrdup_if_nonnull (old->xml_member),
796     .bin_member = xstrdup_if_nonnull (old->bin_member),
797     .png_member = xstrdup_if_nonnull (old->png_member),
798   };
799   return new;
800 }
801
802 size_t
803 spv_info_get_members (const struct spv_info *spv_info, const char **members,
804                       size_t allocated_members)
805 {
806   if (!spv_info)
807     return 0;
808
809   const char *s[] = {
810     spv_info->structure_member,
811     spv_info->xml_member,
812     spv_info->bin_member,
813     spv_info->png_member,
814   };
815   size_t n = 0;
816   for (size_t i = 0; i < sizeof s / sizeof *s; i++)
817     if (s[i] && n < allocated_members)
818       members[n++] = s[i];
819   return n;
820 }