FREQUENCIES and layered split file has tests that work
[pspp] / src / output / pivot-table.c
index 3698b78559037a955e949a2c56ce7201b03897da..6d1efb882b4748b3a009d6d3e0e64c05b0b1e624 100644 (file)
@@ -24,6 +24,7 @@
 #include <stdlib.h>
 
 #include "data/data-out.h"
+#include "data/dictionary.h"
 #include "data/settings.h"
 #include "data/value.h"
 #include "data/variable.h"
@@ -748,11 +749,11 @@ struct result_class
 /* Formats for most of the result classes. */
 static struct result_class result_classes[] =
   {
-    { PIVOT_RC_INTEGER,      { FMT_F,   40, 0 } },
-    { PIVOT_RC_PERCENT,      { FMT_PCT, 40, 1 } },
-    { PIVOT_RC_CORRELATION,  { FMT_F,   40, 3 } },
-    { PIVOT_RC_SIGNIFICANCE, { FMT_F,   40, 3 } },
-    { PIVOT_RC_RESIDUAL,     { FMT_F,   40, 2 } },
+    { PIVOT_RC_INTEGER,      { .type = FMT_F,   .w = 40, .d = 0 } },
+    { PIVOT_RC_PERCENT,      { .type = FMT_PCT, .w = 40, .d = 1 } },
+    { PIVOT_RC_CORRELATION,  { .type = FMT_F,   .w = 40, .d = 3 } },
+    { PIVOT_RC_SIGNIFICANCE, { .type = FMT_F,   .w = 40, .d = 3 } },
+    { PIVOT_RC_RESIDUAL,     { .type = FMT_F,   .w = 40, .d = 2 } },
     { PIVOT_RC_COUNT,        { 0, 0, 0 } },
     { PIVOT_RC_OTHER,        { 0, 0, 0 } },
   };
@@ -865,20 +866,20 @@ pivot_table_create (const char *title)
 struct pivot_table *
 pivot_table_create__ (struct pivot_value *title, const char *subtype)
 {
-  struct pivot_table *table = xzalloc (sizeof *table);
-  table->ref_cnt = 1;
-  table->show_title = true;
-  table->show_caption = true;
-  table->weight_format = (struct fmt_spec) { FMT_F, 40, 0 };
-  table->title = title;
-  table->subtype = subtype ? pivot_value_new_text (subtype) : NULL;
-  table->command_c = output_get_command_name ();
-  table->look = pivot_table_look_ref (pivot_table_look_get_default ());
-  table->settings = fmt_settings_copy (settings_get_fmt_settings ());
-  table->small = settings_get_small ();
-
-  hmap_init (&table->cells);
-
+  struct pivot_table *table = xmalloc (sizeof *table);
+  *table = (struct pivot_table) {
+    .ref_cnt = 1,
+    .show_title = true,
+    .show_caption = true,
+    .weight_format = (struct fmt_spec) { .type = FMT_F, .w = 40 },
+    .title = title,
+    .subtype = subtype ? pivot_value_new_text (subtype) : NULL,
+    .command_c = xstrdup_if_nonempty (output_get_command_name ()),
+    .look = pivot_table_look_ref (pivot_table_look_get_default ()),
+    .settings = fmt_settings_copy (settings_get_fmt_settings ()),
+    .small = settings_get_small (),
+    .cells = HMAP_INITIALIZER (table->cells),
+  };
   return table;
 }
 
@@ -968,7 +969,7 @@ clone_category (struct pivot_category *old,
     .extra_depth = old->extra_depth,
 
     .subs = (old->n_subs
-             ? xzalloc (old->n_subs * sizeof *new->subs)
+             ? xcalloc (old->n_subs, sizeof *new->subs)
              : NULL),
     .n_subs = old->n_subs,
     .allocated_subs = old->n_subs,
@@ -1006,9 +1007,9 @@ clone_dimension (struct pivot_dimension *old, struct pivot_table *new_pt)
     .axis_type = old->axis_type,
     .level = old->level,
     .top_index = old->top_index,
-    .data_leaves = xzalloc (old->n_leaves * sizeof *new->data_leaves),
-    .presentation_leaves = xzalloc (old->n_leaves
-                                    * sizeof *new->presentation_leaves),
+    .data_leaves = xcalloc (old->n_leaves , sizeof *new->data_leaves),
+    .presentation_leaves = xcalloc (old->n_leaves
+                                    , sizeof *new->presentation_leaves),
     .n_leaves = old->n_leaves,
     .allocated_leaves = old->n_leaves,
     .hide_all_labels = old->hide_all_labels,
@@ -1191,6 +1192,43 @@ pivot_table_is_shared (const struct pivot_table *table)
   return table->ref_cnt > 1;
 }
 
