+static struct ctables_value *
+ctables_value_find (struct ctables_table *t,
+ const union value *value, int width)
+{
+ return ctables_value_find__ (t, value, width,
+ value_hash (value, width, 0));
+}
+
+static void
+ctables_table_output (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));
+
+ bool summary_dimension = (t->summary_axis != t->slabels_axis
+ || (!t->slabels_visible
+ && t->summary_specs.n > 1));
+ if (summary_dimension)
+ {
+ struct pivot_dimension *d = pivot_dimension_create (
+ pt, t->slabels_axis, N_("Statistics"));
+ const struct ctables_summary_spec_set *specs = &t->summary_specs;
+ if (!t->slabels_visible)
+ d->hide_all_labels = true;
+ for (size_t i = 0; i < specs->n; i++)
+ pivot_category_create_leaf (
+ d->root, pivot_value_new_text (specs->specs[i].label));
+ }
+
+ bool categories_dimension = t->clabels_example != NULL;
+ if (categories_dimension)
+ {
+ struct pivot_dimension *d = pivot_dimension_create (
+ pt, t->label_axis[t->clabels_from_axis],
+ t->clabels_from_axis == PIVOT_AXIS_ROW
+ ? N_("Row Categories")
+ : N_("Column Categories"));
+ const struct variable *var = t->clabels_example;
+ const struct ctables_categories *c = t->categories[var_get_dict_index (var)];
+ for (size_t i = 0; i < t->n_clabels_values; i++)
+ {
+ const struct ctables_value *value = t->clabels_values[i];
+ const struct ctables_category *cat = ctables_categories_match (c, &value->value, var);
+ assert (cat != NULL);
+ pivot_category_create_leaf (d->root, ctables_category_create_label (
+ cat, t->clabels_example, &value->value));
+ }
+ }
+
+ 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);
+ size_t n_sorted = 0;
+
+ struct ctables_cell *cell;
+ HMAP_FOR_EACH (cell, struct ctables_cell, node, &t->cells)
+ if (!cell->hide)
+ sorted[n_sorted++] = cell;
+ assert (n_sorted <= t->cells.count);
+
+ struct ctables_cell_sort_aux aux = { .t = t, .a = a };
+ sort (sorted, n_sorted, 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;
+
+ /* Pivot categories:
+
+ - variable label for nest->vars[0], if vlabel != CTVL_NONE
+ - category for nest->vars[0], if nest->scale_idx != 0
+ - variable label for nest->vars[1], if vlabel != CTVL_NONE
+ - category for nest->vars[1], if nest->scale_idx != 1
+ ...
+ - variable label for nest->vars[n - 1], if vlabel != CTVL_NONE
+ - category for nest->vars[n - 1], if t->label_axis[a] == a && nest->scale_idx != n - 1.
+ - summary function, if 'a == t->slabels_axis && a ==
+ t->summary_axis'.
+
+ Additional dimensions:
+
+ - If 'a == t->slabels_axis && a != t->summary_axis', add a summary
+ dimension.
+ - If 't->label_axis[b] == a' for some 'b != a', add a category
+ dimension to 'a'.
+ */
+
+ struct ctables_level
+ {
+ enum ctables_level_type