1
0
mirror of https://git.zap.org.au/git/trader.git synced 2024-12-04 14:46:45 -05:00

Rename l_strfmon() and rewrite as xstrfmon() to handle POSIX locale better

Current and previous versions of ISO/IEC 9945-1 (POSIX), particularly SUSv3
(2001) and SUSv4 (2008), require strfmon() to return rather meaningless
strings when used with the POSIX "C" locale.  In particular, the standard
POSIX locale does not define a currency symbol, a monetary radix symbol
(decimal point) or a negative sign.  This means strfmon(..., "%n", -123.45)
is supposed to produce "12345" instead of something like "$-123.45"!

The new xstrfmon() overcomes these limitations by using snprintf() as
appropriate.
This commit is contained in:
John Zaitseff 2018-08-21 21:55:21 +10:00
parent 827db8a537
commit 1e0285f6c0
4 changed files with 116 additions and 75 deletions

4
NEWS
View File

@ -18,6 +18,10 @@ location: https://www.zap.org.au/gitweb/trader.git
Version 7.13 (not yet released) Version 7.13 (not yet released)
------------------------------- -------------------------------
Modified the code to work more reliably on non-Glibc (GNU C Library) plat-
forms, particularly FreeBSD, Solaris and Cygwin, when using the standard
POSIX locale.
Added the Spanish translation, with thanks to Francisco Javier Serrador. Added the Spanish translation, with thanks to Francisco Javier Serrador.
Updated the Esperanto translation, with thanks to Felipe Castro. Updated the Esperanto translation, with thanks to Felipe Castro.

View File

