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