strutil: Locale independent snprintf() and vsnprintf() functions

This commit is contained in:
Frank Stettner 2018-01-26 13:53:45 +01:00 committed by Uwe Hermann
parent eac9fcd268
commit 79034d4f39
3 changed files with 187 additions and 1 deletions

View File

@ -18,8 +18,9 @@
*/
/* Needed for isascii(), as used in the GNU libstdc++ headers */
/* Needed in strutil.c for POSIX.1-2008 locale functions */
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600
#define _XOPEN_SOURCE 700
#endif
#include <config.h>

View File

@ -245,6 +245,10 @@ SR_API uint64_t sr_parse_timestring(const char *timestring);
SR_API gboolean sr_parse_boolstring(const char *boolstring);
SR_API int sr_parse_period(const char *periodstr, uint64_t *p, uint64_t *q);
SR_API int sr_parse_voltage(const char *voltstr, uint64_t *p, uint64_t *q);
SR_API int sr_snprintf_ascii(char *buf, size_t buf_size,
const char *format, ...);
SR_API int sr_vsnprintf_ascii(char *buf, size_t buf_size,
const char *format, va_list args);
SR_API int sr_parse_rational(const char *str, struct sr_rational *ret);
/*--- version.c -------------------------------------------------------------*/

View File

@ -17,8 +17,17 @@
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/* Needed for POSIX.1-2008 locale functions */
#define _XOPEN_SOURCE 700
#include <config.h>
#include <ctype.h>
#include <locale.h>
#if defined(__FreeBSD__) || defined(__APPLE__)
#include <xlocale.h>
#endif
#if defined(__FreeBSD__)
#include <sys/param.h>
#endif
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@ -250,6 +259,178 @@ SR_PRIV int sr_atof_ascii(const char *str, float *ret)
return SR_OK;
}
/**
* Composes a string with a format string (like printf) in the buffer pointed
* by buf (taking buf_size as the maximum buffer capacity to fill).
* If the resulting string would be longer than n - 1 characters, the remaining
* characters are discarded and not stored, but counted for the value returned
* by the function.
* A terminating NUL character is automatically appended after the content
* written.
* After the format parameter, the function expects at least as many additional
* arguments as needed for format.
*
* This version ignores the actual locale and uses the locale "C" for Linux,
* FreeBSD, OSX and Android.
*
* @param buf Pointer to a buffer where the resulting C string is stored.
* @param buf_size Maximum number of bytes to be used in the buffer. The
* generated string has a length of at most buf_size - 1, leaving space
* for the additional terminating NUL character.
* @param format C string that contains a format string (see printf).
* @param ... A sequence of additional arguments, each containing a value to be
* used to replace a format specifier in the format string.
*
* @return On success, the number of characters that would have been written if
* buf_size had been sufficiently large, not counting the terminating
* NUL character. On failure, a negative number is returned.
* Notice that only when this returned value is non-negative and less
* than buf_size, the string has been completely written.
*
* @since 0.6.0
*/
SR_API int sr_snprintf_ascii(char *buf, size_t buf_size,
const char *format, ...)
{
int ret;
va_list args;
va_start(args, format);
ret = sr_vsnprintf_ascii(buf, buf_size, format, args);
va_end(args);
return ret;
}
/**
* Composes a string with a format string (like printf) in the buffer pointed
* by buf (taking buf_size as the maximum buffer capacity to fill).
* If the resulting string would be longer than n - 1 characters, the remaining
* characters are discarded and not stored, but counted for the value returned
* by the function.
* A terminating NUL character is automatically appended after the content
* written.
* Internally, the function retrieves arguments from the list identified by
* args as if va_arg was used on it, and thus the state of args is likely to
* be altered by the call.
* In any case, arg should have been initialized by va_start at some point
* before the call, and it is expected to be released by va_end at some point
* after the call.
*
* This version ignores the actual locale and uses the locale "C" for Linux,
* FreeBSD, OSX and Android.
*
* @param buf Pointer to a buffer where the resulting C string is stored.
* @param buf_size Maximum number of bytes to be used in the buffer. The
* generated string has a length of at most buf_size - 1, leaving space
* for the additional terminating NUL character.
* @param format C string that contains a format string (see printf).
* @param args A value identifying a variable arguments list initialized with
* va_start.
*
* @return On success, the number of characters that would have been written if
* buf_size had been sufficiently large, not counting the terminating
* NUL character. On failure, a negative number is returned.
* Notice that only when this returned value is non-negative and less
* than buf_size, the string has been completely written.
*
* @since 0.6.0
*/
SR_API int sr_vsnprintf_ascii(char *buf, size_t buf_size,
const char *format, va_list args)
{
#if defined(_WIN32)
int ret;
#if 0
/*
* TODO: This part compiles with mingw-w64 but doesn't run with Win7.
* Doesn't start because of "Procedure entry point _create_locale
* not found in msvcrt.dll".
* mingw-w64 should link to msvcr100.dll not msvcrt.dll!.
*/
_locale_t locale;
locale = _create_locale(LC_NUMERIC, "C");
ret = _vsnprintf_l(buf, buf_size, format, locale, args);
_free_locale(locale);
#endif
/* vsprintf uses the current locale, may cause issues for floats. */
ret = vsnprintf(buf, buf_size, format, args);
return ret;
#elif defined(__APPLE__)
/*
* See:
* https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man3/printf_l.3.html
* https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man3/xlocale.3.html
*/
int ret;
locale_t locale;
locale = newlocale(LC_NUMERIC_MASK, "C", NULL);
ret = vsnprintf_l(buf, buf_size, locale, format, args);
freelocale(locale);
return ret;
#elif defined(__FreeBSD__) && __FreeBSD_version >= 901000
/*
* See:
* https://www.freebsd.org/cgi/man.cgi?query=printf_l&apropos=0&sektion=3&manpath=FreeBSD+9.1-RELEASE
* https://www.freebsd.org/cgi/man.cgi?query=xlocale&apropos=0&sektion=3&manpath=FreeBSD+9.1-RELEASE
*/
int ret;
locale_t locale;
locale = newlocale(LC_NUMERIC_MASK, "C", NULL);
ret = vsnprintf_l(buf, buf_size, locale, format, args);
freelocale(locale);
return ret;
#elif defined(__ANDROID__)
/*
* The Bionic libc only has two locales ("C" aka "POSIX" and "C.UTF-8"
* aka "en_US.UTF-8"). The decimal point is hard coded as ".".
* See: https://android.googlesource.com/platform/bionic/+/master/libc/bionic/locale.cpp
*/
int ret;
ret = vsnprintf(buf, buf_size, format, args);
return ret;
#elif defined(__linux__)
int ret;
locale_t old_locale, temp_locale;
/* Switch to C locale for proper float/double conversion. */
temp_locale = newlocale(LC_NUMERIC, "C", NULL);
old_locale = uselocale(temp_locale);
ret = vsnprintf(buf, buf_size, format, args);
/* Switch back to original locale. */
uselocale(old_locale);
freelocale(temp_locale);
return ret;
#elif defined(__unix__) || defined(__unix)
/*
* This is a fallback for all other BSDs, *nix and FreeBSD <= 9.0, by
* using the current locale for snprintf(). This may not work correctly
* for floats!
*/
int ret;
ret = vsnprintf(buf, buf_size, format, args);
return ret;
#else
/* No implementation for unknown systems! */
return -1;
#endif
}
/**
* Convert a string representation of a numeric value to a sr_rational.
*