Change enum legacy_encoding to const char *.
[pspp-builds.git] / src / data / data-in.c
index 7ecded2f9333f8235488c8f676692c57ba44b5fd..e7a83f25b44a5f7a55d2c46e20aed4dc7785e40e 100644 (file)
@@ -1,21 +1,18 @@
-/* PSPP - computes sample statistics.
-   Copyright (C) 1997-9, 2000, 2006 Free Software Foundation, Inc.
-   Written by Ben Pfaff <blp@gnu.org>.
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 1997-9, 2000, 2006, 2009 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 the Free Software Foundation; either version 2 of the
-   License, or (at your option) any later version.
+   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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
 
-   This program is distributed in the hope that it will be useful, but
-   WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-   General Public License for more details.
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301, USA. */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 #include <config.h>
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdbool.h>
+#include <limits.h>
 
 #include "calendar.h"
 #include "identifier.h"
 #include "settings.h"
-#include "variable.h"
+#include "value.h"
+#include "format.h"
 
 #include <libpspp/assertion.h>
+#include <libpspp/legacy-encoding.h>
 #include <libpspp/compiler.h>
 #include <libpspp/integer-format.h>
-#include <libpspp/magic.h>
 #include <libpspp/message.h>
 #include <libpspp/misc.h>
 #include <libpspp/str.h>
-
 #include "c-ctype.h"
+#include "c-strtod.h"
 #include "minmax.h"
-#include "size_max.h"
 #include "xalloc.h"
 
 #include "gettext.h"
@@ -55,6 +53,7 @@
 /* Information about parsing one data field. */
 struct data_in
   {
+    const char *encoding;       /* Encoding of source. */
     struct substring input;     /* Source. */
     enum fmt_type format;       /* Input format. */
     int implied_decimals;       /* Number of implied decimal places. */
@@ -66,11 +65,7 @@ struct data_in
     int last_column;           /* Last column. */
   };
 
-/* Integer format used for IB and PIB input. */
-static enum integer_format input_integer_format = INTEGER_NATIVE;
 
-/* Floating-point format used for RB and RBHEX input. */
-static enum float_format input_float_format = FLOAT_NATIVE_DOUBLE;
 
 typedef bool data_in_parser_func (struct data_in *);
 #define FMT(NAME, METHOD, IMIN, OMIN, IO, CATEGORY) \
@@ -88,34 +83,53 @@ static bool trim_spaces_and_check_missing (struct data_in *);
 
 static int hexit_value (int c);
 \f
-/* Parses the characters in INPUT according to FORMAT.  Stores
-   the parsed representation in OUTPUT, which has the given WIDTH
-   (0 for a numeric field, otherwise the string width).
+/* Parses the characters in INPUT, which are encoded in the given
+   ENCODING, according to FORMAT.  Stores the parsed
+   representation in OUTPUT, which the caller must have
+   initialized with the given WIDTH (0 for a numeric field,
+   otherwise the string width).
 
    If no decimal point is included in a numeric format, then
    IMPLIED_DECIMALS decimal places are implied.  Specify 0 if no
    decimal places should be implied.
 
-   If FIRST_COLUMN is nonzero, then it should be the 1-based
-   column number of the first character in INPUT, used in error
-   messages. */
+   If FIRST_COLUMN and LAST_COLUMN are nonzero, then they should
+   be the 1-based column number of the first and
+   one-past-the-last-character in INPUT, for use in error
+   messages.  (LAST_COLUMN cannot always be calculated from
+   FIRST_COLUMN plus the length of the input because of the
+   possibility of escaped quotes in strings, etc.) */
 bool
