Change how checking for missing values works.
[pspp] / src / data / missing-values.c
index 61bb9bcb13c4968a4500fc4fc98ee13e2f631c1b..44d0bf9ee67315148224715f3ae4f083019c3636 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2005, 2009 Free Software Foundation, Inc.
+   Copyright (C) 2005, 2009, 2011, 2013 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
    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 #include <config.h>
-#include <data/missing-values.h>
+
+#include "data/missing-values.h"
+
 #include <assert.h>
 #include <stdlib.h>
-#include <data/variable.h>
-#include <libpspp/assertion.h>
-#include <libpspp/str.h>
+
+#include "data/variable.h"
+#include "libpspp/assertion.h"
+#include "libpspp/cast.h"
+#include "libpspp/i18n.h"
+#include "libpspp/str.h"
+
+#include "gl/minmax.h"
 
 /* Types of user-missing values.
    Invisible--use access functions defined below instead. */
@@ -108,7 +115,7 @@ mv_is_acceptable (const union value *value, int width)
   int i;
 
   for (i = MV_MAX_STRING; i < width; i++)
-    if (value_str (value, width)[i] != ' ')
+    if (value->s[i] != ' ')
       return false;
   return true;
 }
@@ -155,19 +162,23 @@ mv_add_value (struct missing_values *mv, const union value *v)
   NOT_REACHED ();
 }
 
-/* Attempts to add S to the set of string missing values MV.  S
-   must contain exactly as many characters as MV's width.
-   Returns true if successful, false if MV has no more room for
+/* Attempts to add S, which is LEN bytes long, to the set of string missing
+   values MV.  Returns true if successful, false if MV has no more room for
    missing values or if S is not an acceptable missing value. */
 bool
-mv_add_str (struct missing_values *mv, const uint8_t s[])
+mv_add_str (struct missing_values *mv, const uint8_t s[], size_t len)
 {
   union value v;
   bool ok;
 
   assert (mv->width > 0);
+  while (len > mv->width)
+    if (s[--len] != ' ')
+      return false;
+
   value_init (&v, mv->width);
-  memcpy (value_str_rw (&v, mv->width), s, mv->width);
+  buf_copy_rpad (CHAR_CAST (char *, v.s), mv->width,
+                 CHAR_CAST (char *, s), len, ' ');
   ok = mv_add_value (mv, &v);
   value_destroy (&v, mv->width);
 
@@ -413,14 +424,14 @@ is_str_user_missing (const struct missing_values *mv, const uint8_t s[])
     case MVT_NONE:
       return false;
     case MVT_1:
-      return !memcmp (value_str (&v[0], mv->width), s, mv->width);
+      return !memcmp (v[0].s, s, mv->width);
     case MVT_2:
-      return (!memcmp (value_str (&v[0], mv->width), s, mv->width)
-              || !memcmp (value_str (&v[1], mv->width), s, mv->width));
+      return (!memcmp (v[0].s, s, mv->width)
+              || !memcmp (v[1].s, s, mv->width));
     case MVT_3:
-      return (!memcmp (value_str (&v[0], mv->width), s, mv->width)
-              || !memcmp (value_str (&v[1], mv->width), s, mv->width)
-              || !memcmp (value_str (&v[2], mv->width), s, mv->width));
+      return (!memcmp (v[0].s, s, mv->width)
+              || !memcmp (v[1].s, s, mv->width)
+              || !memcmp (v[2].s, s, mv->width));
     case MVT_RANGE:
     case MVT_RANGE_1:
       NOT_REACHED ();
@@ -428,37 +439,97 @@ is_str_user_missing (const struct missing_values *mv, const uint8_t s[])
   NOT_REACHED ();
 }
 
-/* Returns true if V is a missing value in the given CLASS in MV,
-   false otherwise. */
-bool
-mv_is_value_missing (const struct missing_values *mv, const union value *v,
-                     enum mv_class class)
+/* Returns MV_USER if V is a user-missing value in MV, MV_SYSTEM if V is
+   system-missing (and MV is numeric), or 0 if V is not missing. */
+enum mv_class
+mv_is_value_missing (const struct missing_values *mv, const union value *v)
 {
   return (mv->width == 0
-          ? mv_is_num_missing (mv, v->f, class)
-          : mv_is_str_missing (mv, value_str (v, mv->width), class));
+          ? mv_is_num_missing (mv, v->f)
+          : mv_is_str_missing (mv, v->s));
 }
 
