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)
|
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.
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
161
src/utils.c
161
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_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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
22
src/utils.h
22
src/utils.h
@ -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);
|
||||||
|
|
||||||
|
|
||||||
/************************************************************************
|
/************************************************************************
|
||||||
|
Loading…
Reference in New Issue
Block a user