-data_in (struct substring input, 
+data_in (struct substring input, const char *encoding,
          enum fmt_type format, int implied_decimals,
-         int first_column, union value *output, int width) 
+         int first_column, int last_column, union value *output, int width)
 {
-  static data_in_parser_func *const handlers[FMT_NUMBER_OF_FORMATS] = 
+  static data_in_parser_func *const handlers[FMT_NUMBER_OF_FORMATS] =
     {
 #define FMT(NAME, METHOD, IMIN, OMIN, IO, CATEGORY) parse_##METHOD,
 #include "format.def"
     };
 
   struct data_in i;
+  void *copy = NULL;
   bool ok;
 
   assert ((width != 0) == fmt_is_string (format));
 
-  i.input = input;
+  if (0 == strcmp (encoding, LEGACY_NATIVE)
+      || fmt_get_category (format) & (FMT_CAT_BINARY | FMT_CAT_STRING))
+    {
+      i.input = input;
+      i.encoding = encoding;
+    }
+  else
+    {
+      ss_alloc_uninit (&i.input, ss_length (input));
+      legacy_recode (encoding, ss_data (input), LEGACY_NATIVE,
+                     ss_data (i.input), ss_length (input));
+      i.encoding = LEGACY_NATIVE;
+      copy = ss_data (i.input);
+    }
   i.format = format;
   i.implied_decimals = implied_decimals;
 
@@ -123,11 +137,11 @@ data_in (struct substring input,
   i.width = width;
 
   i.first_column = first_column;
-  i.last_column = first_column + ss_length (input) - 1;
+  i.last_column = last_column;
 
-  if (!ss_is_empty (i.input)) 
+  if (!ss_is_empty (i.input))
     {
-      ok = handlers[i.format] (&i); 
+      ok = handlers[i.format] (&i);
       if (!ok)
         default_result (&i);
     }
@@ -137,47 +151,21 @@ data_in (struct substring input,
       ok = true;
     }
 
-  return ok;
-}
+  if (copy)
+    free (copy);
 
-/* Returns the integer format used for IB and PIB input. */
-enum integer_format
-data_in_get_integer_format (void) 
-{
-  return input_integer_format;
-}
-
-/* Sets the integer format used for IB and PIB input to
-   FORMAT. */
-void
-data_in_set_integer_format (enum integer_format format) 
-{
-  input_integer_format = format;
-}
-
-/* Returns the floating-point format used for RB and RBHEX
-   input. */
-enum float_format
-data_in_get_float_format (void) 
-{
-  return input_float_format;
+  return ok;
 }
 
-/* Sets the floating-point format used for RB and RBHEX input to
-   FORMAT. */
-void
-data_in_set_float_format (enum float_format format) 
-{
-  input_float_format = format;
-}
 \f
-/* Format parsers. */ 
+/* Format parsers. */
 
 /* Parses F, COMMA, DOT, DOLLAR, PCT, and E input formats. */
 static bool
 parse_number (struct data_in *i)
 {
-  const struct fmt_number_style *style = fmt_get_style (i->format);
+  const struct fmt_number_style *style =
+    settings_get_style (i->format);
 
   struct string tmp;
 
@@ -185,7 +173,10 @@ parse_number (struct data_in *i)
   int save_errno;
   char *tail;
 
-  assert (fmt_get_category (i->format) != FMT_CAT_CUSTOM);
+  if  (fmt_get_category (i->format) == FMT_CAT_CUSTOM)
+    {
+      style = settings_get_style (FMT_F);
+    }
 
   /* Trim spaces and check for missing value representation. */
   if (trim_spaces_and_check_missing (i))
@@ -193,28 +184,28 @@ parse_number (struct data_in *i)
 
   ds_init_empty (&tmp);
   ds_extend (&tmp, 64);
-  
+
   /* Prefix character may precede sign. */
-  if (!ss_is_empty (style->prefix)) 
+  if (!ss_is_empty (style->prefix))
     {
       ss_match_char (&i->input, ss_first (style->prefix));
       ss_ltrim (&i->input, ss_cstr (CC_SPACES));
     }
 
   /* Sign. */
-  if (ss_match_char (&i->input, '-')) 
+  if (ss_match_char (&i->input, '-'))
     {
       ds_put_char (&tmp, '-');
       ss_ltrim (&i->input, ss_cstr (CC_SPACES));
     }
-  else 
+  else
     {
       ss_match_char (&i->input, '+');
       ss_ltrim (&i->input, ss_cstr (CC_SPACES));
     }
 
   /* Prefix character may follow sign. */
-  if (!ss_is_empty (style->prefix)) 
+  if (!ss_is_empty (style->prefix))
     {
       ss_match_char (&i->input, ss_first (style->prefix));
       ss_ltrim (&i->input, ss_cstr (CC_SPACES));
@@ -229,7 +220,7 @@ parse_number (struct data_in *i)
     }
 
   /* Decimal point and following digits. */
-  if (ss_match_char (&i->input, style->decimal)) 
+  if (ss_match_char (&i->input, style->decimal))
     {
       explicit_decimals = true;
       ds_put_char (&tmp, '.');
@@ -245,7 +236,7 @@ parse_number (struct data_in *i)
       explicit_decimals = true;
       ds_put_char (&tmp, 'e');
 
-      if (strchr ("eEdD", ss_first (i->input))) 
+      if (strchr ("eEdD", ss_first (i->input)))
         {
           ss_advance (&i->input, 1);
           ss_match_char (&i->input, ' ');
@@ -276,10 +267,10 @@ parse_number (struct data_in *i)
       return false;
     }
 
-  /* Let strtod() do the conversion. */
+  /* Let c_strtod() do the conversion. */
   save_errno = errno;
   errno = 0;
-  i->output->f = strtod (ds_cstr (&tmp), &tail);
+  i->output->f = c_strtod (ds_cstr (&tmp), &tail);
   if (*tail != '\0')
     {
       data_warning (i, _("Invalid numeric syntax."));
@@ -287,20 +278,20 @@ parse_number (struct data_in *i)
       ds_destroy (&tmp);
       return false;
     }
-  else if (errno == ERANGE) 
+  else if (errno == ERANGE)
     {
       if (fabs (i->output->f) > 1)
         {
           data_warning (i, _("Too-large number set to system-missing."));
           i->output->f = SYSMIS;
         }
-      else 
+      else
         {
           data_warning (i, _("Too-small number set to zero."));
-          i->output->f = 0.0; 
+          i->output->f = 0.0;
         }
     }
-  else 
+  else
     {
       errno = save_errno;
       if (!explicit_decimals)
@@ -318,14 +309,14 @@ parse_N (struct data_in *i)
   int c;
 
   i->output->f = 0;
-  while ((c = ss_get_char (&i->input)) != EOF) 
+  while ((c = ss_get_char (&i->input)) != EOF)
     {
       if (!c_isdigit (c))
         {
           data_warning (i, _("All characters in field must be digits."));
           return false;
         }
-      i->output->f = i->output->f * 10.0 + (c - '0'); 
+      i->output->f = i->output->f * 10.0 + (c - '0');
     }
 
   apply_implied_decimals (i);
@@ -350,7 +341,7 @@ parse_PIBHEX (struct data_in *i)
         }
       n = n * 16.0 + hexit_value (c);
     }
-  
+
   i->output->f = n;
   return true;
 }
@@ -363,7 +354,7 @@ parse_RBHEX (struct data_in *i)
   size_t j;
 
   memset (&d, 0, sizeof d);
-  for (j = 0; !ss_is_empty (i->input) && j < sizeof d; j++) 
+  for (j = 0; !ss_is_empty (i->input) && j < sizeof d; j++)
     {
       int hi = ss_get_char (&i->input);
       int lo = ss_get_char (&i->input);
@@ -377,11 +368,11 @@ parse_RBHEX (struct data_in *i)
          data_warning (i, _("Field must contain only hex digits."));
          return false;
        }
-      ((unsigned char *) &d)[j] = 16 * hexit_value (hi) + hexit_value (lo); 
+      ((unsigned char *) &d)[j] = 16 * hexit_value (hi) + hexit_value (lo);
     }