-/* Returns true if D is a missing value in the given CLASS in MV,
-   false otherwise.
-   MV must be a set of numeric missing values. */
-bool
-mv_is_num_missing (const struct missing_values *mv, double d,
-                   enum mv_class class)
+/* Returns MV_USER if V is a user-missing value in MV, MV_SYSTEM if V is
+   system-missing, or 0 if V is not missing.  MV must be a set of numeric
+   missing values. */
+enum mv_class
+mv_is_num_missing (const struct missing_values *mv, double d)
 {
   assert (mv->width == 0);
-  return ((class & MV_SYSTEM && d == SYSMIS)
-          || (class & MV_USER && is_num_user_missing (mv, d)));
+  return (d == SYSMIS ? MV_SYSTEM
+          : is_num_user_missing (mv, d) ? MV_USER
+          : 0);
 }
 
-/* Returns true if S[] is a missing value in the given CLASS in
-   MV, false otherwise.
-   MV must be a set of string missing values.
-   S[] must contain exactly as many characters as MV's width. */
-bool
-mv_is_str_missing (const struct missing_values *mv, const uint8_t s[],
-                   enum mv_class class)
+/* Returns MV_USER if S[] is a user-missing value in MV, or 0 if V is not
+   missing.  MV must be a set of string missing values.  S[] must contain
+   exactly as many characters as MV's width. */
+enum mv_class
+mv_is_str_missing (const struct missing_values *mv, const uint8_t s[])
 {
   assert (mv->width > 0);
-  return class & MV_USER && is_str_user_missing (mv, s);
+  return is_str_user_missing (mv, s) ? MV_USER : 0;
+}
+
+/* Like mv_is_value_missing(), this tests whether V is a missing value in MV.
+   It supports the uncommon case where V and MV might have different widths:
+   the caller must specify VW, the width of V.  MV and VW must be both numeric
+   or both string.
+
+   Comparison of strings of different width is done by conceptually extending
+   both strings to infinite width by appending spaces. */
+enum mv_class
+mv_is_value_missing_varwidth (const struct missing_values *mv,
+                              const union value *v, int vw)
+{
+  int mvw = mv->width;
+  if (mvw == vw)
+    return mv_is_value_missing (mv, v);
+
+  /* Make sure they're both strings. */
+  assert (mvw && vw);
+  if (mv->type == MVT_NONE)
+    return false;
+
+  for (int i = 0; i < mv->type; i++)
+    if (!buf_compare_rpad (CHAR_CAST_BUG (const char *, mv->values[i].s), mvw,
+                           CHAR_CAST_BUG (const char *, v->s), vw))
+      return MV_USER;
+  return 0;
+}
+
+char *
+mv_to_string (const struct missing_values *mv, const char *encoding)
+{
+  struct string s = DS_EMPTY_INITIALIZER;
+  if (mv_has_range (mv))
+    {
+      double x, y;
+      mv_get_range (mv, &x, &y);
+      if (x == LOWEST)
+        ds_put_format (&s, "LOWEST THRU %.*g", DBL_DIG + 1, y);
+      else if (y == HIGHEST)
+        ds_put_format (&s, "%.*g THRU HIGHEST", DBL_DIG + 1, x);
+      else
+        ds_put_format (&s, "%.*g THRU %.*g",
+                       DBL_DIG + 1, x,
+                       DBL_DIG + 1, y);
+    }
+  for (size_t j = 0; j < mv_n_values (mv); j++)
+    {
+      const union value *value = mv_get_value (mv, j);
+      if (!ds_is_empty (&s))
+        ds_put_cstr (&s, "; ");
+      if (!mv->width)
+        ds_put_format (&s, "%.*g", DBL_DIG + 1, value->f);
+      else
+        {
+          char *mvs = recode_string (
+            "UTF-8", encoding, CHAR_CAST (char *, value->s),
+            MIN (mv->width, MV_MAX_STRING));
+          ds_put_format (&s, "\"%s\"", mvs);
+          free (mvs);
+        }
+    }
+  return ds_is_empty (&s) ? NULL : ds_steal_cstr (&s);
 }