pivot-table: Allow all pivot_value formatting functions to use defaults.
[pspp] / src / output / pivot-table.c
index f8f917d9dd1d965b3d5b2830b725c7c12004eee1..ec652e50de0b532e1230a93941369a99e3cdb7c1 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"
@@ -1443,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
@@ -2372,8 +2374,15 @@ get_text_from_markup (const char *markup, struct string *out)
   xmlFreeParserCtxt (parser);
 }
 
-/* Appends a text representation of the body of VALUE to OUT.  Settings on
-   PT control whether variable and value labels are included.
+static const struct pivot_table pivot_value_format_defaults = {
+  .show_values = SETTINGS_VALUE_SHOW_DEFAULT,
+  .show_variables = SETTINGS_VALUE_SHOW_DEFAULT,
+  .settings = FMT_SETTINGS_INIT,
+};
+
+/* Appends a text representation of the body of VALUE to OUT.  Settings on PT
+   control whether variable and value labels are included (pass NULL for PT to
+   get default formatting in the absence of a pivot table).
 
    The "body" omits subscripts and superscripts and footnotes.
 
@@ -2381,9 +2390,10 @@ get_text_from_markup (const char *markup, struct string *out)
    otherwise.  */
 bool
 pivot_value_format_body (const struct pivot_value *value,
-                         const struct pivot_table *pt,
+                         const struct pivot_table *pt_,
                          struct string *out)
 {
+  const struct pivot_table *pt = pt_ ? pt_ : &pivot_value_format_defaults;
   enum settings_value_show show;
   bool numeric = false;
 
@@ -2474,8 +2484,9 @@ pivot_value_format_body (const struct pivot_value *value,
   return numeric;
 }
 
-/* Appends a text representation of VALUE to OUT.  Settings on
-   PT control whether variable and value labels are included.
+/* Appends a text representation of VALUE to OUT.  Settings on PT control
+   whether variable and value labels are included (pass NULL for PT to get
+   default formatting in the absence of a pivot table).
 
    Subscripts and footnotes are included.
 
@@ -2483,9 +2494,10 @@ pivot_value_format_body (const struct pivot_value *value,
    otherwise.  */
 bool
 pivot_value_format (const struct pivot_value *value,
-                    const struct pivot_table *pt,
+                    const struct pivot_table *pt_,
                     struct string *out)
 {
+  const struct pivot_table *pt = pt_ ? pt_ : &pivot_value_format_defaults;
   bool numeric = pivot_value_format_body (value, pt, out);
 
   const struct pivot_value_ex *ex = value->ex;
@@ -2513,7 +2525,9 @@ pivot_value_format (const struct pivot_value *value,
 }
 
 /* Returns a text representation of VALUE.  The caller must free the string,
-   with free(). */
+   with free().  Settings on PT control whether variable and value labels are
+   included (pass NULL for PT to get default formatting in the absence of a
+   pivot table). */
 char *
 pivot_value_to_string (const struct pivot_value *value,
                        const struct pivot_table *pt)
@@ -2523,17 +2537,6 @@ pivot_value_to_string (const struct pivot_value *value,
   return ds_steal_cstr (&s);
 }
 
-char *
-pivot_value_to_string_defaults (const struct pivot_value *value)
-{
-  static const struct pivot_table pt = {
-    .show_values = SETTINGS_VALUE_SHOW_DEFAULT,
-    .show_variables = SETTINGS_VALUE_SHOW_DEFAULT,
-    .settings = FMT_SETTINGS_INIT,
-  };
-  return pivot_value_to_string (value, &pt);
-}
-
 struct pivot_value *
 pivot_value_clone (const struct pivot_value *old)
 {
@@ -3028,3 +3031,282 @@ 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
+  {
+    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),
+        .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)),
+    .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);
+        }
+      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 (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));
+          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 (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[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);
+}