-  
+
   i->output->f = d;
-  
+
   return true;
 }
 
@@ -390,7 +381,7 @@ static const char z_digits[] = "0123456789{ABCDEFGHI}JKLMNOPQR";
 
 /* Returns true if C is a Z format digit, false otherwise. */
 static bool
-is_z_digit (int c) 
+is_z_digit (int c)
 {
   return c > 0 && strchr (z_digits, c) != NULL;
 }
@@ -407,7 +398,7 @@ z_digit_value (int c)
 /* Returns true if Z format digit C represents a negative value,
    false otherwise. */
 static bool
-is_negative_z_digit (int c) 
+is_negative_z_digit (int c)
 {
   assert (is_z_digit (c));
   return (strchr (z_digits, c) - z_digits) >= 20;
@@ -423,7 +414,7 @@ parse_Z (struct data_in *i)
 
   bool got_dot = false;
   bool got_final_digit = false;
+
   /* Trim spaces and check for missing value representation. */
   if (trim_spaces_and_check_missing (i))
     return true;
@@ -437,22 +428,22 @@ parse_Z (struct data_in *i)
       int c = ss_get_char (&i->input);
       if (c_isdigit (c) && !got_final_digit)
         ds_put_char (&tmp, c);
-      else if (is_z_digit (c) && !got_final_digit) 
+      else if (is_z_digit (c) && !got_final_digit)
         {
           ds_put_char (&tmp, z_digit_value (c) + '0');
           if (is_negative_z_digit (c))
             ds_data (&tmp)[0] = '-';
           got_final_digit = true;
         }
-      else if (c == '.' && !got_dot) 
+      else if (c == '.' && !got_dot)
         {
           ds_put_char (&tmp, '.');
-          got_dot = true; 
+          got_dot = true;
         }
-      else 
+      else
         {
           ds_destroy (&tmp);
-          return false; 
+          return false;
         }
     }
 