+static void
+pivot_table_set_value__ (struct pivot_value **dstp, struct pivot_value *src)
+{
+  pivot_value_destroy (*dstp);
+  *dstp = src;
+}
+
+/* Changes the title of TABLE to TITLE.  Takes ownership of TITLE. */
+void
+pivot_table_set_title (struct pivot_table *table, struct pivot_value *title)
+{
+  pivot_table_set_value__ (&table->title, title);
+}
+
+/* Changes the subtype of TABLE to SUBTYPE.  Takes ownership of SUBTYPE. */
+void
+pivot_table_set_subtype (struct pivot_table *table, struct pivot_value *subtype)
+{
+  pivot_table_set_value__ (&table->subtype, subtype);
+}
+
+/* Changes the corner text of TABLE to CORNER_TEXT.  Takes ownership of
+   CORNER_TEXT. */
+void
+pivot_table_set_corner_text (struct pivot_table *table,
+                             struct pivot_value *corner_text)
+{
+  pivot_table_set_value__ (&table->corner_text, corner_text);
+}
+
+/* Changes the caption of TABLE to CAPTION.  Takes ownership of CAPTION. */
+void
+pivot_table_set_caption (struct pivot_table *table, struct pivot_value *caption)
+{
+  pivot_table_set_value__ (&table->caption, caption);
+}
+
 /* Swaps axes A and B in TABLE. */
 void
 pivot_table_swap_axes (struct pivot_table *table,
@@ -1406,8 +1444,9 @@ pivot_table_insert_cell (struct pivot_table *table, const size_t *dindexes)
 }
 
 /* Puts VALUE in the cell in TABLE whose indexes are given by the N indexes in
-   DINDEXES.  N must be the number of dimensions in TABLE.  Takes ownership of
-   VALUE.
+   DINDEXES.  The order of the indexes is the same as the order in which the
+   dimensions were created.  N must be the number of dimensions in TABLE.
+   Takes ownership of VALUE.
 
    If VALUE is a numeric value without a specified format, this function checks
    each of the categories designated by DINDEXES[] and takes the format from
@@ -1939,6 +1978,8 @@ compose_headings (const struct pivot_table *pt,
 static void
 free_headings (const struct pivot_axis *axis, char ***headings)
 {
+  if (!headings)
+    return;
   for (size_t i = 0; i < axis->label_depth; i++)
     {
       for (size_t j = 0; j < axis->extent; j++)
@@ -2094,21 +2135,30 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
       size_t *row_enumeration = pivot_table_enumerate_axis (
         table, PIVOT_AXIS_ROW, layer_indexes, table->look->omit_empty, NULL);
 
+      /* Print column headings.
+
+         Ordinarily the test for nonnull 'column_headings' would be
+         unnecessary, because 'column_headings' is null only if the axis's
+         label_depth is 0, but there is a special case for the column axis only
+         in pivot_table_assign_label_depth(). */
       char ***column_headings = compose_headings (
         table, &table->axes[PIVOT_AXIS_COLUMN], column_enumeration);
-      for (size_t y = 0; y < table->axes[PIVOT_AXIS_COLUMN].label_depth; y++)
+      if (column_headings)
         {
-          indent (indentation + 1);
-          for (size_t x = 0; x < table->axes[PIVOT_AXIS_COLUMN].extent; x++)
+          for (size_t y = 0; y < table->axes[PIVOT_AXIS_COLUMN].label_depth; y++)
             {
-              if (x)
-                fputs ("; ", stdout);
-              if (column_headings[y][x])
-                fputs (column_headings[y][x], stdout);
+              indent (indentation + 1);
+              for (size_t x = 0; x < table->axes[PIVOT_AXIS_COLUMN].extent; x++)
+                {
+                  if (x)
+                    fputs ("; ", stdout);
+                  if (column_headings && column_headings[y] && column_headings[y][x])
+                    fputs (column_headings[y][x], stdout);
+                }
+              putchar ('\n');
             }
-          putchar ('\n');
+          free_headings (&table->axes[PIVOT_AXIS_COLUMN], column_headings);
         }
