8a6e9c865931963ee741d84a17a310f6bbdfcccf
[pspp] / src / output / pivot-output.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 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 <stdlib.h>
20
21 #include "output/pivot-output.h"
22
23 #include "data/settings.h"
24 #include "libpspp/assertion.h"
25 #include "libpspp/pool.h"
26 #include "output/output-item.h"
27 #include "output/pivot-table.h"
28 #include "output/table-provider.h"
29 #include "output/table.h"
30
31 #include "gl/minmax.h"
32 #include "gl/xalloc.h"
33
34 #define H TABLE_HORZ
35 #define V TABLE_VERT
36
37 size_t *
38 pivot_output_next_layer (const struct pivot_table *pt, size_t *indexes,
39                          bool print)
40 {
41   const struct pivot_axis *layer_axis = &pt->axes[PIVOT_AXIS_LAYER];
42   if (print && pt->look->print_all_layers)
43     return pivot_axis_iterator_next (indexes, layer_axis);
44   else if (!indexes)
45     {
46       size_t size = layer_axis->n_dimensions * sizeof *pt->current_layer;
47       return size ? xmemdup (pt->current_layer, size) : xmalloc (1);
48     }
49   else
50     {
51       free (indexes);
52       return NULL;
53     }
54 }
55
56 static const struct pivot_category *
57 find_category (const struct pivot_dimension *d, int dim_index,
58                const size_t *indexes, int row_ofs)
59 {
60   size_t index = indexes[dim_index];
61   assert (index < d->n_leaves);
62   for (const struct pivot_category *c = d->presentation_leaves[index];
63        c; c = c->parent)
64     {
65       /* A category can cover multiple rows.  Only return the category for its
66          top row. */
67       if (row_ofs == c->extra_depth)
68         return c;
69
70       row_ofs -= 1 + c->extra_depth;
71       if (row_ofs < 0)
72         return NULL;
73     }
74   return NULL;
75 }
76
77 static struct table_area_style *
78 table_area_style_override (struct pool *pool,
79                            const struct table_area_style *in,
80                            const struct cell_style *cell_,
81                            const struct font_style *font_,
82                            bool rotate_label)
83 {
84   const struct cell_style *cell = cell_ ? cell_ : &in->cell_style;
85   const struct font_style *font = font_ ? font_ : &in->font_style;
86
87   struct table_area_style *out = (pool
88                             ? pool_alloc (pool, sizeof *out)
89                             : xmalloc (sizeof *out));
90   *out = (struct table_area_style) {
91     .cell_style.halign = rotate_label ? TABLE_HALIGN_CENTER : cell->halign,
92     .cell_style.valign = rotate_label ? TABLE_VALIGN_CENTER : cell->valign,
93     .cell_style.decimal_offset = cell->decimal_offset,
94     .cell_style.margin[H][0] = cell->margin[H][0],
95     .cell_style.margin[H][1] = cell->margin[H][1],
96     .cell_style.margin[V][0] = cell->margin[V][0],
97     .cell_style.margin[V][1] = cell->margin[V][1],
98     .font_style.fg[0] = font->fg[0],
99     .font_style.fg[1] = font->fg[1],
100     .font_style.bg[0] = font->bg[0],
101     .font_style.bg[1] = font->bg[1],
102     .font_style.typeface = (font->typeface
103                             ? pool_strdup (pool, font->typeface)
104                             : NULL),
105     .font_style.size = font->size,
106     .font_style.bold = font->bold,
107     .font_style.italic = font->italic,
108     .font_style.underline = font->underline,
109     .font_style.markup = font->markup,
110   };
111   return out;
112 }
113
114 static void
115 fill_cell (struct table *t, int x1, int y1, int x2, int y2,
116            int style_idx, const struct pivot_value *value,
117            bool rotate_label)
118 {
119   int options = style_idx << TABLE_CELL_STYLE_SHIFT;
120   if (rotate_label)
121     options |= TABLE_CELL_ROTATE;
122
123   table_put (t, x1, y1, x2, y2, options, value);
124 }
125
126 static void
127 fill_cell_owned (struct table *t, int x1, int y1, int x2, int y2,
128                  int style_idx, struct string *s, bool rotate_label)
129 {
130   int options = style_idx << TABLE_CELL_STYLE_SHIFT;
131   if (rotate_label)
132     options |= TABLE_CELL_ROTATE;
133
134   table_put_owned (t, x1, y1, x2, y2, options,
135                    pivot_value_new_user_text_nocopy (ds_steal_cstr (s)));
136 }
137
138 static void
139 draw_line (struct table *t, enum pivot_border border_idx,
140            enum table_axis axis, int a, int b0, int b1)
141 {
142   if (axis == H)
143     table_hline (t, border_idx, b0, b1, a);
144   else
145     table_vline (t, border_idx, a, b0, b1);
146 }
147
148 /* Fills row or column headings into T.
149
150    This function uses terminology and variable names for column headings, but
151    it also applies to row headings because it uses variables for the
152    differences, e.g. when for column headings it would use the H axis, it
153    instead uses 'h', which is set to H for column headings and V for row
154    headings.  */
155 static void
156 compose_headings (struct table *t,
157                   const struct pivot_axis *h_axis, enum table_axis h,
158                   const struct pivot_axis *v_axis,
159                   enum pivot_border dim_col_horz,
160                   enum pivot_border dim_col_vert,
161                   enum pivot_border cat_col_horz,
162                   enum pivot_border cat_col_vert,
163                   const size_t *column_enumeration, size_t n_columns,
164                   int label_style_idx,
165                   bool rotate_inner_labels, bool rotate_outer_labels)
166 {
167   const enum table_axis v = !h;
168   const int v_size = h_axis->label_depth;
169   const int h_ofs = v_axis->label_depth;
170
171   if (!h_axis->n_dimensions || !n_columns || !v_size)
172     return;
173
174   const int stride = MAX (1, h_axis->n_dimensions);
175
176   /* Below, we're going to iterate through the dimensions.  Each dimension
177      occupies one or more rows in the heading.  'top_row' is the top row of
178      these (and 'top_row + d->label_depth - 1' is the bottom row). */
179   int top_row = 0;
180
181   /* We're going to iterate through dimensions and the rows that label them
182      from top to bottom (from outer to inner dimensions).  As we move downward,
183      we start drawing vertical rules to separate categories and groups.  After
184      we start drawing a vertical rule in a particular horizontal position, it
185      continues until the bottom of the heading.  vrules[pos] indicates whether,
186      in our current row, we have already started drawing a vertical rule in
187      horizontal position 'pos'.  (There are n_columns + 1 horizontal positions.
188      We allocate all of them for convenience below but only the inner n_columns
189      - 1 of them really matter.)
190
191      Here's an example that shows how vertical rules continue all the way
192      downward:
193
194      +-----------------------------------------------------+ __
195      |                         bbbb                        |  |
196      +-----------------+-----------------+-----------------+  |dimension "bbbb"
197      |      bbbb1      |      bbbb2      |      bbbb3      | _|
198      +-----------------+-----------------+-----------------+ __
199      |       aaaa      |       aaaa      |       aaaa      |  |
200      +-----+-----+-----+-----+-----+-----+-----+-----+-----+  |dimension "aaaa"
201      |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3| _|
202      +-----+-----+-----+-----+-----+-----+-----+-----+-----+
203
204      ^     ^     ^     ^     ^     ^     ^     ^     ^     ^
205      |     |     |     |     |     |     |     |     |     |
206      0     1     2     3     4     5     6     7     8     9
207      |___________________vrules[] indexes__________________|
208
209      Our data structures are more naturally iterated from bottom to top (inner
210      to outer dimensions).  A previous version of this code actually worked
211      like that, but it didn't draw all of the vertical lines correctly as shown
212      above.  It ended up rendering the above heading much like shown below,
213      which isn't what users expect.  The "aaaa" label really needs to be shown
214      three times for clarity:
215
216      +-----------------------------------------------------+
217      |                         bbbb                        |
218      +-----------------+-----------------+-----------------+
219      |      bbbb1      |      bbbb2      |      bbbb3      |
220      +-----------------+-----------------+-----------------+
221      |                 |       aaaa      |                 |
222      +-----+-----+-----+-----+-----+-----+-----+-----+-----+
223      |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|
224      +-----+-----+-----+-----+-----+-----+-----+-----+-----+
225   */
226   bool *vrules = XCALLOC (n_columns + 1, bool);
227   vrules[0] = vrules[n_columns] = true;
228   for (int dim_index = h_axis->n_dimensions; --dim_index >= 0; )
229     {
230       const struct pivot_dimension *d = h_axis->dimensions[dim_index];
231       if (d->hide_all_labels)
232         continue;
233
234       for (int row_ofs = 0; row_ofs < d->label_depth; row_ofs++)
235         {
236           for (size_t x1 = 0; x1 < n_columns;)
237             {
238               const struct pivot_category *c = find_category (
239                 d, dim_index, column_enumeration + x1 * stride,
240                 d->label_depth - row_ofs - 1);
241               if (!c)
242                 {
243                   x1++;
244                   continue;
245                 }
246
247               size_t x2;
248               for (x2 = x1 + 1; x2 < n_columns; x2++)
249                 {
250                   if (vrules[x2])
251                     break;
252                   const struct pivot_category *c2 = find_category (
253                     d, dim_index, column_enumeration + x2 * stride,
254                     d->label_depth - row_ofs - 1);
255                   if (c != c2)
256                     break;
257                 }
258
259               int y1 = top_row + row_ofs;
260               int y2 = top_row + row_ofs + c->extra_depth + 1;
261               bool is_outer_row = y1 == 0;
262               bool is_inner_row = y2 == v_size;
263               if (pivot_category_is_leaf (c) || c->show_label)
264                 {
265                   int bb[TABLE_N_AXES][2];
266                   bb[h][0] = x1 + h_ofs;
267                   bb[h][1] = x2 + h_ofs - 1;
268                   bb[v][0] = y1;
269                   bb[v][1] = y2 - 1;
270                   bool rotate = ((rotate_inner_labels && is_inner_row)
271                                  || (rotate_outer_labels && is_outer_row));
272                   fill_cell (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
273                              label_style_idx, c->name, rotate);
274
275                   /* Draw all the vertical lines in our running example, other
276                      than the far left and far right ones.  Only the ones that
277                      start in the last row of the heading are drawn with the
278                      "category" style, the rest with the "dimension" style,
279                      e.g. only the # below are category style:
280
281                      +-----------------------------------------------------+
282                      |                         bbbb                        |
283                      +-----------------+-----------------+-----------------+
284                      |      bbbb1      |      bbbb2      |      bbbb3      |
285                      +-----------------+-----------------+-----------------+
286                      |       aaaa      |       aaaa      |       aaaa      |
287                      +-----+-----+-----+-----+-----+-----+-----+-----+-----+
288                      |aaaa1#aaaa2#aaaa3|aaaa1#aaaa2#aaaa3|aaaa1#aaaa2#aaaa3|
289                      +-----+-----+-----+-----+-----+-----+-----+-----+-----+
290                   */
291                   enum pivot_border style
292                     = (y1 == v_size - 1 ? cat_col_vert : dim_col_vert);
293                   if (!vrules[x2])
294                     {
295                       draw_line (t, style, v, x2 + h_ofs, y1, t->n[v] - 1);
296                       vrules[x2] = true;
297                     }
298                   if (!vrules[x1])
299                     {
300                       draw_line (t, style, v, x1 + h_ofs, y1, t->n[v] - 1);
301                       vrules[x1] = true;
302                     }
303                 }
304
305               /* Draws the horizontal lines within a dimension, that is, those
306                  that separate a separating a category (or group) from its
307                  parent group or dimension's label.  Our running example
308                  doesn't have groups but the ==== lines below show the
309                  separators between categories and their dimension label:
310
311                  +-----------------------------------------------------+
312                  |                         bbbb                        |
313                  +=================+=================+=================+
314                  |      bbbb1      |      bbbb2      |      bbbb3      |
315                  +-----------------+-----------------+-----------------+
316                  |       aaaa      |       aaaa      |       aaaa      |
317                  +=====+=====+=====+=====+=====+=====+=====+=====+=====+
318                  |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|
319                  +-----+-----+-----+-----+-----+-----+-----+-----+-----+
320               */
321               if (c->parent && c->parent->show_label)
322                 draw_line (t, cat_col_horz, h, y1, x1 + h_ofs, x2 + h_ofs - 1);
323               x1 = x2;
324             }
325         }
326
327       if (d->root->show_label_in_corner && h_ofs > 0)
328         {
329           int bb[TABLE_N_AXES][2];
330           bb[h][0] = 0;
331           bb[h][1] = h_ofs - 1;
332           bb[v][0] = top_row;
333           bb[v][1] = top_row + d->label_depth - 1;
334           fill_cell (t, bb[H][0], bb[V][0], bb[H][1], bb[V][1],
335                      PIVOT_AREA_CORNER, d->root->name, false);
336         }
337
338       /* Draw the horizontal line between dimensions, e.g. the ===== line here:
339
340          +-----------------------------------------------------+ __
341          |                         bbbb                        |  |
342          +-----------------+-----------------+-----------------+  |dim "bbbb"
343          |      bbbb1      |      bbbb2      |      bbbb3      | _|
344          +=================+=================+=================+ __
345          |       aaaa      |       aaaa      |       aaaa      |  |
346          +-----+-----+-----+-----+-----+-----+-----+-----+-----+  |dim "aaaa"
347          |aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3|aaaa1|aaaa2|aaaa3| _|
348          +-----+-----+-----+-----+-----+-----+-----+-----+-----+
349       */
350       if (dim_index != h_axis->n_dimensions - 1)
351         draw_line (t, dim_col_horz, h, top_row, h_ofs, t->n[h] - 1);
352       top_row += d->label_depth;
353     }
354   free (vrules);
355 }
356
357 static struct table *
358 create_aux_table (const struct pivot_table *pt, int nc, int nr,
359                   int style_idx)
360 {
361   struct table *table = table_create (nc, nr, 0, 0, 0, 0);
362   table->styles[style_idx] = table_area_style_override (
363       table->container, &pt->look->areas[style_idx], NULL, NULL, false);
364   return table;
365 }
366
367
368 static void
369 add_references (const struct pivot_table *pt, const struct table *table,
370                 bool *refs, size_t *n_refs)
371 {
372   if (!table)
373     return;
374
375   for (int y = 0; y < table->n[V]; y++)
376     for (int x = 0; x < table->n[H]; )
377       {
378         struct table_cell cell;
379         table_get_cell (table, x, y, &cell);
380
381         if (x == cell.d[H][0] && y == cell.d[V][0])
382           {
383             const struct pivot_value_ex *ex = pivot_value_ex (cell.value);
384             for (size_t i = 0; i < ex->n_footnotes; i++)
385               {
386                 size_t idx = ex->footnote_indexes[i];
387                 assert (idx < pt->n_footnotes);
388
389                 if (!refs[idx] && pt->footnotes[idx]->show)
390                   {
391                     refs[idx] = true;
392                     (*n_refs)++;
393                   }
394               }
395           }
396
397         x = cell.d[TABLE_HORZ][1];
398       }
399 }
400
401 static struct pivot_footnote **
402 collect_footnotes (const struct pivot_table *pt,
403                    const struct table *title,
404                    const struct table *layers,
405                    const struct table *body,
406                    const struct table *caption,
407                    size_t *n_footnotesp)
408 {
409   if (!pt->n_footnotes)
410     {
411       *n_footnotesp = 0;
412       return NULL;
413     }
414
415   bool *refs = XCALLOC (pt->n_footnotes, bool);
416   size_t n_refs = 0;
417   add_references (pt, title, refs, &n_refs);
418   add_references (pt, layers, refs, &n_refs);
419   add_references (pt, body, refs, &n_refs);
420   add_references (pt, caption, refs, &n_refs);
421
422   struct pivot_footnote **footnotes = xnmalloc (n_refs, sizeof *footnotes);
423   size_t n_footnotes = 0;
424   for (size_t i = 0; i < pt->n_footnotes; i++)
425     if (refs[i])
426       footnotes[n_footnotes++] = pt->footnotes[i];
427   assert (n_footnotes == n_refs);
428
429   free (refs);
430
431   *n_footnotesp = n_footnotes;
432   return footnotes;
433 }
434
435 void
436 pivot_output (const struct pivot_table *pt,
437               const size_t *layer_indexes,
438               bool printing UNUSED,
439               struct table **titlep,
440               struct table **layersp,
441               struct table **bodyp,
442               struct table **captionp,
443               struct table **footnotesp,
444               struct pivot_footnote ***fp, size_t *nfp)
445 {
446   const size_t *pindexes[PIVOT_N_AXES]
447     = { [PIVOT_AXIS_LAYER] = layer_indexes };
448
449   size_t data[TABLE_N_AXES];
450   size_t *column_enumeration = pivot_table_enumerate_axis (
451     pt, PIVOT_AXIS_COLUMN, layer_indexes, pt->look->omit_empty, &data[H]);
452   size_t *row_enumeration = pivot_table_enumerate_axis (
453     pt, PIVOT_AXIS_ROW, layer_indexes, pt->look->omit_empty, &data[V]);
454
455   int stub[TABLE_N_AXES] = {
456     [H] = pt->axes[PIVOT_AXIS_ROW].label_depth,
457     [V] = pt->axes[PIVOT_AXIS_COLUMN].label_depth,
458   };
459   struct table *body = table_create (data[H] + stub[H],
460                                      data[V] + stub[V],
461                                      stub[H], 0, stub[V], 0);
462   for (size_t i = 0; i < PIVOT_N_AREAS; i++)
463     body->styles[i] = table_area_style_override (
464       body->container, &pt->look->areas[i], NULL, NULL, false);
465
466   body->n_borders = PIVOT_N_BORDERS;
467   body->borders = pool_nmalloc (body->container, PIVOT_N_BORDERS,
468                                 sizeof *body->borders);
469   for (size_t i = 0; i < PIVOT_N_BORDERS; i++)
470     {
471       const struct table_border_style *src = &pt->look->borders[i];
472       struct table_border_style *dst = &body->borders[i];
473       *dst = (!printing && pt->show_grid_lines && src->stroke == TABLE_STROKE_NONE
474               ? (struct table_border_style) { .stroke = TABLE_STROKE_DASHED,
475                                               .color = CELL_COLOR_BLACK }
476               : *src);
477     }
478
479   compose_headings (body,
480                     &pt->axes[PIVOT_AXIS_COLUMN], H, &pt->axes[PIVOT_AXIS_ROW],
481                     PIVOT_BORDER_DIM_COL_HORZ,
482                     PIVOT_BORDER_DIM_COL_VERT,
483                     PIVOT_BORDER_CAT_COL_HORZ,
484                     PIVOT_BORDER_CAT_COL_VERT,
485                     column_enumeration, data[H],
486                     PIVOT_AREA_COLUMN_LABELS,
487                     pt->rotate_outer_row_labels, false);
488
489   compose_headings (body,
490                     &pt->axes[PIVOT_AXIS_ROW], V, &pt->axes[PIVOT_AXIS_COLUMN],
491                     PIVOT_BORDER_DIM_ROW_VERT,
492                     PIVOT_BORDER_DIM_ROW_HORZ,
493                     PIVOT_BORDER_CAT_ROW_VERT,
494                     PIVOT_BORDER_CAT_ROW_HORZ,
495                     row_enumeration, data[V],
496                     PIVOT_AREA_ROW_LABELS,
497                     false, pt->rotate_inner_column_labels);
498
499   size_t *dindexes = XCALLOC (pt->n_dimensions, size_t);
500   size_t y = 0;
501   PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_ROW], row_enumeration,
502                               &pt->axes[PIVOT_AXIS_ROW])
503     {
504       size_t x = 0;
505       PIVOT_ENUMERATION_FOR_EACH (pindexes[PIVOT_AXIS_COLUMN],
506                                   column_enumeration,
507                                   &pt->axes[PIVOT_AXIS_COLUMN])
508         {
509           pivot_table_convert_indexes_ptod (pt, pindexes, dindexes);
510           const struct pivot_value *value = pivot_table_get (pt, dindexes);
511           fill_cell (body, x + stub[H], y + stub[V], x + stub[H], y + stub[V],
512                      PIVOT_AREA_DATA, value, false);
513
514           x++;
515         }
516
517       y++;
518     }
519   free (dindexes);
520
521   if ((pt->corner_text || !pt->look->row_labels_in_corner)
522       && stub[H] && stub[V])
523     fill_cell (body, 0, 0, stub[H] - 1, stub[V] - 1,
524                PIVOT_AREA_CORNER, pt->corner_text, false);
525
526   if (body->n[H] && body->n[V])
527     {
528       table_hline (body, PIVOT_BORDER_INNER_TOP, 0, body->n[H] - 1, 0);
529       table_hline (body, PIVOT_BORDER_INNER_BOTTOM, 0, body->n[H] - 1,
530                    body->n[V]);
531       table_vline (body, PIVOT_BORDER_INNER_LEFT, 0, 0, body->n[V] - 1);
532       table_vline (body, PIVOT_BORDER_INNER_RIGHT, body->n[H], 0,
533                    body->n[V] - 1);
534
535       if (stub[V])
536         table_hline (body, PIVOT_BORDER_DATA_TOP, 0, body->n[H] - 1, stub[V]);
537       if (stub[H])
538         table_vline (body, PIVOT_BORDER_DATA_LEFT, stub[H], 0, body->n[V] - 1);
539
540     }
541   free (column_enumeration);
542   free (row_enumeration);
543
544   /* Title. */
545   struct table *title;
546   if (pt->title && pt->show_title && titlep)
547     {
548       title = create_aux_table (pt, 1, 1, PIVOT_AREA_TITLE);
549       fill_cell (title, 0, 0, 0, 0, PIVOT_AREA_TITLE, pt->title, false);
550     }
551   else
552     title = NULL;
553
554   /* Layers. */
555   const struct pivot_axis *layer_axis = &pt->axes[PIVOT_AXIS_LAYER];
556   int n_layers = 0;
557   if (layersp)
558     for (size_t i = 0; i < layer_axis->n_dimensions; i++)
559       {
560         const struct pivot_dimension *d = layer_axis->dimensions[i];
561         if (d->n_leaves)
562           n_layers++;
563       }
564
565   struct table *layers;
566   if (n_layers > 0)
567     {
568       layers = create_aux_table (pt, 1, n_layers, PIVOT_AREA_LAYERS);
569       size_t y = n_layers - 1;
570       for (size_t i = 0; i < layer_axis->n_dimensions; i++)
571         {
572           const struct pivot_dimension *d = layer_axis->dimensions[i];
573           if (!d->n_leaves)
574             continue;
575
576           struct string s = DS_EMPTY_INITIALIZER;
577           pivot_value_format (d->data_leaves[layer_indexes[i]]->name, pt, &s);
578           fill_cell_owned (layers, 0, y, 0, y, PIVOT_AREA_LAYERS, &s, false);
579           y--;
580         }
581     }
582   else
583     layers = NULL;
584
585   /* Caption. */
586   struct table *caption;
587   if (pt->caption && pt->show_caption && captionp)
588     {
589       caption = create_aux_table (pt, 1, 1, PIVOT_AREA_CAPTION);
590       fill_cell (caption, 0, 0, 0, 0, PIVOT_AREA_CAPTION, pt->caption, false);
591     }
592   else
593     caption = NULL;
594
595   /* Footnotes. */
596   size_t nf;
597   struct pivot_footnote **f = collect_footnotes (pt, title, layers, body,
598                                                  caption, &nf);
599   struct table *footnotes;
600   if (nf && footnotesp)
601     {
602       footnotes = create_aux_table (pt, 1, nf, PIVOT_AREA_FOOTER);
603
604       for (size_t i = 0; i < nf; i++)
605         {
606           struct string s = DS_EMPTY_INITIALIZER;
607           pivot_footnote_format_marker (f[i], pt, &s);
608           ds_put_cstr (&s, ". ");
609           pivot_value_format (f[i]->content, pt, &s);
610           fill_cell_owned (footnotes, 0, i, 0, i, PIVOT_AREA_FOOTER, &s,
611                            false);
612         }
613     }
614   else
615     footnotes = NULL;
616
617   *titlep = title;
618   if (layersp)
619     *layersp = layers;
620   *bodyp = body;
621   if (captionp)
622     *captionp = caption;
623   if (footnotesp)
624     *footnotesp = footnotes;
625   if (fp)
626     {
627       *fp = f;
628       *nfp = nf;
629     }
630   else
631     free (f);
632 }
633
634 void
635 pivot_table_submit (struct pivot_table *pt)
636 {
637   output_item_submit (table_item_create (pt));
638 }