Clean up how transformations work.
[pspp] / src / language / stats / autorecode.c
index de7848202f9e1d49b15bcdf0ade32979fdca5a1f..2d569a0436eed60105da88150650cc1ae23e9811 100644 (file)
@@ -1,5 +1,6 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2009, 2010, 2012, 2013, 2014 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2009, 2010, 2012, 2013, 2014
+   2021,  Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -36,6 +37,7 @@
 #include "libpspp/message.h"
 #include "libpspp/pool.h"
 #include "libpspp/str.h"
+#include "output/pivot-table.h"
 
 #include "gl/xalloc.h"
 #include "gl/c-xvasprintf.h"
@@ -44,8 +46,7 @@
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
-
-/* FIXME: Implement PRINT subcommand. */
+#define N_(msgid) (msgid)
 
 /* Explains how to recode one value. */
 struct arc_item
@@ -64,6 +65,8 @@ struct arc_spec
   {
     int width;                  /* Variable width. */
     int src_idx;                /* Case index of source variable. */
+    char *src_name;             /* Name of source variable. */
+    struct fmt_spec format;     /* Print format in source variable. */
     struct variable *dst;       /* Target variable. */
     struct missing_values mv;   /* Missing values of source variable. */
     char *label;                /* Variable label of source variable. */
@@ -93,8 +96,7 @@ struct autorecode_pgm
   bool blank_valid;
 };
 
-static trns_proc_func autorecode_trns_proc;
-static trns_free_func autorecode_trns_free;
+static const struct trns_class autorecode_trns_class;
 
 static int compare_arc_items (const void *, const void *, const void *aux);
 static void arc_free (struct autorecode_pgm *);
@@ -126,9 +128,10 @@ cmd_autorecode (struct lexer *lexer, struct dataset *ds)
   size_t n_dsts = 0;
 
   enum arc_direction direction = ASCENDING;
+  bool print = false;
 
   /* Create procedure. */
-  struct autorecode_pgm *arc = xzalloc (sizeof *arc);
+  struct autorecode_pgm *arc = XZALLOC (struct autorecode_pgm);
   arc->blank_valid = true;
 
   /* Parse variable lists. */
@@ -171,9 +174,7 @@ cmd_autorecode (struct lexer *lexer, struct dataset *ds)
       if (lex_match_id (lexer, "DESCENDING"))
         direction = DESCENDING;
       else if (lex_match_id (lexer, "PRINT"))
-        {
-          /* Not yet implemented. */
-        }
+        print = true;
       else if (lex_match_id (lexer, "GROUP"))
         group = true;
       else if (lex_match_id (lexer, "BLANK"))
@@ -240,9 +241,11 @@ cmd_autorecode (struct lexer *lexer, struct dataset *ds)
 
       spec->width = var_get_width (src_vars[i]);
       spec->src_idx = var_get_case_index (src_vars[i]);
+      spec->src_name = xstrdup (var_get_name (src_vars[i]));
+      spec->format = *var_get_print_format (src_vars[i]);
 
       const char *label = var_get_label (src_vars[i]);
-      spec->label = label ? xstrdup (label) : NULL;
+      spec->label = xstrdup_if_nonnull (label);
 
       if (group && i > 0)
         spec->items = arc->specs[0].items;
