+static void
+add_weight (double dst[N_CTWS], const double src[N_CTWS])
+{
+ for (enum ctables_weighting wt = 0; wt < N_CTWS; wt++)
+ dst[wt] += src[wt];
+}
+
+static void
+ctables_cell_add__ (struct ctables_section *s, const struct ccase *c,
+ const struct ctables_category **cats[PIVOT_N_AXES],
+ bool is_included, double weight[N_CTWS])
+{
+ struct ctables_cell *cell = ctables_cell_insert__ (s, c, cats);
+ const struct ctables_nest *ss = s->nests[s->table->summary_axis];
+
+ const struct ctables_summary_spec_set *specs = &ss->specs[cell->sv];
+ const union value *value = case_data (c, specs->var);
+ bool is_missing = var_is_value_missing (specs->var, value);
+ bool is_scale_missing
+ = is_missing || (specs->is_scale && is_listwise_missing (specs, c));
+
+ for (size_t i = 0; i < specs->n; i++)
+ ctables_summary_add (&cell->summaries[i], &specs->specs[i], value,
+ is_scale_missing, is_included,
+ weight[specs->specs[i].weighting]);
+ for (enum ctables_area_type at = 0; at < N_CTATS; at++)
+ if (!(cell->omit_areas && (1u << at)))
+ {
+ struct ctables_area *a = cell->areas[at];
+
+ add_weight (a->total, weight);
+ if (is_included)
+ add_weight (a->count, weight);
+ if (!is_missing)
+ {
+ add_weight (a->valid, weight);
+
+ if (!is_scale_missing)
+ for (size_t i = 0; i < s->table->n_sum_vars; i++)
+ {
+ const struct variable *var = s->table->sum_vars[i];
+ double addend = case_num (c, var);
+ if (!var_is_num_missing (var, addend))
+ for (enum ctables_weighting wt = 0; wt < N_CTWS; wt++)
+ a->sums[i].sum[wt] += addend * weight[wt];
+ }
+ }
+ }
+}
+
+static void
+recurse_totals (struct ctables_section *s, const struct ccase *c,
+ const struct ctables_category **cats[PIVOT_N_AXES],
+ bool is_included, double weight[N_CTWS],
+ 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 = s->nests[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 (
+ s->table->categories[var_get_dict_index (var)]);
+ if (total)
+ {
+ const struct ctables_category *save = cats[a][i];
+ cats[a][i] = total;
+ ctables_cell_add__ (s, c, cats, is_included, weight);
+ recurse_totals (s, c, cats, is_included, weight, a, i + 1);
+ cats[a][i] = save;
+ }
+ }
+ start_nest = 0;
+ }
+}
+
+static void
+recurse_subtotals (struct ctables_section *s, const struct ccase *c,
+ const struct ctables_category **cats[PIVOT_N_AXES],
+ bool is_included, double weight[N_CTWS],
+ 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 = s->nests[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__ (s, c, cats, is_included, weight);
+ recurse_subtotals (s, c, cats, is_included, weight, a, i + 1);
+ cats[a][i] = save;
+ }
+ }
+ start_nest = 0;
+ }
+}
+
+static void
+ctables_cell_insert (struct ctables_section *s, const struct ccase *c,
+ double weight[N_CTWS])
+{
+ const struct ctables_category *layer_cats[s->nests[PIVOT_AXIS_LAYER]->n];
+ const struct ctables_category *row_cats[s->nests[PIVOT_AXIS_ROW]->n];
+ const struct ctables_category *column_cats[s->nests[PIVOT_AXIS_COLUMN]->n];
+ const struct ctables_category **cats[PIVOT_N_AXES] =
+ {
+ [PIVOT_AXIS_LAYER] = layer_cats,
+ [PIVOT_AXIS_ROW] = row_cats,
+ [PIVOT_AXIS_COLUMN] = column_cats,
+ };
+
+ bool is_included = true;
+
+ for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+ {
+ const struct ctables_nest *nest = s->nests[a];
+ for (size_t i = 0; i < nest->n; i++)
+ if (i != nest->scale_idx)
+ {
+ const struct variable *var = nest->vars[i];
+ const union value *value = case_data (c, var);
+
+ cats[a][i] = ctables_categories_match (
+ s->table->categories[var_get_dict_index (var)], value, var);
+ if (!cats[a][i])
+ {
+ if (i != nest->summary_idx)
+ return;
+
+ if (!var_is_value_missing (var, value))
+ return;
+
+ static const struct ctables_category cct_excluded_missing = {
+ .type = CCT_EXCLUDED_MISSING,
+ .hide = true,
+ };
+ cats[a][i] = &cct_excluded_missing;
+ is_included = false;
+ }
+ }
+ }
+
+ if (is_included)
+ for (enum pivot_axis_type a = 0; a < PIVOT_N_AXES; a++)
+ {
+ const struct ctables_nest *nest = s->nests[a];
+ for (size_t i = 0; i < nest->n; i++)
+ if (i != nest->scale_idx)
+ {
+ const struct variable *var = nest->vars[i];
+ const union value *value = case_data (c, var);
+ ctables_add_occurrence (var, value, &s->occurrences[a][i]);
+ }
+ }
+
+ ctables_cell_add__ (s, c, cats, is_included, weight);
+ recurse_totals (s, c, cats, is_included, weight, 0, 0);
+ recurse_subtotals (s, c, cats, is_included, weight, 0, 0);
+}
+\f
+struct ctables_value
+ {
+ struct hmap_node node;
+ union value value;
+ int leaf;
+ };
+
+static struct ctables_value *
+ctables_value_find__ (struct ctables_table *t, const union value *value,
+ int width, unsigned int hash)
+{
+ struct ctables_value *clv;
+ HMAP_FOR_EACH_WITH_HASH (clv, struct ctables_value, node,
+ hash, &t->clabels_values_map)
+ if (value_equal (value, &clv->value, width))
+ return clv;
+ return NULL;
+}
+
+static void
+ctables_value_insert (struct ctables_table *t, const union value *value,
+ int width)
+{
+ unsigned int hash = value_hash (value, width, 0);
+ struct ctables_value *clv = ctables_value_find__ (t, value, width, hash);
+ if (!clv)
+ {
+ clv = xmalloc (sizeof *clv);
+ value_clone (&clv->value, value, width);
+ hmap_insert (&t->clabels_values_map, &clv->node, hash);
+ }
+}
+
+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 int
+compare_ctables_values_3way (const void *a_, const void *b_, const void *width_)
+{
+ const struct ctables_value *const *ap = a_;
+ const struct ctables_value *const *bp = b_;
+ const struct ctables_value *a = *ap;
+ const struct ctables_value *b = *bp;
+ const int *width = width_;
+ return value_compare_3way (&a->value, &b->value, *width);
+}
+
+static void
+ctables_sort_clabels_values (struct ctables_table *t)
+{
+ const struct variable *v0 = t->clabels_example;
+ int width = var_get_width (v0);
+
+ struct ctables_categories *c0 = t->categories[var_get_dict_index (v0)];
+ if (c0->show_empty)
+ {
+ const struct val_labs *val_labs = var_get_value_labels (v0);
+ for (const struct val_lab *vl = val_labs_first (val_labs); vl;
+ vl = val_labs_next (val_labs, vl))
+ if (ctables_categories_match (c0, &vl->value, v0))
+ ctables_value_insert (t, &vl->value, width);
+ }
+
+ size_t n = hmap_count (&t->clabels_values_map);
+ t->clabels_values = xnmalloc (n, sizeof *t->clabels_values);
+
+ struct ctables_value *clv;
+ size_t i = 0;
+ HMAP_FOR_EACH (clv, struct ctables_value, node, &t->clabels_values_map)
+ t->clabels_values[i++] = clv;
+ t->n_clabels_values = n;
+ assert (i == n);
+
+ sort (t->clabels_values, n, sizeof *t->clabels_values,
+ compare_ctables_values_3way, &width);
+
+ for (size_t i = 0; i < n; i++)
+ t->clabels_values[i]->leaf = i;
+}
+\f
+struct ctables
+ {
+ const struct dictionary *dict;
+ struct pivot_table_look *look;
+
+ /* For CTEF_* formats. */
+ struct fmt_settings ctables_formats;