+struct ctables_cell_sort_aux
+ {
+ const struct ctables_table *t;
+ enum pivot_axis_type a;
+ };
+
+static int
+ctables_cell_compare_3way (const void *a_, const void *b_, const void *aux_)
+{
+ const struct ctables_cell_sort_aux *aux = aux_;
+ struct ctables_cell *const *ap = a_;
+ struct ctables_cell *const *bp = b_;
+ const struct ctables_cell *a = *ap;
+ const struct ctables_cell *b = *bp;
+
+ size_t a_idx = a->axes[aux->a].nest_idx;
+ size_t b_idx = b->axes[aux->a].nest_idx;
+ if (a_idx != b_idx)
+ return a_idx < b_idx ? -1 : 1;
+
+ const struct ctables_nest *nest = &aux->t->stacks[aux->a].nests[a_idx];
+ for (size_t i = 0; i < nest->n; i++)
+ if (i != nest->scale_idx)
+ {
+ const struct variable *var = nest->vars[i];
+ const struct ctables_cell_value *a_cv = &a->axes[aux->a].cvs[i];
+ const struct ctables_cell_value *b_cv = &b->axes[aux->a].cvs[i];
+ if (a_cv->category != b_cv->category)
+ return a_cv->category > b_cv->category ? 1 : -1;
+
+ const union value *a_val = &a_cv->value;
+ const union value *b_val = &b_cv->value;
+ switch (a_cv->category->type)
+ {
+ case CCT_NUMBER:
+ case CCT_STRING:
+ case CCT_SUBTOTAL:
+ case CCT_HSUBTOTAL:
+ case CCT_TOTAL:
+ /* Must be equal. */
+ continue;
+
+ case CCT_RANGE:
+ case CCT_MISSING:
+ case CCT_OTHERNM:
+ {
+ int cmp = value_compare_3way (a_val, b_val, var_get_width (var));
+ if (cmp)
+ return cmp;
+ }
+ break;
+
+ case CCT_VALUE:
+ {
+ int cmp = value_compare_3way (a_val, b_val, var_get_width (var));
+ if (cmp)
+ return a_cv->category->sort_ascending ? cmp : -cmp;
+ }
+ break;
+
+ case CCT_LABEL:
+ {
+ const char *a_label = var_lookup_value_label (var, a_val);
+ const char *b_label = var_lookup_value_label (var, b_val);
+ int cmp = (a_label
+ ? (b_label ? strcmp (a_label, b_label) : 1)
+ : (b_label ? -1 : value_compare_3way (
+ a_val, b_val, var_get_width (var))));
+ if (cmp)
+ return a_cv->category->sort_ascending ? cmp : -cmp;
+ }
+ break;
+
+ case CCT_FUNCTION:
+ NOT_REACHED ();
+ }
+ }
+ return 0;
+}
+
+/* Algorithm:
+
+ For each row:
+ For each ctables_table:
+ For each combination of row vars:
+ For each combination of column vars:
+ For each combination of layer vars:
+ Add entry
+ Make a table of row values:
+ Sort entries by row values
+ Assign a 0-based index to each actual value
+ Construct a dimension
+ Make a table of column values
+ Make a table of layer values
+ For each entry:
+ Fill the table entry using the indexes from before.
+ */
+
+static struct ctables_domain *
+ctables_domain_insert (struct ctables_table *t, struct ctables_cell *cell,
+ enum ctables_domain_type domain)
+{
+ size_t hash = 0;
+ for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+ {
+ size_t idx = cell->axes[a].nest_idx;
+ const struct ctables_nest *nest = &t->stacks[a].nests[idx];
+ hash = hash_int (idx, hash);
+ for (size_t i = 0; i < nest->n_domains[domain]; i++)
+ {
+ size_t v_idx = nest->domains[domain][i];
+ hash = value_hash (&cell->axes[a].cvs[v_idx].value,
+ var_get_width (nest->vars[v_idx]), hash);
+ }
+ }
+
+ struct ctables_domain *d;
+ HMAP_FOR_EACH_WITH_HASH (d, struct ctables_domain, node, hash, &t->domains[domain])
+ {
+ const struct ctables_cell *df = d->example;
+ for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+ {
+ size_t idx = cell->axes[a].nest_idx;
+ if (idx != df->axes[a].nest_idx)
+ goto not_equal;
+
+ const struct ctables_nest *nest = &t->stacks[a].nests[idx];
+ for (size_t i = 0; i < nest->n_domains[domain]; i++)
+ {
+ size_t v_idx = nest->domains[domain][i];
+ if (!value_equal (&df->axes[a].cvs[v_idx].value,
+ &cell->axes[a].cvs[v_idx].value,
+ var_get_width (nest->vars[v_idx])))
+ goto not_equal;
+ }
+ }
+ return d;
+
+ not_equal: ;
+ }
+
+ d = xmalloc (sizeof *d);
+ *d = (struct ctables_domain) { .example = cell };
+ hmap_insert (&t->domains[domain], &d->node, hash);
+ return d;
+}
+
+static const struct ctables_category *
+ctables_categories_match (const struct ctables_categories *c,
+ const union value *v, const struct variable *var)
+{
+ const struct ctables_category *othernm = NULL;
+ for (size_t i = c->n_cats; i-- > 0; )
+ {
+ const struct ctables_category *cat = &c->cats[i];
+ switch (cat->type)
+ {
+ case CCT_NUMBER:
+ if (cat->number == v->f)
+ return cat;
+ break;
+
+ case CCT_STRING:
+ NOT_REACHED ();
+
+ case CCT_RANGE:
+ if ((cat->range[0] == -DBL_MAX || v->f >= cat->range[0])
+ && (cat->range[1] == DBL_MAX || v->f <= cat->range[1]))
+ return cat;
+ break;
+
+ case CCT_MISSING:
+ if (var_is_value_missing (var, v))
+ return cat;
+ break;
+
+ case CCT_OTHERNM:
+ if (!othernm)
+ othernm = cat;
+ break;
+
+ case CCT_SUBTOTAL:
+ case CCT_HSUBTOTAL:
+ case CCT_TOTAL:
+ break;
+
+ case CCT_VALUE:
+ case CCT_LABEL:
+ case CCT_FUNCTION:
+ return (cat->include_missing || !var_is_value_missing (var, v) ? cat
+ : NULL);
+ }
+ }
+
+ return var_is_value_missing (var, v) ? NULL : othernm;
+}
+
+static const struct ctables_category *
+ctables_categories_total (const struct ctables_categories *c)
+{
+ const struct ctables_category *first = &c->cats[0];
+ const struct ctables_category *last = &c->cats[c->n_cats - 1];
+ return (first->type == CCT_TOTAL ? first
+ : last->type == CCT_TOTAL ? last
+ : NULL);
+}
+
+static struct ctables_cell *
+ctables_cell_insert__ (struct ctables_table *t, const struct ccase *c,
+ size_t ix[PIVOT_N_AXES],
+ const struct ctables_category *cats[PIVOT_N_AXES][10])
+{
+ const struct ctables_nest *ss = &t->stacks[t->summary_axis].nests[ix[t->summary_axis]];
+
+ size_t hash = 0;
+ enum ctables_summary_variant sv = CSV_CELL;
+ for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+ {
+ const struct ctables_nest *nest = &t->stacks[a].nests[ix[a]];
+ hash = hash_int (ix[a], hash);
+ for (size_t i = 0; i < nest->n; i++)
+ if (i != nest->scale_idx)
+ {
+ hash = hash_pointer (cats[a][i], hash);
+ if (cats[a][i]->type != CCT_TOTAL
+ && cats[a][i]->type != CCT_SUBTOTAL
+ && cats[a][i]->type != CCT_HSUBTOTAL)
+ hash = value_hash (case_data (c, nest->vars[i]),
+ var_get_width (nest->vars[i]), hash);
+ else
+ sv = CSV_TOTAL;
+ }
+ }
+
+ struct ctables_cell *cell;
+ HMAP_FOR_EACH_WITH_HASH (cell, struct ctables_cell, node, hash, &t->cells)
+ {
+ for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+ {
+ const struct ctables_nest *nest = &t->stacks[a].nests[ix[a]];
+ if (cell->axes[a].nest_idx != ix[a])
+ goto not_equal;
+ for (size_t i = 0; i < nest->n; i++)
+ if (i != nest->scale_idx
+ && (cats[a][i] != cell->axes[a].cvs[i].category
+ || (cats[a][i]->type != CCT_TOTAL
+ && cats[a][i]->type != CCT_SUBTOTAL
+ && cats[a][i]->type != CCT_HSUBTOTAL
+ && !value_equal (case_data (c, nest->vars[i]),
+ &cell->axes[a].cvs[i].value,
+ var_get_width (nest->vars[i])))))
+ goto not_equal;
+ }
+
+ return cell;
+
+ not_equal: ;
+ }
+
+ cell = xmalloc (sizeof *cell);
+ cell->hide = false;
+ cell->sv = sv;
+ cell->contributes_to_domains = true;
+ for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+ {
+ const struct ctables_nest *nest = &t->stacks[a].nests[ix[a]];
+ cell->axes[a].nest_idx = ix[a];
+ cell->axes[a].cvs = (nest->n
+ ? xnmalloc (nest->n, sizeof *cell->axes[a].cvs)
+ : NULL);
+ for (size_t i = 0; i < nest->n; i++)
+ {
+ const struct ctables_category *cat = cats[a][i];
+
+ if (i != nest->scale_idx)
+ {
+ const struct ctables_category *subtotal = cat->subtotal;
+ if (subtotal && subtotal->type == CCT_HSUBTOTAL)
+ cell->hide = true;
+
+ if (cat->type == CCT_TOTAL || cat->type == CCT_SUBTOTAL || cat->type == CCT_HSUBTOTAL)
+ cell->contributes_to_domains = false;
+ }
+
+ cell->axes[a].cvs[i].category = cat;
+ value_clone (&cell->axes[a].cvs[i].value, case_data (c, nest->vars[i]),
+ var_get_width (nest->vars[i]));
+ }
+ }
+
+ const struct ctables_summary_spec_set *specs = &ss->specs[cell->sv];
+ cell->summaries = xmalloc (specs->n * sizeof *cell->summaries);
+ for (size_t i = 0; i < specs->n; i++)
+ ctables_summary_init (&cell->summaries[i], &specs->specs[i]);
+ for (enum ctables_domain_type dt = 0; dt < N_CTDTS; dt++)
+ cell->domains[dt] = ctables_domain_insert (t, cell, dt);
+ hmap_insert (&t->cells, &cell->node, hash);
+ return cell;
+}
+
+static void
+ctables_cell_add__ (struct ctables_table *t, const struct ccase *c,
+ size_t ix[PIVOT_N_AXES],
+ const struct ctables_category *cats[PIVOT_N_AXES][10],
+ double d_weight, double e_weight)
+{
+ struct ctables_cell *cell = ctables_cell_insert__ (t, c, ix, cats);
+ const struct ctables_nest *ss = &t->stacks[t->summary_axis].nests[ix[t->summary_axis]];
+
+ const struct ctables_summary_spec_set *specs = &ss->specs[cell->sv];
+ for (size_t i = 0; i < specs->n; i++)
+ ctables_summary_add (&cell->summaries[i], &specs->specs[i], specs->var,
+ case_data (c, specs->var), d_weight, e_weight);
+ if (cell->contributes_to_domains)
+ {
+ for (enum ctables_domain_type dt = 0; dt < N_CTDTS; dt++)
+ {
+ cell->domains[dt]->d_valid += d_weight;
+ cell->domains[dt]->e_valid += e_weight;
+ }
+ }
+}
+
+static void
+recurse_totals (struct ctables_table *t, const struct ccase *c,
+ size_t ix[PIVOT_N_AXES],
+ const struct ctables_category *cats[PIVOT_N_AXES][10],
+ double d_weight, double e_weight,
+ enum pivot_axis_type start_axis, size_t start_nest)
+{
+ for (enum pivot_axis_type a = start_axis; a < PIVOT_N_AXES; a++)
+ {
+ const struct ctables_nest *nest = &t->stacks[a].nests[ix[a]];
+ for (size_t i = start_nest; i < nest->n; i++)
+ {
+ if (i == nest->scale_idx)
+ continue;
+
+ const struct variable *var = nest->vars[i];
+
+ const struct ctables_category *total = ctables_categories_total (
+ t->categories[var_get_dict_index (var)]);
+ if (total)
+ {
+ const struct ctables_category *save = cats[a][i];
+ cats[a][i] = total;
+ ctables_cell_add__ (t, c, ix, cats, d_weight, e_weight);
+ recurse_totals (t, c, ix, cats, d_weight, e_weight, a, i + 1);
+ cats[a][i] = save;
+ }
+ }
+ start_nest = 0;
+ }
+}
+
+static void
+recurse_subtotals (struct ctables_table *t, const struct ccase *c,
+ size_t ix[PIVOT_N_AXES],
+ const struct ctables_category *cats[PIVOT_N_AXES][10],
+ double d_weight, double e_weight,
+ enum pivot_axis_type start_axis, size_t start_nest)
+{
+ for (enum pivot_axis_type a = start_axis; a < PIVOT_N_AXES; a++)
+ {
+ const struct ctables_nest *nest = &t->stacks[a].nests[ix[a]];
+ for (size_t i = start_nest; i < nest->n; i++)
+ {
+ if (i == nest->scale_idx)
+ continue;
+
+ const struct ctables_category *save = cats[a][i];
+ if (save->subtotal)
+ {
+ cats[a][i] = save->subtotal;
+ ctables_cell_add__ (t, c, ix, cats, d_weight, e_weight);
+ recurse_subtotals (t, c, ix, cats, d_weight, e_weight, a, i + 1);
+ cats[a][i] = save;
+ }
+ }
+ start_nest = 0;
+ }
+}
+
+static void
+ctables_cell_insert (struct ctables_table *t,
+ const struct ccase *c,
+ size_t ix[PIVOT_N_AXES],
+ double d_weight, double e_weight)
+{
+ const struct ctables_category *cats[PIVOT_N_AXES][10];
+ for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+ {
+ const struct ctables_nest *nest = &t->stacks[a].nests[ix[a]];
+ for (size_t i = 0; i < nest->n; i++)
+ {
+ if (i == nest->scale_idx)
+ continue;
+
+ const struct variable *var = nest->vars[i];
+ const union value *value = case_data (c, var);
+
+ if (var_is_numeric (var) && value->f == SYSMIS)
+ return;
+
+ cats[a][i] = ctables_categories_match (
+ t->categories[var_get_dict_index (var)], value, var);
+ if (!cats[a][i])
+ return;
+ }
+ }
+
+ ctables_cell_add__ (t, c, ix, cats, d_weight, e_weight);