@@ -326,22 +329,24 @@ cmd_autorecode (struct lexer *lexer, struct dataset *ds)
   for (size_t i = 0; i < arc->n_specs; i++)
     {
       struct arc_spec *spec = &arc->specs[i];
-      struct arc_item **items;
-      struct arc_item *item;
-      size_t n_items;
-      size_t j;
 
       /* Create destination variable. */
       spec->dst = dict_create_var_assert (dict, dst_names[i], 0);
       var_set_label (spec->dst, spec->label);
 
+      /* Set print format. */
+      size_t n_items = hmap_count (&spec->items->ht);
+      char *longest_value = xasprintf ("%zu", n_items);
+      struct fmt_spec format = { .type = FMT_F, .w = strlen (longest_value) };
+      var_set_both_formats (spec->dst, &format);
+      free (longest_value);
+
       /* Create array of pointers to items. */
-      n_items = hmap_count (&spec->items->ht);
-      items = xmalloc (n_items * sizeof *items);
-      j = 0;
+      struct arc_item **items = xmalloc (n_items * sizeof *items);
+      struct arc_item *item;
+      size_t j = 0;
       HMAP_FOR_EACH (item, struct arc_item, hmap_node, &spec->items->ht)
         items[j++] = item;
-
       assert (j == n_items);
 
       /* Sort array by value. */
@@ -351,6 +356,51 @@ cmd_autorecode (struct lexer *lexer, struct dataset *ds)
       for (j = 0; j < n_items; j++)
         items[j]->to = j + 1;
 
+      if (print && (!group || i == 0))
+        {
+          struct pivot_value *title
+            = (group
+               ? pivot_value_new_text (N_("Recoding grouped variables."))
+               : spec->label && spec->label[0]
+               ? pivot_value_new_text_format (N_("Recoding %s into %s (%s)."),
+                                              spec->src_name,
+                                              var_get_name (spec->dst),
+                                              spec->label)
+               : pivot_value_new_text_format (N_("Recoding %s into %s."),
+                                              spec->src_name,
+                                              var_get_name (spec->dst)));
+          struct pivot_table *table = pivot_table_create__ (title, "Recoding");
+
+          pivot_dimension_create (
+            table, PIVOT_AXIS_COLUMN, N_("Attributes"),
+            N_("New Value"), N_("Value Label"));
+
+          struct pivot_dimension *old_values = pivot_dimension_create (
+            table, PIVOT_AXIS_ROW, N_("Old Value"));
+          old_values->root->show_label = true;
+
+          for (size_t k = 0; k < n_items; k++)
+            {
+              const struct arc_item *item = items[k];
+              int old_value_idx = pivot_category_create_leaf (
+                old_values->root, pivot_value_new_value (
+                  &item->from, item->width,
+                  (item->width
+                   ? &(struct fmt_spec) { .type = FMT_F, .w = item->width }
+                   : &spec->format),
+                  dict_get_encoding (dict)));
+              pivot_table_put2 (table, 0, old_value_idx,
+                                pivot_value_new_integer (item->to));
+
+              const char *value_label = item->value_label;
+              if (value_label && value_label[0])
+                pivot_table_put2 (table, 1, old_value_idx,
+                                  pivot_value_new_user_text (value_label, -1));
+            }
+
+          pivot_table_submit (table);
+        }
+
       /* Assign user-missing values.
 
          User-missing values in the source variable(s) must be marked
@@ -378,7 +428,7 @@ cmd_autorecode (struct lexer *lexer, struct dataset *ds)
           mv_init (&mv, 0);
           if (n_missing > 3)
             mv_add_range (&mv, lo, hi);
-          else if (n_missing > 0)
+          else
             for (size_t k = 0; k < n_missing; k++)
               mv_add_num (&mv, lo + k);
           var_set_missing_values (spec->dst, &mv);
@@ -399,7 +449,7 @@ cmd_autorecode (struct lexer *lexer, struct dataset *ds)
       /* Free array. */
       free (items);
     }
-  add_transformation (ds, autorecode_trns_proc, autorecode_trns_free, arc);
+  add_transformation (ds, &autorecode_trns_class, arc);
 
   for (size_t i = 0; i < n_dsts; i++)
     free (dst_names[i]);
@@ -436,13 +486,15 @@ arc_free (struct autorecode_pgm *arc)
               free (item);
             }
           free (spec->label);
+          free (spec->src_name);
           mv_destroy (&spec->mv);
         }
 
       size_t n_rec_items =
-        (arc->n_specs == 1 || arc->specs[0].items == arc->specs[1].items
+        (arc->n_specs >= 2 && arc->specs[0].items == arc->specs[1].items
          ? 1
          : arc->n_specs);
+
       for (size_t i = 0; i < n_rec_items; i++)
         {
           struct arc_spec *spec = &arc->specs[i];
@@ -500,7 +552,7 @@ compare_arc_items (const void *a_, const void *b_, const void *direction_)
   return direction == ASCENDING ? cmp : -cmp;
 }
 
-static int
+static enum trns_result
 autorecode_trns_proc (void *arc_, struct ccase **c,
                       casenumber case_idx UNUSED)
 {
@@ -515,7 +567,7 @@ autorecode_trns_proc (void *arc_, struct ccase **c,
       size_t hash = value_hash (value, width, 0);
       const struct arc_item *item = find_arc_item (spec->items, value, width,
                                                    hash);
-      case_data_rw (*c, spec->dst)->f = item ? item->to : SYSMIS;
+      *case_num_rw (*c, spec->dst) = item ? item->to : SYSMIS;
     }
 
   return TRNS_CONTINUE;
@@ -529,3 +581,9 @@ autorecode_trns_free (void *arc_)
   arc_free (arc);
   return true;
 }
+
+static const struct trns_class autorecode_trns_class = {
+  .name = "AUTORECODE",
+  .execute = autorecode_trns_proc,
+  .destroy = autorecode_trns_free,
+};