+ int leaf = cell->axes[a].leaf;
+ if (a == t->summary_axis)
+ leaf += j;
+ dindexes[n_dindexes++] = leaf;
+ }
+
+ double d = ctables_summary_value (cell, &cell->summaries[j], &specs->specs[j]);
+ struct pivot_value *value = pivot_value_new_number (d);
+ value->numeric.format = specs->specs[j].format;
+ pivot_table_put (pt, dindexes, n_dindexes, value);
+ }
+ }
+
+ pivot_table_submit (pt);
+}
+
+static void
+ctables_table_output_different_axis (struct ctables *ct, struct ctables_table *t)
+{
+ struct pivot_table *pt = pivot_table_create__ (
+ (t->title
+ ? pivot_value_new_user_text (t->title, SIZE_MAX)
+ : pivot_value_new_text (N_("Custom Tables"))),
+ "Custom Tables");
+ if (t->caption)
+ pivot_table_set_caption (
+ pt, pivot_value_new_user_text (t->caption, SIZE_MAX));
+ if (t->corner)
+ pivot_table_set_caption (
+ pt, pivot_value_new_user_text (t->corner, SIZE_MAX));
+
+ pivot_table_set_look (pt, ct->look);
+ struct pivot_dimension *d[PIVOT_N_AXES];
+ for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+ {
+ static const char *names[] = {
+ [PIVOT_AXIS_ROW] = N_("Rows"),
+ [PIVOT_AXIS_COLUMN] = N_("Columns"),
+ [PIVOT_AXIS_LAYER] = N_("Layers"),
+ };
+ d[a] = (t->axes[a] || a == t->summary_axis
+ ? pivot_dimension_create (pt, a, names[a])
+ : NULL);
+ if (!d[a])
+ continue;
+
+ assert (t->axes[a]);
+
+ struct ctables_cell **sorted = xnmalloc (t->cells.count, sizeof *sorted);
+
+ struct ctables_cell *cell;
+ size_t n = 0;
+ HMAP_FOR_EACH (cell, struct ctables_cell, node, &t->cells)
+ if (!cell->hide)
+ sorted[n++] = cell;
+ assert (n <= t->cells.count);
+
+ struct ctables_cell_sort_aux aux = { .t = t, .a = a };
+ sort (sorted, n, sizeof *sorted, ctables_cell_compare_3way, &aux);
+
+ size_t max_depth = 0;
+ for (size_t j = 0; j < t->stacks[a].n; j++)
+ if (t->stacks[a].nests[j].n > max_depth)
+ max_depth = t->stacks[a].nests[j].n;
+
+ struct pivot_category **groups = xnmalloc (max_depth, sizeof *groups);
+ struct pivot_category *top = NULL;
+ int prev_leaf = 0;
+ for (size_t j = 0; j < n; j++)
+ {
+ struct ctables_cell *cell = sorted[j];
+ const struct ctables_nest *nest = &t->stacks[a].nests[cell->axes[a].stack_idx];
+
+ /* Pivot categories:
+
+ - variable label for nest->vars[0], if vlabel != CTVL_NONE
+ - category for nest->vars[0]
+ - variable label for nest->vars[1], if vlabel != CTVL_NONE
+ - category for nest->vars[1]
+ ...
+ - variable label for nest->vars[nest->n - 1], if vlabel != CTVL_NONE
+ - category for nest->vars[nest->n - 1], unless axis A's
+ labels are moved to another axis
+ - summary function, if 'a == t->summary_axis && t->slabels_axis ==
+ t->summary_axis'
+
+ Additional dimensions:
+
+ - If 't->slabels_axis != t->summary_axis', add a summary dimension
+ to t->slabels_axis.
+ - If 't->row_labels ==
+
+ */
+
+ size_t n_common = 0;
+ bool new_subtable = false;
+ if (j > 0)
+ {
+ struct ctables_cell *prev = sorted[j - 1];
+ if (prev->axes[a].stack_idx == cell->axes[a].stack_idx)
+ {
+ for (; n_common < nest->n; n_common++)
+ if (n_common != nest->scale_idx
+ && (prev->axes[a].cvs[n_common].category
+ != cell->axes[a].cvs[n_common].category
+ || !value_equal (&prev->axes[a].cvs[n_common].value,
+ &cell->axes[a].cvs[n_common].value,
+ var_get_type (nest->vars[n_common]))))
+ break;
+ }
+ else
+ new_subtable = true;
+ }
+ else
+ new_subtable = true;
+
+ if (new_subtable)
+ {
+ enum ctables_vlabel vlabel = ct->vlabels[var_get_dict_index (nest->vars[0])];
+ top = d[a]->root;
+ if (vlabel != CTVL_NONE)
+ top = pivot_category_create_group__ (
+ top, pivot_value_new_variable (nest->vars[0]));
+ }
+ if (n_common == nest->n)
+ {
+ cell->axes[a].leaf = prev_leaf;
+ continue;
+ }
+
+ for (size_t k = n_common; k < nest->n; k++)
+ {
+ struct pivot_category *parent = k > 0 ? groups[k - 1] : top;
+
+ struct pivot_value *label
+ = (k == nest->scale_idx ? NULL
+ : (cell->axes[a].cvs[k].category->type == CCT_TOTAL
+ || cell->axes[a].cvs[k].category->type == CCT_SUBTOTAL
+ || cell->axes[a].cvs[k].category->type == CCT_HSUBTOTAL)
+ ? pivot_value_new_user_text (cell->axes[a].cvs[k].category->total_label,
+ SIZE_MAX)
+ : pivot_value_new_var_value (nest->vars[k],
+ &cell->axes[a].cvs[k].value));
+ if (k == nest->n - 1)
+ {
+ if (a == t->slabels_axis)
+ {
+ if (label)
+ parent = pivot_category_create_group__ (parent, label);
+ const struct ctables_summary_spec_set *specs = &t->summary_specs;
+ for (size_t m = 0; m < specs->n; m++)
+ {
+ int leaf = pivot_category_create_leaf (
+ parent, pivot_value_new_text (specs->specs[m].label));
+ if (m == 0)
+ prev_leaf = leaf;
+ }
+ }
+ else
+ {
+ prev_leaf = pivot_category_create_leaf (parent, label ? label : pivot_value_new_user_text ("text", SIZE_MAX));
+ }
+ break;
+ }
+
+ if (label)
+ parent = pivot_category_create_group__ (parent, label);
+
+ enum ctables_vlabel vlabel = ct->vlabels[var_get_dict_index (nest->vars[k + 1])];
+ if (vlabel != CTVL_NONE)
+ parent = pivot_category_create_group__ (
+ parent, pivot_value_new_variable (nest->vars[k + 1]));
+ groups[k] = parent;
+ }
+
+ cell->axes[a].leaf = prev_leaf;
+ }
+ free (sorted);
+ free (groups);
+ }
+ pivot_table_submit (pt);
+}
+
+
+static void
+ctables_prepare_table (struct ctables_table *t)
+{
+ for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+ if (t->axes[a])
+ {
+ t->stacks[a] = enumerate_fts (a, t->axes[a]);
+
+ for (size_t j = 0; j < t->stacks[a].n; j++)
+ {
+ struct ctables_nest *nest = &t->stacks[a].nests[j];
+ for (enum ctables_domain_type dt = 0; dt < N_CTDTS; dt++)
+ {
+ nest->domains[dt] = xmalloc (nest->n * sizeof *nest->domains[dt]);
+ nest->n_domains[dt] = 0;
+
+ for (size_t k = 0; k < nest->n; k++)