DO REPEAT: Order PRINT output correctly.
[pspp] / src / output / pivot-table.c
index 8ecbd361a5e6b31566ee85ea0fb29f14d387c287..9251621676f2dd630ebde735ba955fba3b0b7f56 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"
@@ -217,10 +218,8 @@ pivot_table_look_builtin_default (void)
 
     .omit_empty = true,
     .row_labels_in_corner = true,
-    .width_ranges = {
-      [TABLE_HORZ] = { 36, 72 },
-      [TABLE_VERT] = { 36, 120 },
-    },
+    .col_heading_width_range = { 36, 72 },
+    .row_heading_width_range = { 36, 120 },
 
     .areas = {
 #define AREA(BOLD, H, V, L, R, T, B) {                         \
@@ -777,7 +776,7 @@ pivot_table_use_rc (const struct pivot_table *table, const char *s,
     {
       if (!strcmp (s, PIVOT_RC_OTHER))
         {
-          *format = *settings_get_format ();
+          *format = settings_get_format ();
           *honor_small = true;
         }
       else if (!strcmp (s, PIVOT_RC_COUNT) && !overridden_count_format)
@@ -805,13 +804,13 @@ pivot_table_use_rc (const struct pivot_table *table, const char *s,
    include the RC_ prefix) to *FORMAT.  Returns true if successful, false if S
    does not name a known result class. */
 bool
-pivot_result_class_change (const char *s_, const struct fmt_spec *format)
+pivot_result_class_change (const char *s_, struct fmt_spec format)
 {
   char *s = xasprintf ("RC_%s", s_);
   struct result_class *rc = pivot_result_class_find (s);
   if (rc)
     {
-      rc->format = *format;
+      rc->format = format;
       if (!strcmp (s, PIVOT_RC_COUNT))
         overridden_count_format = true;
     }
@@ -1373,15 +1372,13 @@ pivot_table_set_weight_var (struct pivot_table *table,
 }
 
 /* Sets the format used for PIVOT_RC_COUNT cells to WFMT, which should be the
-   format for the dictionary whose data or statistics are being put into TABLE.
-
-   This has no effect if WFMT is NULL. */
+   format for the dictionary whose data or statistics are being put into
+   TABLE. */
 void
-pivot_table_set_weight_format (struct pivot_table *table,
-                               const struct fmt_spec *wfmt)
+pivot_table_set_weight_format (struct pivot_table *table, struct fmt_spec wfmt)
 {
-  if (wfmt)
-    table->weight_format = *wfmt;
+  wfmt.w = 40;
+  table->weight_format = wfmt;
 }
 
 /* Returns true if TABLE has no cells, false otherwise. */
@@ -1443,8 +1440,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
@@ -1474,7 +1472,7 @@ pivot_table_put (struct pivot_table *table, const size_t *dindexes, size_t n,
                 }
             }
         }
-      value->numeric.format = *settings_get_format ();
+      value->numeric.format = settings_get_format ();
       value->numeric.honor_small = true;
 
     done:;
@@ -1962,7 +1960,10 @@ compose_headings (const struct pivot_table *pt,
                 {
                   headings[row][column] = pivot_value_to_string (c->name, pt);
                   if (!*headings[row][column])
-                    headings[row][column] = xstrdup ("<blank>");
+                    {
+                      free (headings[row][column]);
+                      headings[row][column] = xstrdup ("<blank>");
+                    }
                   row--;
                 }
             }
@@ -2023,6 +2024,20 @@ pivot_table_sizing_dump (const char *name,
     }
 }
 
+static void
+dump_leaf (const struct pivot_table *table, const struct pivot_category *c)
+{
+  if (c)
+    {
+      dump_leaf (table, c->parent);
+      if (pivot_category_is_leaf (c) || c->show_label)
+        {
+          putchar (' ');
+          pivot_value_dump (c->name, table);
+        }
+    }
+}
+
 void
 pivot_table_dump (const struct pivot_table *table, int indentation)
 {
@@ -2050,9 +2065,9 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
 
   indent (indentation);
   printf ("sizing:\n");
-  pivot_table_sizing_dump ("column", table->look->width_ranges[TABLE_HORZ],
+  pivot_table_sizing_dump ("column", table->look->col_heading_width_range,
                            &table->sizing[TABLE_HORZ], indentation + 1);
-  pivot_table_sizing_dump ("row", table->look->width_ranges[TABLE_VERT],
+  pivot_table_sizing_dump ("row", table->look->row_heading_width_range,
                            &table->sizing[TABLE_VERT], indentation + 1);
 
   indent (indentation);
@@ -2082,11 +2097,17 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
         {
           const struct pivot_dimension *d = layer_axis->dimensions[i];
           char *name = pivot_value_to_string (d->root->name, table);
-          char *value = pivot_value_to_string (
-            d->data_leaves[table->current_layer[i]]->name, table);
-          printf (" %s=%s", name, value);
-          free (value);
+          printf (" %s", name);
           free (name);
+
+          size_t ofs = table->current_layer[i];
+          if (ofs < d->n_leaves)
+            {
+              char *value = pivot_value_to_string (d->data_leaves[ofs]->name,
+                                                   table);
+              printf ("=%s", value);
+              free (value);
+            }
         }
 
       putchar ('\n');
@@ -2108,23 +2129,7 @@ pivot_table_dump (const struct pivot_table *table, int indentation)
           pivot_value_dump (d->root->name, table);
           fputs (" =", stdout);
 
-          struct pivot_value **names = xnmalloc (d->n_leaves, sizeof *names);
-          size_t n_names = 0;
-          for (const struct pivot_category *c
-                 = d->presentation_leaves[layer_indexes[i]];
-               c;
-               c = c->parent)
-            {
-              if (pivot_category_is_leaf (c) || c->show_label)
-                names[n_names++] = c->name;
-            }
-
-          for (size_t i = n_names; i-- > 0;)
-            {
-              putchar (' ');
-              pivot_value_dump (names[i], table);
-            }
-          free (names);
+          dump_leaf (table, d->presentation_leaves[layer_indexes[i]]);
         }
       putchar ('\n');
 
@@ -2404,13 +2409,13 @@ pivot_value_format_body (const struct pivot_value *value,
                              value->numeric.value_label != NULL);
       if (show & SETTINGS_VALUE_SHOW_VALUE)
         {
-          const struct fmt_spec *f = &value->numeric.format;
-          const struct fmt_spec *format
-            = (f->type == FMT_F
+          struct fmt_spec f = value->numeric.format;
+          const struct fmt_spec format
+            = (f.type == FMT_F
                && value->numeric.honor_small
                && value->numeric.x != 0
                && fabs (value->numeric.x) < pt->small
-               ? &(struct fmt_spec) { .type = FMT_E, .w = 40, .d = f->d }
+               ? (struct fmt_spec) { .type = FMT_E, .w = 40, .d = f.d }
                : f);
 
           char *s = data_out (&(union value) { .f = value->numeric.x },
@@ -2865,7 +2870,7 @@ pivot_value_new_var_value (const struct variable *variable,
    encoding. */
 struct pivot_value *
 pivot_value_new_value (const union value *value, int width,
-                       const struct fmt_spec *format, const char *encoding)
+                       struct fmt_spec format, const char *encoding)
 {
   struct pivot_value *pv = XZALLOC (struct pivot_value);
   if (width > 0)
@@ -2878,13 +2883,13 @@ pivot_value_new_value (const union value *value, int width,
 
       pv->type = PIVOT_VALUE_STRING;
       pv->string.s = s;
-      pv->string.hex = format->type == FMT_AHEX;
+      pv->string.hex = format.type == FMT_AHEX;
     }
   else
     {
       pv->type = PIVOT_VALUE_NUMERIC;
       pv->numeric.x = value->f;
-      pv->numeric.format = *format;
+      pv->numeric.format = format;
     }
 
   return pv;
@@ -2908,7 +2913,7 @@ pivot_value_new_variable__ (const char *name, const char *label)
     .variable = {
       .type = PIVOT_VALUE_VARIABLE,
       .var_name = xstrdup (name),
-      .var_label = xstrdup_if_nonempty (label),
+      .var_label = xstrdup_if_nonnull (label),
     },
   };
   return value;
@@ -3029,3 +3034,340 @@ pivot_value_ex_destroy (struct pivot_value_ex *ex)
       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;
+    const struct variable *var;
+    int width;
+    struct hmap values;
+  };
+
+struct pivot_splits_dup
+  {
+    struct hmap_node hmap_node;
+    union value *values;
+  };
+
+struct pivot_splits
+  {
+    struct pivot_splits_var *vars;
+    size_t n;
+    char *encoding;
+
+    struct hmap dups;
+
+    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),
+        .values = HMAP_INITIALIZER (psvar->values),
+        .dimension = d,
+        .var = var,
+      };
+    }
+
+  struct pivot_splits *ps = xmalloc (sizeof *ps);
+  *ps = (struct pivot_splits) {
+    .vars = psvars,
+    .n = n,
+    .encoding = xstrdup (dict_get_encoding (dict)),
+    .dups = HMAP_INITIALIZER (ps->dups),
+    .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);
+
+  struct pivot_splits_dup *dup, *next_dup;
+  HMAP_FOR_EACH_SAFE (dup, next_dup, struct pivot_splits_dup, hmap_node,
+                      &ps->dups)
+    {
+      for (size_t i = 0; i < ps->n; i++)
+        value_destroy (&dup->values[i], ps->vars[i].width);
+      free (dup->values);
+      free (dup);
+    }
+  hmap_destroy (&ps->dups);
+
+  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);
+        }
+      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;
+}
+
+static bool
+pivot_splits_find_dup (struct pivot_splits *ps, const struct ccase *example)
+{
+  unsigned int hash = 0;
+  for (size_t i = 0; i < ps->n; i++)
+    {
+      struct pivot_splits_var *psvar = &ps->vars[i];
+      const union value *value = case_data (example, psvar->var);
+      hash = value_hash (value, psvar->width, hash);
+    }
+  struct pivot_splits_dup *dup;
+  HMAP_FOR_EACH_WITH_HASH (dup, struct pivot_splits_dup, hmap_node, hash,
+                           &ps->dups)
+    {
+      bool equal = true;
+      for (size_t i = 0; i < ps->n && equal; i++)
+        {
+          struct pivot_splits_var *psvar = &ps->vars[i];
+          const union value *value = case_data (example, psvar->var);
+          equal = value_equal (value, &dup->values[i], psvar->width);
+        }
+      if (equal)
+        return true;
+    }
+
+  union value *values = xmalloc (ps->n * sizeof *values);
+  for (size_t i = 0; i < ps->n; i++)
+    {
+      struct pivot_splits_var *psvar = &ps->vars[i];
+      const union value *value = case_data (example, psvar->var);
+      value_clone (&values[i], value, psvar->width);
+    }
+
+  dup = xmalloc (sizeof *dup);
+  dup->values = values;
+  hmap_insert (&ps->dups, &dup->hmap_node, hash);
+  return false;
+}
+
+/* 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;
+
+  for (size_t i = 0; i < ps->n; i++)
+    {
+      struct pivot_splits_var *psvar = &ps->vars[i];
+      const union value *value = case_data (example, psvar->var);
+      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_var_value (psvar->var, value));
+        }
+
+      ps->dindexes[i] = psval->leaf;
+    }
+
+  if (pivot_splits_find_dup (ps, example))
+    {
+      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 (example, psvar->var);
+              ds_put_format (&s, "%s = ", var_get_name (psvar->var));
+
+              char *s2 = data_out (value, ps->encoding,
+                                   var_get_print_format (psvar->var),
+                                   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 (example, psvar->var);
+      ps->dindexes[0] = pivot_category_create_leaf (
+        psvar->dimension->root,
+        pivot_value_new_var_value (psvar->var, value));
+    }
+}
+
+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[ps->n - i - 1] = 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);
+}