calendar: Use sensible error reporting in calendar_gregorian_to_offset().
[pspp] / src / data / data-in.c
index 28d6da2a42b7b2629779838b28e80000e28f90f0..2b842b8dcd93eb71fd5324eda0c4d7bd7c8c5c8c 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2006 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2006, 2009, 2010 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
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdbool.h>
+#include <limits.h>
 
 #include "calendar.h"
 #include "identifier.h"
 #include "settings.h"
 #include "value.h"
 #include "format.h"
+#include "dictionary.h"
 
 #include <libpspp/assertion.h>
 #include <libpspp/legacy-encoding.h>
+#include <libpspp/i18n.h>
 #include <libpspp/compiler.h>
 #include <libpspp/integer-format.h>
 #include <libpspp/message.h>
@@ -52,7 +55,7 @@
 /* Information about parsing one data field. */
 struct data_in
   {
-    enum legacy_encoding encoding;/* Encoding of source. */
+    const char *src_enc;        /* Encoding of source. */
     struct substring input;     /* Source. */
     enum fmt_type format;       /* Input format. */
     int implied_decimals;       /* Number of implied decimal places. */
@@ -71,8 +74,6 @@ typedef bool data_in_parser_func (struct data_in *);
         static data_in_parser_func parse_##METHOD;
 #include "format.def"
 
-static void vdata_warning (const struct data_in *, const char *, va_list)
-     PRINTF_FORMAT (2, 0);
 static void data_warning (const struct data_in *, const char *, ...)
      PRINTF_FORMAT (2, 3);
 
@@ -84,8 +85,12 @@ static int hexit_value (int c);
 \f
 /* Parses the characters in INPUT, which are encoded in the given
    ENCODING, according to FORMAT.  Stores the parsed
-   representation in OUTPUT, which has the given WIDTH (0 for
-   a numeric field, otherwise the string width).
+   representation in OUTPUT, which the caller must have
+   initialized with the given WIDTH (0 for a numeric field,
+   otherwise the string width).
+   Iff FORMAT is a string format, then DICT must be a pointer
+   to the dictionary associated with OUTPUT.  Otherwise, DICT
+   may be null.
 
    If no decimal point is included in a numeric format, then
    IMPLIED_DECIMALS decimal places are implied.  Specify 0 if no
@@ -98,9 +103,11 @@ static int hexit_value (int c);
    FIRST_COLUMN plus the length of the input because of the
    possibility of escaped quotes in strings, etc.) */
 bool
-data_in (struct substring input, enum legacy_encoding encoding,
+data_in (struct substring input, const char *encoding,
          enum fmt_type format, int implied_decimals,
-         int first_column, int last_column, union value *output, int width)
+         int first_column, int last_column,
+        const struct dictionary *dict,
+        union value *output, int width)
 {
   static data_in_parser_func *const handlers[FMT_NUMBER_OF_FORMATS] =
     {
@@ -109,25 +116,12 @@ data_in (struct substring input, enum legacy_encoding encoding,
     };
 
   struct data_in i;
-  void *copy = NULL;
+
+  char *s = NULL;
   bool ok;
 
   assert ((width != 0) == fmt_is_string (format));
 
-  if (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;
 
@@ -136,22 +130,39 @@ data_in (struct substring input, enum legacy_encoding encoding,
 
   i.first_column = first_column;
   i.last_column = last_column;
+  i.src_enc = encoding;
 
-  if (!ss_is_empty (i.input))
+  if (ss_is_empty (input))
     {
-      ok = handlers[i.format] (&i);
-      if (!ok)
-        default_result (&i);
+      default_result (&i);
+      return true;
+    }
+
+  if (fmt_get_category (format) & ( FMT_CAT_BINARY | FMT_CAT_HEXADECIMAL | FMT_CAT_LEGACY))
+    {
+      i.input = input;
     }
   else
     {
-      default_result (&i);
-      ok = true;
+      const char *dest_encoding;
+
+      if ( dict == NULL)
+       {
+         assert (0 == (fmt_get_category (format) & (FMT_CAT_BINARY | FMT_CAT_STRING)));
+         dest_encoding = LEGACY_NATIVE;
+       }
+      else
+       dest_encoding = dict_get_encoding (dict);
+
+      s = recode_string (dest_encoding, i.src_enc, ss_data (input), ss_length (input));
+      i.input = ss_cstr (s);
     }
 
-  if (copy)
-    free (copy);
+  ok = handlers[i.format] (&i);
+  if (!ok)
+    default_result (&i);
 
+  free (s);
   return ok;
 }
 
@@ -606,12 +617,13 @@ parse_A (struct data_in *i)
 {
   /* This is equivalent to buf_copy_rpad, except that we posibly
      do a character set recoding in the middle. */
-  char *dst = i->output->s;
+  uint8_t *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));
+  memcpy (dst, src, MIN (src_size, dst_size));
+
   if (dst_size > src_size)
     memset (&dst[src_size], ' ', dst_size - src_size);
 
@@ -622,6 +634,7 @@ parse_A (struct data_in *i)
 static bool
 parse_AHEX (struct data_in *i)
 {
+  uint8_t *s = value_str_rw (i->output, i->width);
   size_t j;
 
   for (j = 0; ; j++)
@@ -636,10 +649,10 @@ parse_AHEX (struct data_in *i)
           return false;
         }
 
-      if (i->encoding != LEGACY_NATIVE)
+      if (0 != strcmp (i->src_enc, LEGACY_NATIVE))
         {
-          hi = legacy_to_native (i->encoding, hi);
-          lo = legacy_to_native (i->encoding, lo);
+          hi = legacy_to_native (i->src_enc, hi);
+          lo = legacy_to_native (i->src_enc, lo);
         }
       if (!c_isxdigit (hi) || !c_isxdigit (lo))
        {
@@ -648,10 +661,10 @@ parse_AHEX (struct data_in *i)
        }
 
       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;
 }
@@ -858,7 +871,7 @@ parse_trailer (struct data_in *i)
   if (ss_is_empty (i->input))
     return true;
 
-  data_warning (i, _("Trailing garbage \"%.*s\" following date."),
+  data_warning (i, _("Trailing garbage `%.*s' following date."),
               (int) ss_length (i->input), ss_data (i->input));
   return false;
 }
@@ -1004,19 +1017,6 @@ parse_weekday (struct data_in *i, long *weekday)
 \f
 /* Date & time formats. */
 
-/* Helper function for passing to
-   calendar_gregorian_to_offset. */
-static void
-calendar_error (void *i_, const char *format, ...)
-{
-  struct data_in *i = i_;
-  va_list args;
-
-  va_start (args, format);
-  vdata_warning (i, format, args);
-  va_end (args);
-}
-
 /* Parses WKDAY format. */
 static bool
 parse_WKDAY (struct data_in *i)
@@ -1147,10 +1147,16 @@ parse_date (struct data_in *i)
 
   if (year != INT_MIN)
     {
-      double ofs = calendar_gregorian_to_offset (year, month, day,
-                                                 calendar_error, i);
+      char *error;
+      double ofs;
+
+      ofs = calendar_gregorian_to_offset (year, month, day, &error);
       if (ofs == SYSMIS)
-        return false;
+        {
+          data_warning (i, "%s", error);
+          free (error);
+          return false;
+        }
       date = (yday - 1 + ofs) * 60. * 60. * 24.;
     }
   else
@@ -1162,47 +1168,33 @@ parse_date (struct data_in *i)
 \f
 /* Utility functions. */
 
-/* Outputs FORMAT with the given ARGS as a warning for input
-   I. */
+/* Outputs FORMAT with as a warning for input I. */
 static void
-vdata_warning (const struct data_in *i, const char *format, va_list args)
+data_warning (const struct data_in *i, const char *format, ...)
 {
+  va_list args;
   struct msg m;
   struct string text;
 
   ds_init_empty (&text);
   ds_put_char (&text, '(');
-  if (i->first_column != 0)
-    {
-      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 - 1);
-      ds_put_cstr (&text, ", ");
-    }
   ds_put_format (&text, _("%s field) "), fmt_name (i->format));
+
+  va_start (args, format);
   ds_put_vformat (&text, format, args);
+  va_end (args);
 
-  m.category = MSG_DATA;
-  m.severity = MSG_WARNING;
+  m.category = MSG_C_DATA;
+  m.severity = MSG_S_WARNING;
   m.text = ds_cstr (&text);
+  m.where.file_name = NULL;
+  m.where.line_number = 0;
+  m.where.first_column = i->first_column;
+  m.where.last_column = i->last_column;
 
   msg_emit (&m);
 }
 
-/* Outputs FORMAT with the given ARGS as a warning for input
-   I. */
-static void
-data_warning (const struct data_in *i, const char *format, ...)
-{
-  va_list args;
-
-  va_start (args, format);
-  vdata_warning (i, format, args);
-  va_end (args);
-}
-
 /* Apply implied decimal places to output. */
 static void
 apply_implied_decimals (struct data_in *i)
@@ -1219,7 +1211,7 @@ 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 = settings_get_blanks ();
 }