-      free_headings (&table->axes[PIVOT_AXIS_COLUMN], column_headings);
 
       indent (indentation + 1);
       printf ("-----------------------------------------------\n");
@@ -2411,7 +2461,7 @@ pivot_value_format_body (const struct pivot_value *value,
       break;
 
     case PIVOT_VALUE_TEXT:
-      if (value->font_style && value->font_style->markup)
+      if (value->ex && value->ex->font_style && value->ex->font_style->markup)
         get_text_from_markup (value->text.local, out);
       else
         ds_put_cstr (out, value->text.local);
@@ -2440,21 +2490,25 @@ pivot_value_format (const struct pivot_value *value,
 {
   bool numeric = pivot_value_format_body (value, pt, out);
 
-  if (value->n_subscripts)
+  const struct pivot_value_ex *ex = value->ex;
+  if (ex)
     {
-      for (size_t i = 0; i < value->n_subscripts; i++)
-        ds_put_format (out, "%c%s", i ? ',' : '_', value->subscripts[i]);
-    }
+      if (ex->n_subscripts)
+        {
+          for (size_t i = 0; i < ex->n_subscripts; i++)
+            ds_put_format (out, "%c%s", i ? ',' : '_', ex->subscripts[i]);
+        }
 
-  for (size_t i = 0; i < value->n_footnotes; i++)
-    {
-      ds_put_byte (out, '[');
+      for (size_t i = 0; i < ex->n_footnotes; i++)
+        {
+          ds_put_byte (out, '[');
 
-      size_t idx = value->footnote_indexes[i];
-      const struct pivot_footnote *f = pt->footnotes[idx];
-      pivot_footnote_format_marker (f, pt, out);
+          size_t idx = ex->footnote_indexes[i];
+          const struct pivot_footnote *f = pt->footnotes[idx];
+          pivot_footnote_format_marker (f, pt, out);
 
-      ds_put_byte (out, ']');
+          ds_put_byte (out, ']');
+        }
     }
 
   return numeric;
@@ -2489,22 +2543,8 @@ pivot_value_clone (const struct pivot_value *old)
     return NULL;
 
   struct pivot_value *new = xmemdup (old, sizeof *new);
-  if (old->font_style)
-    {
-      new->font_style = xmalloc (sizeof *new->font_style);
-      font_style_copy (NULL, new->font_style, old->font_style);
-    }
-  if (old->cell_style)
-    new->cell_style = xmemdup (old->cell_style, sizeof *new->cell_style);
-  if (old->n_subscripts)
-    {
-      new->subscripts = xnmalloc (old->n_subscripts, sizeof *new->subscripts);
-      for (size_t i = 0; i < old->n_subscripts; i++)
-        new->subscripts[i] = xstrdup (old->subscripts[i]);
-    }
-  if (old->n_footnotes)
-    new->footnote_indexes = xmemdup (
-      old->footnote_indexes, old->n_footnotes * sizeof *new->footnote_indexes);
+  if (old->ex)
+    new->ex = pivot_value_ex_clone (old->ex);
 
   switch (new->type)
     {
@@ -2557,15 +2597,7 @@ pivot_value_destroy (struct pivot_value *value)
 {
   if (value)
     {
-      font_style_uninit (value->font_style);
-      free (value->font_style);
-      free (value->cell_style);
-      free (value->footnote_indexes);
-
-      for (size_t i = 0; i < value->n_subscripts; i++)
-        free (value->subscripts[i]);
-      free (value->subscripts);
-
+      pivot_value_ex_destroy (value->ex);
       switch (value->type)
         {
         case PIVOT_VALUE_NUMERIC:
@@ -2617,12 +2649,10 @@ pivot_value_get_style (struct pivot_value *value,
                        const struct cell_style *base_cell_style,
                        struct table_area_style *area)
 {
-  font_style_copy (NULL, &area->font_style, (value->font_style
-                                             ? value->font_style
-                                             : base_font_style));
-  area->cell_style = *(value->cell_style
-                       ? value->cell_style
-                       : base_cell_style);
+  const struct pivot_value_ex *ex = pivot_value_ex (value);
+  font_style_copy (NULL, &area->font_style,
+                   ex->font_style ? ex->font_style : base_font_style);
+  area->cell_style = *(ex->cell_style ? ex->cell_style : base_cell_style);
 }
 
 /* Copies AREA into VALUE's style. */
@@ -2638,20 +2668,22 @@ void
 pivot_value_set_font_style (struct pivot_value *value,
                             const struct font_style *font_style)
 {
-  if (value->font_style)
-    font_style_uninit (value->font_style);
+  struct pivot_value_ex *ex = pivot_value_ex_rw (value);
+  if (ex->font_style)
+    font_style_uninit (ex->font_style);
   else
-    value->font_style = xmalloc (sizeof *value->font_style);
-  font_style_copy (NULL, value->font_style, font_style);
+    ex->font_style = xmalloc (sizeof *ex->font_style);
+  font_style_copy (NULL, ex->font_style, font_style);
 }
 
 void
 pivot_value_set_cell_style (struct pivot_value *value,
                             const struct cell_style *cell_style)
 {
-  if (!value->cell_style)
-    value->cell_style = xmalloc (sizeof *value->cell_style);
-  *value->cell_style = *cell_style;
+  struct pivot_value_ex *ex = pivot_value_ex_rw (value);
+  if (!ex->cell_style)
+    ex->cell_style = xmalloc (sizeof *ex->cell_style);
+  *ex->cell_style = *cell_style;
 }
 
 void
@@ -2692,8 +2724,8 @@ pivot_value_new_user_text_nocopy (char *text)
 {
   struct pivot_value *value = xmalloc (sizeof *value);
   *value = (struct pivot_value) {
-    .type = PIVOT_VALUE_TEXT,
     .text = {
+      .type = PIVOT_VALUE_TEXT,
       .local = text,
       .c = text,
       .id = text,
@@ -2711,9 +2743,9 @@ pivot_value_new_user_text_nocopy (char *text)
    that pivot_value_new_variable() should be used for variable names).  For
    strings that are part of the PSPP user interface, such as names of
    procedures, statistics, annotations, error messages, etc., use
-   pivot_value_new_text().j
+   pivot_value_new_text().
 
-   The caller retains ownership of TEXT.*/
+   The caller retains ownership of TEXT. */
 struct pivot_value *
 pivot_value_new_user_text (const char *text, size_t length)
 {
@@ -2735,8 +2767,8 @@ pivot_value_new_text (const char *text)
 
   struct pivot_value *value = xmalloc (sizeof *value);
   *value = (struct pivot_value) {
-    .type = PIVOT_VALUE_TEXT,
     .text = {
+      .type = PIVOT_VALUE_TEXT,
       .local = local,
       .c = c,
       .id = c,
@@ -2747,7 +2779,7 @@ pivot_value_new_text (const char *text)
 }
 
 /* Same as pivot_value_new_text() but its argument is a printf()-like format
-   string. */
+   string.  The format string should generally be enclosed in N_(). */
 struct pivot_value * PRINTF_FORMAT (1, 2)
 pivot_value_new_text_format (const char *format, ...)
 {
@@ -2762,8 +2794,8 @@ pivot_value_new_text_format (const char *format, ...)
 
   struct pivot_value *value = xmalloc (sizeof *value);
   *value = (struct pivot_value) {
-    .type = PIVOT_VALUE_TEXT,
     .text = {
+      .type = PIVOT_VALUE_TEXT,
       .local = local,
       .c = c,
       .id = xstrdup (c),
@@ -2784,8 +2816,10 @@ pivot_value_new_number (double x)
 {
   struct pivot_value *value = xmalloc (sizeof *value);
   *value = (struct pivot_value) {
-    .type = PIVOT_VALUE_NUMERIC,
-    .numeric = { .x = x, },
+    .numeric = {
+      .type = PIVOT_VALUE_NUMERIC,
+      .x = x
+    },
   };
   return value;
 }
@@ -2795,7 +2829,7 @@ struct pivot_value *
 pivot_value_new_integer (double x)
 {
   struct pivot_value *value = pivot_value_new_number (x);
-  value->numeric.format = (struct fmt_spec) { FMT_F, 40, 0 };
+  value->numeric.format = (struct fmt_spec) { .type = FMT_F, .w = 40 };
   return value;
 }
 
@@ -2834,7 +2868,7 @@ struct pivot_value *
 pivot_value_new_value (const union value *value, int width,
                        const struct fmt_spec *format, const char *encoding)
 {
-  struct pivot_value *pv = xzalloc (sizeof *pv);
+  struct pivot_value *pv = XZALLOC (struct pivot_value);
   if (width > 0)
     {
       char *s = recode_string (UTF8, encoding, CHAR_CAST (char *, value->s),
@@ -2860,13 +2894,22 @@ pivot_value_new_value (const union value *value, int width,
 /* Returns a new pivot_value for VARIABLE. */
 struct pivot_value *
 pivot_value_new_variable (const struct variable *variable)
+{
+  return pivot_value_new_variable__ (var_get_name (variable),
+                                     var_get_label (variable));
+}
+
+/* Returns a new pivot_value for a variable with the given NAME and optional
+   LABEL. */
+struct pivot_value *
+pivot_value_new_variable__ (const char *name, const char *label)
 {
   struct pivot_value *value = xmalloc (sizeof *value);
   *value = (struct pivot_value) {
-    .type = PIVOT_VALUE_VARIABLE,
     .variable = {
-      .var_name = xstrdup (var_get_name (variable)),
-      .var_label = xstrdup_if_nonempty (var_get_label (variable)),
+      .type = PIVOT_VALUE_VARIABLE,
+      .var_name = xstrdup (name),
+      .var_label = xstrdup_if_nonempty (label),
     },
   };
   return value;
@@ -2877,15 +2920,18 @@ void
 pivot_value_add_footnote (struct pivot_value *v,
                           const struct pivot_footnote *footnote)
 {
+  struct pivot_value_ex *ex = pivot_value_ex_rw (v);
+
   /* Some legacy tables include numerous duplicate footnotes.  Suppress
      them. */
-  for (size_t i = 0; i < v->n_footnotes; i++)
-    if (v->footnote_indexes[i] == footnote->idx)
+  for (size_t i = 0; i < ex->n_footnotes; i++)
+    if (ex->footnote_indexes[i] == footnote->idx)
       return;
 
-  v->footnote_indexes = xrealloc (
-    v->footnote_indexes, (v->n_footnotes + 1) * sizeof *v->footnote_indexes);
-  v->footnote_indexes[v->n_footnotes++] = footnote->idx;
+  ex->footnote_indexes = xrealloc (
+    ex->footnote_indexes,
+    (ex->n_footnotes + 1) * sizeof *ex->footnote_indexes);
+  ex->footnote_indexes[ex->n_footnotes++] = footnote->idx;
   pivot_value_sort_footnotes (v);
 }
 
@@ -2906,9 +2952,9 @@ compare_footnote_indexes (const void *a_, const void *b_)
 void
 pivot_value_sort_footnotes (struct pivot_value *v)
 {
-  if (v->n_footnotes > 1)
-    qsort (v->footnote_indexes, v->n_footnotes, sizeof *v->footnote_indexes,
-           compare_footnote_indexes);
+  if (v->ex && v->ex->n_footnotes > 1)
+    qsort (v->ex->footnote_indexes, v->ex->n_footnotes,
+           sizeof *v->ex->footnote_indexes, compare_footnote_indexes);
 }
 
 /* If VALUE is a numeric value, and RC is a result class such as
@@ -2921,4 +2967,351 @@ pivot_value_set_rc (const struct pivot_table *table, struct pivot_value *value,
     pivot_table_use_rc (table, rc,
                         &value->numeric.format, &value->numeric.honor_small);
 }
+\f
+/* pivot_value_ex. */
+
+struct pivot_value_ex *
+pivot_value_ex_rw (struct pivot_value *value)
+{
+  if (!value->ex)
+    value->ex = xzalloc (sizeof *value->ex);
+  return value->ex;
+}
+
+struct pivot_value_ex *
+pivot_value_ex_clone (const struct pivot_value_ex *old)
+{
+  struct font_style *font_style = NULL;
+  if (old->font_style)
+    {
+      font_style = xmalloc (sizeof *font_style);
+      font_style_copy (NULL, font_style, old->font_style);
+    }
+
+  char **subscripts = NULL;
+  if (old->n_subscripts)
+    {
+      subscripts = xnmalloc (old->n_subscripts, sizeof *subscripts);
+      for (size_t i = 0; i < old->n_subscripts; i++)
+        subscripts[i] = xstrdup (old->subscripts[i]);
+    }
+
+  struct pivot_value_ex *new = xmalloc (sizeof *new);
+  *new = (struct pivot_value_ex) {
+    .font_style = font_style,
+    .cell_style = (old->cell_style
+                   ? xmemdup (old->cell_style, sizeof *new->cell_style)
+                   : NULL),
+    .subscripts = subscripts,
+    .n_subscripts = old->n_subscripts,
+    .footnote_indexes = (
+      old->n_footnotes
+      ? xmemdup (old->footnote_indexes,
+                 old->n_footnotes * sizeof *new->footnote_indexes)
+      : NULL),
+    .n_footnotes = old->n_footnotes
+  };
+  return new;
+}
+
+void
+pivot_value_ex_destroy (struct pivot_value_ex *ex)
+{
+  if (ex)
+    {
+      font_style_uninit (ex->font_style);
+      free (ex->font_style);
+      free (ex->cell_style);
+      free (ex->footnote_indexes);
+
+      for (size_t i = 0; i < ex->n_subscripts; i++)
+        free (ex->subscripts[i]);
+      free (ex->subscripts);
+      free (ex);
+    }
+}
+\f
+/* pivot_splits */
+
+struct pivot_splits_value
+  {
+    struct hmap_node hmap_node;
+    union value value;
+    int leaf;
+  };
+
+struct pivot_splits_var
+  {
+    struct pivot_dimension *dimension;
+    char *name;
+    int width;
+    size_t idx;
+    struct fmt_spec format;
+    struct hmap values;
+  };
+
+struct pivot_splits
+  {
+    struct pivot_splits_var *vars;
+    size_t n;
+    char *encoding;
+
+    size_t dindexes[MAX_SPLITS];
+
+    int warnings_left;
+  };
+
+/* Adds a dimension for each layered split file variable in DICT to PT on AXIS.
+   These dimensions should be the last dimensions added to PT (the
+   pivot_splits_put*() functions rely on this).  Returns a new pivot_splits
+   structure if any dimensions were added, otherwise a null pointer.
+
+   See the large comment on split file handling in pivot-table.h for more
+   information. */
+struct pivot_splits *
+pivot_splits_create (struct pivot_table *pt,
+                     enum pivot_axis_type axis,
+                     const struct dictionary *dict)
+{
+  if (dict_get_split_type (dict) != SPLIT_LAYERED)
+    return NULL;
+
+  size_t n = dict_get_n_splits (dict);
+  assert (n <= MAX_SPLITS);
+
+  const struct variable *const *vars = dict_get_split_vars (dict);
+  struct pivot_splits_var *psvars = xnmalloc (n, sizeof *psvars);
+  for (size_t i = n - 1; i < n; i--)
+    {
+      const struct variable *var = vars[i];
+      struct pivot_splits_var *psvar = &psvars[i];
+
+      struct pivot_dimension *d = pivot_dimension_create__ (
+        pt, axis, pivot_value_new_variable (var));
+      d->root->show_label = true;
+
+      *psvar = (struct pivot_splits_var) {
+        .width = var_get_width (var),
+        .name = xstrdup (var_get_name (var)),
+        .idx = var_get_case_index (var),
+        .format = *var_get_print_format (var),
+        .values = HMAP_INITIALIZER (psvar->values),
+        .dimension = d,
+      };
+    }
+
+  struct pivot_splits *ps = xmalloc (sizeof *ps);
+  *ps = (struct pivot_splits) {
+    .vars = psvars,
+    .n = n,
+    .encoding = xstrdup (dict_get_encoding (dict)),
+    .dindexes = { [0] = SIZE_MAX },
+    .warnings_left = 5,
+  };
+  return ps;
+}
+
+/* Destroys PS. */
+void
+pivot_splits_destroy (struct pivot_splits *ps)
+{
+  if (!ps)
+    return;
+
+  if (ps->warnings_left < 0)
+    msg (SW, ngettext ("Suppressed %d additional warning about duplicate "
+                       "split values.",
+                       "Suppressed %d additional warnings about duplicate "
+                       "split values.", -ps->warnings_left),
+         -ps->warnings_left);
+
+  for (size_t i = 0; i < ps->n; i++)
+    {
+      struct pivot_splits_var *psvar = &ps->vars[i];
+      struct pivot_splits_value *psval, *next;
+      HMAP_FOR_EACH_SAFE (psval, next, struct pivot_splits_value, hmap_node,
+                          &psvar->values)
+        {
+          value_destroy (&psval->value, psvar->width);
+          hmap_delete (&psvar->values, &psval->hmap_node);
+          free (psval);
+        }
+      free (psvar->name);
+      hmap_destroy (&psvar->values);
+    }
+  free (ps->vars);
+  free (ps->encoding);
+  free (ps);
+}
 
+static struct pivot_splits_value *
+pivot_splits_value_find (struct pivot_splits_var *psvar,
+                         const union value *value)
+{
+  struct pivot_splits_value *psval;
+  HMAP_FOR_EACH_WITH_HASH (psval, struct pivot_splits_value, hmap_node,
+                           value_hash (value, psvar->width, 0), &psvar->values)
+    if (value_equal (&psval->value, value, psvar->width))
+      return psval;
+  return NULL;
+}
+
+/* Begins adding data for a new split file group to the pivot table associated
+   with PS.  EXAMPLE should be a case from the new split file group.
+
+   This is a no-op if PS is NULL.
+
+   See the large comment on split file handling in pivot-table.h for more
+   information. */
+void
+pivot_splits_new_split (struct pivot_splits *ps, const struct ccase *example)
+{
+  if (!ps)
+    return;
+
+  size_t n_new = 0;
+  for (size_t i = 0; i < ps->n; i++)
+    {
+      struct pivot_splits_var *psvar = &ps->vars[i];
+      const union value *value = case_data_idx (example, psvar->idx);
+      struct pivot_splits_value *psval = pivot_splits_value_find (psvar, value);
+      if (!psval)
+        {
+          psval = xmalloc (sizeof *psval);
+          hmap_insert (&psvar->values, &psval->hmap_node,
+                       value_hash (value, psvar->width, 0));
+          value_clone (&psval->value, value, psvar->width);
+          psval->leaf = pivot_category_create_leaf (
+            psvar->dimension->root,
+            pivot_value_new_value (value, psvar->width, &psvar->format,
+                                   ps->encoding));
+          n_new++;
+        }
+
+      ps->dindexes[i] = psval->leaf;
+    }
+
+  if (!n_new)
+    {
+      if (ps->warnings_left-- > 0)
+        {
+          struct string s = DS_EMPTY_INITIALIZER;
+          for (size_t i = 0; i < ps->n; i++)
+            {
+              if (i > 0)
+                ds_put_cstr (&s, ", ");
+
+              struct pivot_splits_var *psvar = &ps->vars[i];
+              const union value *value = case_data_idx (example, psvar->idx);
+              ds_put_format (&s, "%s = ", psvar->name);
+
+              char *s2 = data_out (value, ps->encoding, &psvar->format,
+                                   settings_get_fmt_settings ());
+              ds_put_cstr (&s, s2 + strspn (s2, " "));
+              free (s2);
+            }
+          msg (SW, _("When SPLIT FILE is in effect, the input data must be "
+                     "sorted by the split variables (for example, using SORT "
+                     "CASES), but multiple runs of cases with the same split "
+                     "values were found separated by cases with different "
+                     "values.  Each run will be analyzed separately.  The "
+                     "duplicate split values are: %s"), ds_cstr (&s));
+          ds_destroy (&s);
+        }
+
+      struct pivot_splits_var *psvar = &ps->vars[0];
+      const union value *value = case_data_idx (example, psvar->idx);
+      ps->dindexes[0] = pivot_category_create_leaf (
+        psvar->dimension->root,
+        pivot_value_new_value (value, psvar->width, &psvar->format,
+                               ps->encoding));
+    }
+}
+
+static size_t
+pivot_splits_get_dindexes (const struct pivot_splits *ps, size_t *dindexes)
+{
+  if (!ps)
+    return 0;
+
+  assert (ps->dindexes[0] != SIZE_MAX);
+  for (size_t i = 0; i < ps->n; i++)
+    dindexes[i] = ps->dindexes[i];
+  return ps->n;
+}
+
+/* Puts VALUE in the cell in TABLE with index IDX1.  TABLE must have 1
+   dimension plus the split file dimensions from PS (if nonnull).  Takes
+   ownership of VALUE.
+
+   See the large comment on split file handling in pivot-table.h for more
+   information. */
+void
+pivot_splits_put1 (struct pivot_splits *ps, struct pivot_table *table,
+                   size_t idx1, struct pivot_value *value)
+{
+  size_t dindexes[1 + MAX_SPLITS];
+  size_t *p = dindexes;
+  *p++ = idx1;
+  p += pivot_splits_get_dindexes (ps, p);
+  pivot_table_put (table, dindexes, p - dindexes, value);
+}
+
+/* Puts VALUE in the cell in TABLE with index (IDX1, IDX2).  TABLE must have 2
+   dimensions plus the split file dimensions from PS (if nonnull).  Takes
+   ownership of VALUE.
+
+   See the large comment on split file handling in pivot-table.h for more
+   information. */
+void
+pivot_splits_put2 (struct pivot_splits *ps, struct pivot_table *table,
+                   size_t idx1, size_t idx2, struct pivot_value *value)
+{
+  size_t dindexes[2 + MAX_SPLITS];
+  size_t *p = dindexes;
+  *p++ = idx1;
+  *p++ = idx2;
+  p += pivot_splits_get_dindexes (ps, p);
+  pivot_table_put (table, dindexes, p - dindexes, value);
+}
+
+/* Puts VALUE in the cell in TABLE with index (IDX1, IDX2, IDX3).  TABLE must
+   have 3 dimensions plus the split file dimensions from PS (if nonnull).
+   Takes ownership of VALUE.
+
+   See the large comment on split file handling in pivot-table.h for more
+   information. */
+void
+pivot_splits_put3 (struct pivot_splits *ps, struct pivot_table *table,
+                   size_t idx1, size_t idx2, size_t idx3,
+                   struct pivot_value *value)
+{
+  size_t dindexes[3 + MAX_SPLITS];
+  size_t *p = dindexes;
+  *p++ = idx1;
+  *p++ = idx2;
+  *p++ = idx3;
+  p += pivot_splits_get_dindexes (ps, p);
+  pivot_table_put (table, dindexes, p - dindexes, value);
+}
+
+/* Puts VALUE in the cell in TABLE with index (IDX1, IDX2, IDX3, IDX4).  TABLE
+   must have 4 dimensions plus the split file dimensions from PS (if nonnull).
+   Takes ownership of VALUE.
+
+   See the large comment on split file handling in pivot-table.h for more
+   information. */
+void
+pivot_splits_put4 (struct pivot_splits *ps, struct pivot_table *table,
+                   size_t idx1, size_t idx2, size_t idx3, size_t idx4,
+                   struct pivot_value *value)
+{
+  size_t dindexes[4 + MAX_SPLITS];
+  size_t *p = dindexes;
+  *p++ = idx1;
+  *p++ = idx2;
+  *p++ = idx3;
+  *p++ = idx4;
+  p += pivot_splits_get_dindexes (ps, p);
+  pivot_table_put (table, dindexes, p - dindexes, value);
+}