1
0
mirror of https://git.zap.org.au/git/trader.git synced 2024-09-29 17:45:55 -04: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)
-------------------------------
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.
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 */
char *buf = xmalloc(BUFSIZE);
if (l_strfmon(buf, BUFSIZE, spec->flag_nosym ? "%!n" : "%n",
format_arg[spec->arg_num].a.a_double) < 0) {
if (xstrfmon(buf, BUFSIZE, spec->flag_nosym ? "%!n" : "%n",
format_arg[spec->arg_num].a.a_double) < 0) {
saved_errno = errno;
free(buf);
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_BUFSIZE 16
// Default values used to override POSIX locale
#define MOD_POSIX_CURRENCY_SYMBOL "$"
#define MOD_POSIX_FRAC_DIGITS 2
#define MOD_POSIX_P_CS_PRECEDES 1
#define MOD_POSIX_P_SEP_BY_SPACE 0
// Values used to override the standard POSIX locale
#define MOD_POSIX_DECIMAL_POINT "."
#define MOD_POSIX_THOUSANDS_SEP ""
#define MOD_POSIX_GROUPING ""
#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
#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 *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;
add_currency_symbol = false;
is_posix_locale = false;
/* 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
side, we explicitly set the locale to "C", then test the returned
value of that, too. */
cloc = setlocale(LC_MONETARY, "C");
if ( strcmp(cur, cloc) == 0
|| strcmp(cur, "POSIX") == 0 || strcmp(cur, "C") == 0
|| strcmp(cur, "C.UTF-8") == 0 || strcmp(cur, "C.utf8") == 0) {
if ( strcmp(cur, cloc) == 0
|| strcmp(cur, "POSIX") == 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;
lconvinfo.currency_symbol = MOD_POSIX_CURRENCY_SYMBOL;
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;
is_posix_locale = true;
lconvinfo = mod_posix_lconv;
}
// 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,
const char *restrict format, double val)
ssize_t xstrfmon (char *restrict buf, size_t maxsize,
const char *restrict format, double val)
{
/* The current implementation assumes MOD_POSIX_P_CS_PRECEDES is 1
(currency symbol precedes value) and that MOD_POSIX_P_SEP_BY_SPACE
is 0 (no space separates currency symbol and value). It does,
however, handle currency symbols of length > 1. */
/* 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"! This
function overcomes these limitations by using snprintf(). */
assert(MOD_POSIX_P_CS_PRECEDES == 1);
assert(MOD_POSIX_P_SEP_BY_SPACE == 0);
if (! is_posix_locale) {
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) {
if (strstr(format, "!") == NULL) {
/* Insert lconvinfo.currency_symbol to s.
# define MOD_POSIX_FMT_POS MOD_POSIX_POSITIVE_SIGN MOD_POSIX_CURRENCY_SYMBOL "%." MOD_POSIX_q(MOD_POSIX_FRAC_DIGITS) "f"
# define MOD_POSIX_FMT_NEG MOD_POSIX_NEGATIVE_SIGN MOD_POSIX_CURRENCY_SYMBOL "%." MOD_POSIX_q(MOD_POSIX_FRAC_DIGITS) "f"
# 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
that the character encoding is ASCII-safe (such as by
being ASCII itself, or UTF-8). */
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;
}
if (strcmp(format, "%n") == 0) {
if (val >= 0.0) {
return snprintf(buf, maxsize, MOD_POSIX_FMT_POS, val);
} else {
// Make space for currency symbol, then copy it
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);
return snprintf(buf, maxsize, MOD_POSIX_FMT_NEG, -val);
}
} 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
Parameters: buf - Buffer to receive result
maxsize - Maximum size of buffer
format - strfmon() format to use
val - Monetary value to convert
Returns: ssize_t - Size of returned string
Function: xstrfmon - Convert monetary value to a string
Parameters: buf - Buffer to receive result
maxsize - Maximum size of buffer
format - strfmon() format to use
val - Monetary value to convert
Returns: ssize_t - Size of returned string
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
appear in the format, "$" is inserted into the resulting string. This
function overcomes the limitation that the POSIX locale does not define
anything for localeconv()->currency_symbol.
value string, making appropriate adjustments if the POSIX locale is in
effect.
*/
extern ssize_t l_strfmon (char *restrict buf, size_t maxsize,
const char *restrict format, double val);
extern ssize_t xstrfmon (char *restrict buf, size_t maxsize,
const char *restrict format, double val);
/************************************************************************