@@ -466,24 +457,24 @@ parse_Z (struct data_in *i)
       return false;
     }
 
-  /* Let strtod() do the conversion. */
+  /* Let c_strtod() do the conversion. */
   save_errno = errno;
   errno = 0;
-  i->output->f = strtod (ds_cstr (&tmp), NULL);
-  if (errno == ERANGE) 
+  i->output->f = c_strtod (ds_cstr (&tmp), NULL);
+  if (errno == ERANGE)
     {
       if (fabs (i->output->f) > 1)
         {
           data_warning (i, _("Too-large number set to system-missing."));
           i->output->f = SYSMIS;
         }
-      else 
+      else
         {
           data_warning (i, _("Too-small number set to zero."));
-          i->output->f = 0.0; 
+          i->output->f = 0.0;
         }
     }
-  else 
+  else
     {
       errno = save_errno;
       if (!got_dot)
@@ -503,16 +494,16 @@ parse_IB (struct data_in *i)
   uint64_t sign_bit;
 
   bytes = MIN (8, ss_length (i->input));
-  value = integer_get (input_integer_format, ss_data (i->input), bytes);
+  value = integer_get (settings_get_input_integer_format (), ss_data (i->input), bytes);
 
   sign_bit = UINT64_C(1) << (8 * bytes - 1);
-  if (!(value & sign_bit)) 
+  if (!(value & sign_bit))
     i->output->f = value;
-  else 
+  else
     {
       /* Sign-extend to full 64 bits. */
-      value -= sign_bit << 1; 
-      i->output->f = -(double) -value;       
+      value -= sign_bit << 1;
+      i->output->f = -(double) -value;
     }
 
   apply_implied_decimals (i);
@@ -524,9 +515,9 @@ parse_IB (struct data_in *i)
 static bool
 parse_PIB (struct data_in *i)
 {
-  i->output->f = integer_get (input_integer_format, ss_data (i->input),
+  i->output->f = integer_get (settings_get_input_integer_format (), ss_data (i->input),
                               MIN (8, ss_length (i->input)));
-  
+
   apply_implied_decimals (i);
 
   return true;
@@ -535,7 +526,7 @@ parse_PIB (struct data_in *i)
 /* Consumes the first character of S.  Stores its high 4 bits in
    HIGH_NIBBLE and its low 4 bits in LOW_NIBBLE. */
 static void
-get_nibbles (struct substring *s, int *high_nibble, int *low_nibble) 
+get_nibbles (struct substring *s, int *high_nibble, int *low_nibble)
 {
   int c = ss_get_char (s);
   assert (c != EOF);
@@ -548,7 +539,7 @@ static bool
 parse_P (struct data_in *i)
 {
   int high_nibble, low_nibble;
-  
+
   i->output->f = 0.0;
 
   while (ss_length (i->input) > 1)
@@ -563,7 +554,7 @@ parse_P (struct data_in *i)
   if (high_nibble > 9)
     return false;
   i->output->f = (10 * i->output->f) + high_nibble;
-  if (low_nibble < 10) 
+  if (low_nibble < 10)
     i->output->f = (10 * i->output->f) + low_nibble;
   else if (low_nibble == 0xb || low_nibble == 0xd)
     i->output->f = -i->output->f;
@@ -600,9 +591,10 @@ parse_PK (struct data_in *i)
 static bool
 parse_RB (struct data_in *i)
 {
-  size_t size = float_get_size (input_float_format);
+  enum float_format ff = settings_get_input_float_format ();
+  size_t size = float_get_size (ff);
   if (ss_length (i->input) >= size)
-    float_convert (input_float_format, ss_data (i->input),
+    float_convert (ff, ss_data (i->input),
                    FLOAT_NATIVE_DOUBLE, &i->output->f);
   else
     i->output->f = SYSMIS;
@@ -614,8 +606,17 @@ parse_RB (struct data_in *i)
 static bool
 parse_A (struct data_in *i)
 {
-  buf_copy_rpad (i->output->s, i->width,
-                 ss_data (i->input), ss_length (i->input));
+  /* This is equivalent to buf_copy_rpad, except that we posibly
+     do a character set recoding in the middle. */
+  char *dst = value_str_rw (i->output, i->width);
+  size_t dst_size = i->width;
+  const char *src = ss_data (i->input);
+  size_t src_size = ss_length (i->input);
+
+  legacy_recode (i->encoding, src, LEGACY_NATIVE, dst, MIN (src_size, dst_size));
+  if (dst_size > src_size)
+    memset (&dst[src_size], ' ', dst_size - src_size);
+
   return true;
 }
 
@@ -623,9 +624,10 @@ parse_A (struct data_in *i)
 static bool
 parse_AHEX (struct data_in *i)
 {
+  char *s = value_str_rw (i->output, i->width);
   size_t j;
-  
-  for (j = 0; ; j++) 
+
+  for (j = 0; ; j++)
     {
       int hi = ss_get_char (&i->input);
       int lo = ss_get_char (&i->input);
@@ -637,25 +639,30 @@ parse_AHEX (struct data_in *i)
           return false;
         }
 
+      if (0 != strcmp (i->encoding, LEGACY_NATIVE))
+        {
+          hi = legacy_to_native (i->encoding, hi);
+          lo = legacy_to_native (i->encoding, lo);
+        }
       if (!c_isxdigit (hi) || !c_isxdigit (lo))
        {
          data_warning (i, _("Field must contain only hex digits."));
          return false;
        }
-      
+
       if (j < i->width)
-        i->output->s[j] = hexit_value (hi) * 16 + hexit_value (lo);
+        s[j] = hexit_value (hi) * 16 + hexit_value (lo);
     }
 
-  memset (i->output->s + j, ' ', i->width - j);
-  
+  memset (&s[j], ' ', i->width - j);
+
   return true;
 }
 \f
 /* Date & time format components. */
 
 /* Sign of a time value. */
-enum time_sign 
+enum time_sign
   {
     SIGN_NO_TIME,       /* No time yet encountered. */
     SIGN_POSITIVE,      /* Positive time. */
@@ -671,12 +678,12 @@ parse_int (struct data_in *i, long *result, size_t max_digits)
 {
   struct substring head = ss_head (i->input, max_digits);
   size_t n = ss_get_long (&head, result);
-  if (n) 
+  if (n)
     {
       ss_advance (&i->input, n);
       return true;
     }
-  else 
+  else
     {
       data_warning (i, _("Syntax error in date field."));
       return false;
@@ -711,19 +718,19 @@ parse_time_units (struct data_in *i, double seconds_per_unit,
 {
   long units;
 
-  if (*time_sign == SIGN_NO_TIME) 
+  if (*time_sign == SIGN_NO_TIME)
     {
       if (ss_match_char (&i->input, '-'))
         *time_sign = SIGN_NEGATIVE;
       else
         {
           ss_match_char (&i->input, '+');
-          *time_sign = SIGN_POSITIVE; 
+          *time_sign = SIGN_POSITIVE;
         }
     }
   if (!parse_int (i, &units, SIZE_MAX))
     return false;
-  if (units < 0) 
+  if (units < 0)
     {
       data_warning (i, _("Syntax error in date field."));
       return false;
@@ -753,7 +760,7 @@ parse_spaces (struct data_in *i)
 }
 
 static struct substring
-parse_name_token (struct data_in *i) 
+parse_name_token (struct data_in *i)
 {
   struct substring token;
   ss_get_chars (&i->input, ss_span (i->input, ss_cstr (CC_LETTERS)), &token);
@@ -766,17 +773,17 @@ parse_name_token (struct data_in *i)
    exact matches (except for case) are allowed.
    Returns true if successful, false otherwise. */
 static bool
-match_name (struct substring token, const char **names, long *output) 
+match_name (struct substring token, const char *const *names, long *output)
 {
   int i;
 
-  for (i = 1; *names != NULL; i++) 
+  for (i = 1; *names != NULL; i++)
     if (ss_equals_case (ss_cstr (*names++), token))
       {
         *output = i;
         return true;
       }
-  
+
   return false;
 }
 
@@ -793,16 +800,16 @@ parse_month (struct data_in *i, long *month)
       if (*month >= 1 && *month <= 12)
         return true;
     }
-  else 
+  else
     {
-      static const char *english_names[] = 
+      static const char *const english_names[] =
         {
           "jan", "feb", "mar", "apr", "may", "jun",
           "jul", "aug", "sep", "oct", "nov", "dec",
           NULL,
         };
-  
-      static const char *roman_names[] = 
+
+      static const char *const roman_names[] =
         {
           "i", "ii", "iii", "iv", "v", "vi",
           "vii", "viii", "ix", "x", "xi", "xii",
@@ -828,16 +835,16 @@ parse_year (struct data_in *i, long *year, size_t max_digits)
 {
   if (!parse_int (i, year, max_digits))
     return false;
-  
-  if (*year >= 0 && *year <= 99) 
+
+  if (*year >= 0 && *year <= 99)
     {
-      int epoch = get_epoch ();
+      int epoch = settings_get_epoch ();
       int epoch_century = ROUND_DOWN (epoch, 100);
       int epoch_offset = epoch - epoch_century;
       if (*year >= epoch_offset)
         *year += epoch_century;
       else
-        *year += epoch_century + 100; 
+        *year += epoch_century + 100;
     }
   if (*year >= 1582 || *year <= 19999)
     return true;
@@ -853,7 +860,7 @@ parse_trailer (struct data_in *i)
 {
   if (ss_is_empty (i->input))
     return true;
-  
+
   data_warning (i, _("Trailing garbage \"%.*s\" following date."),
               (int) ss_length (i->input), ss_data (i->input));
   return false;
@@ -890,13 +897,13 @@ static bool
 parse_quarter (struct data_in *i, long int *month)
 {
   long quarter;
-  
+
   if (!parse_int (i, &quarter, SIZE_MAX))
     return false;
-  if (quarter >= 1 && quarter <= 4) 
+  if (quarter >= 1 && quarter <= 4)
     {
       *month = (quarter - 1) * 3 + 1;
-      return true; 
+      return true;
     }
 
   data_warning (i, _("Quarter (%ld) must be between 1 and 4."), quarter);
@@ -910,13 +917,13 @@ static bool
 parse_week (struct data_in *i, long int *yday)
 {
   long week;
-  
+
   if (!parse_int (i, &week, SIZE_MAX))
     return false;
-  if (week >= 1 && week <= 53) 
+  if (week >= 1 && week <= 53)
     {
       *yday = (week - 1) * 7 + 1;
-      return true; 
+      return true;
     }
 
   data_warning (i, _("Week (%ld) must be between 1 and 53."), week);
@@ -931,14 +938,14 @@ parse_time_delimiter (struct data_in *i)
 {
   if (ss_ltrim (&i->input, ss_cstr (":" CC_SPACES)) > 0)
     return true;
-  
+
   data_warning (i, _("Delimiter expected between fields in time."));
   return false;
 }
 
 /* Parses minutes and optional seconds from the beginning of I.
    The time is converted into seconds, which are added to
-   *TIME. 
+   *TIME.
    Returns true if successful, false if an error was found. */
 static bool
 parse_minute_second (struct data_in *i, double *time)
@@ -950,10 +957,10 @@ parse_minute_second (struct data_in *i, double *time)
   /* Parse minutes. */
   if (!parse_int (i, &minute, SIZE_MAX))
     return false;
-  if (minute < 0 || minute > 59) 
+  if (minute < 0 || minute > 59)
     {
       data_warning (i, _("Minute (%ld) must be between 0 and 59."), minute);
-      return false; 
+      return false;
     }
   *time += 60. * minute;
 
@@ -966,12 +973,12 @@ parse_minute_second (struct data_in *i, double *time)
   cp = buf;
   while (c_isdigit (ss_first (i->input)))
     *cp++ = ss_get_char (&i->input);
-  if (ss_match_char (&i->input, fmt_decimal_char (FMT_F)))
+  if (ss_match_char (&i->input, settings_get_decimal_char (FMT_F)))
     *cp++ = '.';
   while (c_isdigit (ss_first (i->input)))
     *cp++ = ss_get_char (&i->input);
   *cp = '\0';
-  
+
   *time += strtod (buf, NULL);
 
   return true;
@@ -983,7 +990,7 @@ parse_minute_second (struct data_in *i, double *time)
 static bool
 parse_weekday (struct data_in *i, long *weekday)
 {
-  static const char *weekday_names[] = 
+  static const char *const weekday_names[] =
     {
       "su", "mo", "tu", "we", "th", "fr", "sa",
       NULL,
@@ -1003,7 +1010,7 @@ parse_weekday (struct data_in *i, long *weekday)
 /* Helper function for passing to
    calendar_gregorian_to_offset. */
 static void
-calendar_error (void *i_, const char *format, ...) 
+calendar_error (void *i_, const char *format, ...)
 {
   struct data_in *i = i_;
   va_list args;
@@ -1070,11 +1077,11 @@ parse_date (struct data_in *i)
       unsigned char ch = *template;
       int count = 1;
       bool ok;
-      
-      while (template[count] == ch) 
+
+      while (template[count] == ch)
         count++;
       template += count;
-      
+
       ok = true;
       switch (ch)
         {
@@ -1084,19 +1091,19 @@ parse_date (struct data_in *i)
         case 'm':
           ok = parse_month (i, &month);
           break;
-        case 'y': 
+        case 'y':
           {
             size_t max_digits;
             if (!c_isalpha (*template))
               max_digits = SIZE_MAX;
-            else 
+            else
               {
                 if (ss_length (i->input) >= template_width + 2)
                   max_digits = 4;
                 else
-                  max_digits = 2; 
+                  max_digits = 2;
               }
-            ok = parse_year (i, &year, max_digits); 
+            ok = parse_year (i, &year, max_digits);
           }
           break;
         case 'q':
@@ -1141,7 +1148,7 @@ parse_date (struct data_in *i)
   if (!parse_trailer (i))
     return false;
 
-  if (year != INT_MIN) 
+  if (year != INT_MIN)
     {
       double ofs = calendar_gregorian_to_offset (year, month, day,
                                                  calendar_error, i);
@@ -1168,13 +1175,13 @@ vdata_warning (const struct data_in *i, const char *format, va_list args)
 
   ds_init_empty (&text);
   ds_put_char (&text, '(');
-  if (i->first_column != 0) 
+  if (i->first_column != 0)
     {
-      if (i->first_column == i->last_column)
+      if (i->first_column == i->last_column - 1)
         ds_put_format (&text, _("column %d"), i->first_column);
       else
         ds_put_format (&text, _("columns %d-%d"),
-                       i->first_column, i->last_column);
+                       i->first_column, i->last_column - 1);
       ds_put_cstr (&text, ", ");
     }
   ds_put_format (&text, _("%s field) "), fmt_name (i->format));
@@ -1190,7 +1197,7 @@ vdata_warning (const struct data_in *i, const char *format, va_list args)
 /* Outputs FORMAT with the given ARGS as a warning for input
    I. */
 static void
-data_warning (const struct data_in *i, const char *format, ...) 
+data_warning (const struct data_in *i, const char *format, ...)
 {
   va_list args;
 
@@ -1201,7 +1208,7 @@ data_warning (const struct data_in *i, const char *format, ...)
 
 /* Apply implied decimal places to output. */
 static void
-apply_implied_decimals (struct data_in *i) 
+apply_implied_decimals (struct data_in *i)
 {
   if (i->implied_decimals > 0)
     i->output->f /= pow (10., i->implied_decimals);
@@ -1215,9 +1222,9 @@ static void
 default_result (struct data_in *i)
 {
   if (fmt_is_string (i->format))
-    memset (i->output->s, ' ', i->width);
+    memset (value_str_rw (i->output, i->width), ' ', i->width);
   else
-    i->output->f = get_blanks ();
+    i->output->f = settings_get_blanks ();
 }
 
 /* Trims leading and trailing spaces from I.
@@ -1225,7 +1232,7 @@ default_result (struct data_in *i)
    sets the default result and returns true; otherwise, returns
    false. */
 static bool
-trim_spaces_and_check_missing (struct data_in *i) 
+trim_spaces_and_check_missing (struct data_in *i)
 {
   ss_trim (&i->input, ss_cstr (" "));
   if (ss_is_empty (i->input) || ss_equals (i->input, ss_cstr (".")))