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:
parent
827db8a537
commit
1e0285f6c0
4
NEWS
4
NEWS
@ -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.
|
||||
|
||||
|
@ -1472,7 +1472,7 @@ 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",
|
||||
if (xstrfmon(buf, BUFSIZE, spec->flag_nosym ? "%!n" : "%n",
|
||||
format_arg[spec->arg_num].a.a_double) < 0) {
|
||||
saved_errno = errno;
|
||||
free(buf);
|
||||
|
153
src/utils.c
153
src/utils.c
@ -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,7 +525,7 @@ 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
|
||||
@ -494,14 +533,15 @@ void init_locale (void)
|
||||
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) {
|
||||
|| 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,
|
||||
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(). */
|
||||
|
||||
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 {
|
||||
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 {
|
||||
// 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);
|
||||
// Other strfmon() formats are not supported
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
10
src/utils.h
10
src/utils.h
@ -239,7 +239,7 @@ 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
|
||||
maxsize - Maximum size of buffer
|
||||
format - strfmon() format to use
|
||||
@ -247,12 +247,10 @@ extern void init_locale (void);
|
||||
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,
|
||||
extern ssize_t xstrfmon (char *restrict buf, size_t maxsize,
|
||||
const char *restrict format, double val);
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user