@ -1472,8 +1472,8 @@ int vmkchstr (chtype *restrict chbuf, int chbufsize, chtype attr_norm,
version, so we need a multibyte char buffer */ version, so we need a multibyte char buffer */
char *buf = xmalloc(BUFSIZE); char *buf = xmalloc(BUFSIZE);
if (l_strfmon(buf, BUFSIZE, spec->flag_nosym ? "%!n" : "%n", if (xstrfmon(buf, BUFSIZE, spec->flag_nosym ? "%!n" : "%n",
format_arg[spec->arg_num].a.a_double) < 0) { format_arg[spec->arg_num].a.a_double) < 0) {
saved_errno = errno; saved_errno = errno;
free(buf); free(buf);
errno = saved_errno; errno = saved_errno;

View File

@ -56,11 +56,50 @@ wchar_t *mon_thousands_sep; // Local monetary thousands separator
#define GAME_FILENAME_PROTO "game%d" #define GAME_FILENAME_PROTO "game%d"
#define GAME_FILENAME_BUFSIZE 16 #define GAME_FILENAME_BUFSIZE 16
// Default values used to override POSIX locale // Values used to override the standard POSIX locale
#define MOD_POSIX_CURRENCY_SYMBOL "$" #define MOD_POSIX_DECIMAL_POINT "."
#define MOD_POSIX_FRAC_DIGITS 2 #define MOD_POSIX_THOUSANDS_SEP ""
#define MOD_POSIX_P_CS_PRECEDES 1 #define MOD_POSIX_GROUPING ""
#define MOD_POSIX_P_SEP_BY_SPACE 0 #define MOD_POSIX_INT_CURR_SYMBOL ""
#define MOD_POSIX_CURRENCY_SYMBOL "$" // Standard: ""
#define MOD_POSIX_MON_DECIMAL_POINT "." // Standard: ""
#define MOD_POSIX_MON_THOUSANDS_SEP ""
#define MOD_POSIX_MON_GROUPING ""
#define MOD_POSIX_POSITIVE_SIGN ""
#define MOD_POSIX_NEGATIVE_SIGN "-" // Standard: ""
#define MOD_POSIX_INT_FRAC_DIGITS 2 // Standard: CHAR_MAX
#define MOD_POSIX_FRAC_DIGITS 2 // Standard: CHAR_MAX
#define MOD_POSIX_P_CS_PRECEDES 1 // Standard: CHAR_MAX
#define MOD_POSIX_P_SEP_BY_SPACE 0 // Standard: CHAR_MAX
#define MOD_POSIX_N_CS_PRECEDES 1 // Standard: CHAR_MAX
#define MOD_POSIX_N_SEP_BY_SPACE 0 // Standard: CHAR_MAX
#define MOD_POSIX_P_SIGN_POSN 1 // Standard: CHAR_MAX
#define MOD_POSIX_N_SIGN_POSN 1 // Standard: CHAR_MAX
static const struct lconv mod_posix_lconv =
{
.decimal_point = MOD_POSIX_DECIMAL_POINT,
.thousands_sep = MOD_POSIX_THOUSANDS_SEP,
.grouping = MOD_POSIX_GROUPING,
.int_curr_symbol = MOD_POSIX_INT_CURR_SYMBOL,
.currency_symbol = MOD_POSIX_CURRENCY_SYMBOL,
.mon_decimal_point = MOD_POSIX_MON_DECIMAL_POINT,
.mon_thousands_sep = MOD_POSIX_MON_THOUSANDS_SEP,
.mon_grouping = MOD_POSIX_MON_GROUPING,
.positive_sign = MOD_POSIX_POSITIVE_SIGN,
.negative_sign = MOD_POSIX_NEGATIVE_SIGN,
.int_frac_digits = MOD_POSIX_INT_FRAC_DIGITS,
.frac_digits = MOD_POSIX_FRAC_DIGITS,
.p_cs_precedes = MOD_POSIX_P_CS_PRECEDES,
.p_sep_by_space = MOD_POSIX_P_SEP_BY_SPACE,
.n_cs_precedes = MOD_POSIX_N_CS_PRECEDES,
.n_sep_by_space = MOD_POSIX_N_SEP_BY_SPACE,
.p_sign_posn = MOD_POSIX_P_SIGN_POSN,
.n_sign_posn = MOD_POSIX_N_SIGN_POSN
// ISO/IEC 9945-1:2008 (SUSv4) defines additional fields, but
// this program does not use them.
};
// Constants used for scrambling and unscrambling game data // Constants used for scrambling and unscrambling game data
#define SCRAMBLE_CRC_LEN 8 // Length of CRC in ASCII (excl NUL) #define SCRAMBLE_CRC_LEN 8 // Length of CRC in ASCII (excl NUL)
@ -186,7 +225,7 @@ static const unsigned char xor_table[] = {
static char *home_directory_str = NULL; // Full pathname to home static char *home_directory_str = NULL; // Full pathname to home
static char *data_directory_str = NULL; // Writable data dir pathname static char *data_directory_str = NULL; // Writable data dir pathname
static bool add_currency_symbol = false; // Do we need to add "$"? static bool is_posix_locale = false; // Override strfmon()?
/************************************************************************ /************************************************************************
@ -486,22 +525,23 @@ void init_locale (void)
lconvinfo = *lc; lconvinfo = *lc;
add_currency_symbol = false; is_posix_locale = false;
/* Are we in the POSIX locale? The string returned by setlocale() is /* Are we in the POSIX locale? The string returned by setlocale() is
supposed to be opaque, but in practise is not. To be on the safe supposed to be opaque, but in practise is not. To be on the safe
side, we explicitly set the locale to "C", then test the returned side, we explicitly set the locale to "C", then test the returned
value of that, too. */ value of that, too. */
cloc = setlocale(LC_MONETARY, "C"); cloc = setlocale(LC_MONETARY, "C");
if ( strcmp(cur, cloc) == 0 if ( strcmp(cur, cloc) == 0
|| strcmp(cur, "POSIX") == 0 || strcmp(cur, "C") == 0 || strcmp(cur, "POSIX") == 0
|| strcmp(cur, "C.UTF-8") == 0 || strcmp(cur, "C.utf8") == 0) { || strcmp(cur, "POSIX.UTF-8") == 0
|| strcmp(cur, "POSIX.utf8") == 0
|| strcmp(cur, "C") == 0
|| strcmp(cur, "C.UTF-8") == 0
|| strcmp(cur, "C.utf8") == 0) {
add_currency_symbol = true; is_posix_locale = true;
lconvinfo.currency_symbol = MOD_POSIX_CURRENCY_SYMBOL; lconvinfo = mod_posix_lconv;
lconvinfo.frac_digits = MOD_POSIX_FRAC_DIGITS;
lconvinfo.p_cs_precedes = MOD_POSIX_P_CS_PRECEDES;
lconvinfo.p_sep_by_space = MOD_POSIX_P_SEP_BY_SPACE;
} }
// Convert localeconv() information to wide strings // Convert localeconv() information to wide strings
@ -531,62 +571,61 @@ void init_locale (void)
/***********************************************************************/ /***********************************************************************/
// l_strfmon: Convert monetary value to a string // xstrfmon: Convert monetary value to a string
ssize_t l_strfmon (char *restrict buf, size_t maxsize, ssize_t xstrfmon (char *restrict buf, size_t maxsize,
const char *restrict format, double val) const char *restrict format, double val)
{ {
/* The current implementation assumes MOD_POSIX_P_CS_PRECEDES is 1 /* Current and previous versions of ISO/IEC 9945-1 (POSIX), particularly
(currency symbol precedes value) and that MOD_POSIX_P_SEP_BY_SPACE SUSv3 (2001) and SUSv4 (2008), require strfmon() to return rather
is 0 (no space separates currency symbol and value). It does, meaningless strings when used with the POSIX "C" locale. In
however, handle currency symbols of length > 1. */ particular, the standard POSIX locale does not define a currency
symbol, a monetary radix symbol (decimal point) or a negative
sign. This means strfmon(..., "%n", -123.45) is supposed to
produce "12345" instead of something like "$-123.45"! This
function overcomes these limitations by using snprintf(). */
assert(MOD_POSIX_P_CS_PRECEDES == 1); if (! is_posix_locale) {
assert(MOD_POSIX_P_SEP_BY_SPACE == 0); return strfmon(buf, maxsize, format, val);
} else {
/* The current implementation assumes the monetary decimal point
is overridden to "." (ie, MOD_POSIX_MON_DECIMAL_POINT == "."),
the currency symbol is to precede the value, no spaces are to
separate currency symbol and value, and sign is to precede
both currency symbol and value. */
assert(MOD_POSIX_P_CS_PRECEDES == 1);
assert(MOD_POSIX_P_SEP_BY_SPACE == 0);
assert(MOD_POSIX_N_CS_PRECEDES == 1);
assert(MOD_POSIX_N_SEP_BY_SPACE == 0);
assert(MOD_POSIX_P_SIGN_POSN == 1);
assert(MOD_POSIX_N_SIGN_POSN == 1);
ssize_t ret = strfmon(buf, maxsize, format, val); # define MOD_POSIX_q(s) MOD_POSIX_qq(s)
# define MOD_POSIX_qq(s) #s
if (ret > 0 && add_currency_symbol) { # define MOD_POSIX_FMT_POS MOD_POSIX_POSITIVE_SIGN MOD_POSIX_CURRENCY_SYMBOL "%." MOD_POSIX_q(MOD_POSIX_FRAC_DIGITS) "f"
if (strstr(format, "!") == NULL) { # define MOD_POSIX_FMT_NEG MOD_POSIX_NEGATIVE_SIGN MOD_POSIX_CURRENCY_SYMBOL "%." MOD_POSIX_q(MOD_POSIX_FRAC_DIGITS) "f"
/* Insert lconvinfo.currency_symbol to s. # define MOD_POSIX_FMT_POS_NOSYM MOD_POSIX_POSITIVE_SIGN "%." MOD_POSIX_q(MOD_POSIX_FRAC_DIGITS) "f"
# define MOD_POSIX_FMT_NEG_NOSYM MOD_POSIX_NEGATIVE_SIGN "%." MOD_POSIX_q(MOD_POSIX_FRAC_DIGITS) "f"
NB: add_currency_symbol == true assumes a POSIX locale and if (strcmp(format, "%n") == 0) {
that the character encoding is ASCII-safe (such as by if (val >= 0.0) {
being ASCII itself, or UTF-8). */ return snprintf(buf, maxsize, MOD_POSIX_FMT_POS, val);
const char *sym = lconvinfo.currency_symbol;
int symlen = strlen(sym);
char *p;
int spc;
assert(maxsize > (unsigned int) symlen);
// Count number of leading spaces
for (p = buf, spc = 0; *p == ' '; p++, spc++)
;
if (symlen <= spc) {
/* Enough space for currency symbol: copy it WITHOUT
copying terminating NUL character */
for (p -= symlen; *sym != '\0'; p++, sym++) {
*p = *sym;
}
} else { } else {
// Make space for currency symbol, then copy it return snprintf(buf, maxsize, MOD_POSIX_FMT_NEG, -val);
memmove(buf + symlen - spc, buf, maxsize - (symlen - spc));
buf[maxsize - 1] = '\0';
for ( ; *sym != '\0'; sym++, buf++) {
// Make sure terminating NUL character is NOT copied!
*buf = *sym;
}
ret = MIN((unsigned int) ret + symlen - spc, maxsize - 1);
} }
} else if (strcmp(format, "%!n") == 0) {
if (val >= 0.0) {
return snprintf(buf, maxsize, MOD_POSIX_FMT_POS_NOSYM, val);
} else {
return snprintf(buf, maxsize, MOD_POSIX_FMT_NEG_NOSYM, -val);
}
} else {
// Other strfmon() formats are not supported
errno = EINVAL;
return -1;
} }
} }
return ret;
} }

View File

@ -239,21 +239,19 @@ extern void init_locale (void);
/* /*
Function: l_strfmon - Convert monetary value to a string Function: xstrfmon - Convert monetary value to a string
Parameters: buf - Buffer to receive result Parameters: buf - Buffer to receive result
maxsize - Maximum size of buffer maxsize - Maximum size of buffer
format - strfmon() format to use format - strfmon() format to use
val - Monetary value to convert val - Monetary value to convert
Returns: ssize_t - Size of returned string Returns: ssize_t - Size of returned string
This function calls strfmon() to convert val to a suitable monetary This function calls strfmon() to convert val to a suitable monetary
value string. If the POSIX or C locale is in effect, and "!" does NOT value string, making appropriate adjustments if the POSIX locale is in
appear in the format, "$" is inserted into the resulting string. This effect.
function overcomes the limitation that the POSIX locale does not define
anything for localeconv()->currency_symbol.
*/ */
extern ssize_t l_strfmon (char *restrict buf, size_t maxsize, extern ssize_t xstrfmon (char *restrict buf, size_t maxsize,
const char *restrict format, double val); const char *restrict format, double val);
/************************************************************************ /************************************************************************