# HG changeset patch # User Pascal Dupuis # Date 1326922829 18000 # Node ID 238e499c5fea79c62dc3675baadeb968ba8a2554 # Parent 67a0ab9f8283b14f33e161c6bf40ff08fd991045 locale support implementation in [fs]scanf functions Implement the ability to scan "1,2" and get 1.2 as results under French locale, where the decimal separator is ','. This uses the C++ standard library locale approach: the interpretation of some stream locale is modified using the "imbue" function. In case of fscanf, the previous locale is restored after each call. The functions interface is not modified. When the last argument is a string, its value is used as the new locale setting. * src/oct-stream.h (octave_base_stream::imbue): new function, which merely ignores its argument. (octave_stream::imbue): add a real implementation, which is a proxy to std::[io]stream.imbue. * src/file_io.cc: (fscanf): when the last argument is a string, save the actual locale value and set a new value from this string. Generate a warning in case the requested locale is not supported. Revert to previous locale if OK, or to standard locale in case of problem. (scanf): Likewise, except the stream is generated on-the-fly, there is no need to restore its locale value. Added a test case. Updated the doc for both functions. diff --git a/src/file-io.cc b/src/file-io.cc --- a/src/file-io.cc +++ b/src/file-io.cc @@ -43,7 +43,9 @@ #include #include +#include #include +#include #include #include @@ -1070,7 +1072,7 @@ DEFUN (fscanf, args, , "-*- texinfo -*-\n\ @deftypefn {Built-in Function} {[@var{val}, @var{count}, @var{errmsg}] =} fscanf (@var{fid}, @var{template}, @var{size})\n\ -@deftypefnx {Built-in Function} {[@var{v1}, @var{v2}, @dots{}, @var{count}, @var{errmsg}] =} fscanf (@var{fid}, @var{template}, \"C\")\n\ ++@deftypefnx {Built-in Function} {[@var{v1}, @var{v2}, @dots{}, @var{count}] =} fscanf (@var{fid}, @var{template}, @var{locale})\n\ In the first form, read from @var{fid} according to @var{template},\n\ returning the result in the matrix @var{val}.\n\ \n\ @@ -1109,7 +1111,10 @@ with each conversion specifier in @var{template} corresponding to a\n\ single scalar return value. This form is more `C-like', and also\n\ compatible with previous versions of Octave. The number of successful\n\ -conversions is returned in @var{count}\n\ +conversions is returned in @var{count}. It permits to explicitly\n\ +specify a locale to take into account langage specific features, \n\ +such as decimal separator. This operation restores the previous locales\n\ +setting at the end of the conversion.\n\ @ifclear OCTAVE_MANUAL\n\ \n\ See the Formatted Input section of the GNU Octave manual for a\n\ @@ -1131,7 +1136,25 @@ if (! error_state) { if (args(1).is_string ()) - retval = os.oscanf (args(1), who); + { + std::locale oldloc; + try + { + // Use args(2) val as the new locale setting. Keep + // old val for restoring afterwards. + oldloc = + os.imbue (std::locale (args(2).string_value ().c_str ())); + + } + catch (std::runtime_error) + { + // Display a warning if the specified locale is unknown + warning ("fscanf: invalid locale. Try `locale -a' for a list of supported values."); + oldloc = std::locale::classic (); + } + retval = os.oscanf (args(1), who); + os.imbue (oldloc); + } else ::error ("%s: format TEMPLATE must be a string", who.c_str ()); } @@ -1199,7 +1222,7 @@ DEFUN (sscanf, args, , "-*- texinfo -*-\n\ @deftypefn {Built-in Function} {[@var{val}, @var{count}, @var{errmsg}, @var{pos}] =} sscanf (@var{string}, @var{template}, @var{size})\n\ -@deftypefnx {Built-in Function} {[@var{v1}, @var{v2}, @dots{}, @var{count}, @var{errmsg}] =} sscanf (@var{string}, @var{template}, \"C\")\n\ +@deftypefnx {Built-in Function} {[@var{v1}, @var{v2}, @dots{}, @var{count}] =} sscanf (@var{string}, @var{template}, @var{locale})\n\ This is like @code{fscanf}, except that the characters are taken from the\n\ string @var{string} instead of from a stream. Reaching the end of the\n\ string is treated as an end-of-file condition. In addition to the values\n\ @@ -1224,8 +1247,22 @@ if (os.is_valid ()) { - if (args(1).is_string ()) - retval = os.oscanf (args(1), who); + if (args(1).is_string ()) + { + // Use args(2) val as the new locale setting. As the os + // object is short lived, we don't need to restore + // locale afterwards. + try + { + os.imbue (std::locale (args(2).string_value ().c_str ())); + } + catch (std::runtime_error) + { + // Display a warning if the specified locale is unknown + warning ("sscanf: invalid locale. Try `locale -a' for a list of supported values."); + } + retval = os.oscanf (args(1), who); + } else ::error ("%s: format TEMPLATE must be a string", who.c_str ()); } @@ -1293,10 +1330,16 @@ return retval; } +/* +%!test +%! assert(sscanf('1,2', '%f', 'C'), 1) +%! assert(sscanf('1,2', '%f', 'fr_FR'), 1.2) +*/ + DEFUN (scanf, args, nargout, "-*- texinfo -*-\n\ @deftypefn {Built-in Function} {[@var{val}, @var{count}, @var{errmsg}] =} scanf (@var{template}, @var{size})\n\ -@deftypefnx {Built-in Function} {[@var{v1}, @var{v2}, @dots{}, @var{count}, @var{errmsg}]] =} scanf (@var{template}, \"C\")\n\ +@deftypefnx {Built-in Function} {[@var{v1}, @var{v2}, @dots{}, @var{count}]] =} scanf (@var{template}, @var{locale}))\n\ This is equivalent to calling @code{fscanf} with @var{fid} = @code{stdin}.\n\ \n\ It is currently not useful to call @code{scanf} in interactive\n\ diff --git a/src/oct-stream.h b/src/oct-stream.h --- a/src/oct-stream.h +++ b/src/oct-stream.h @@ -371,6 +371,12 @@ virtual std::ostream *output_stream (void) { return 0; } + // If the derived class is locale-aware, it must implement this function + // in order to set a new locale. By default, this function avoids messing + // with locales and ignores its input argument. + virtual std::locale imbue ( const std::locale & loc ) + { return std::locale::classic (); } + // Return TRUE if this stream is open. bool is_open (void) const { return open_state; } @@ -613,7 +619,23 @@ { return rep ? rep->output_stream () : 0; } - + + std::locale imbue (const std::locale & loc ) + { + if (!rep) return std::locale::classic (); + + std::istream *is = rep->input_stream (); + std::ostream *os = rep->output_stream (); + + if (os) + { + if (is) + (void) is->imbue (loc); + return os->imbue (loc); + } + return is ? is->imbue (loc) : std::locale::classic (); + } + void clearerr (void) { if (rep) rep->clearerr (); } private: