ossp-pkg/str/str_format.c
/*
** OSSP str - String Handling
** Copyright (c) 1999-2005 Ralf S. Engelschall <rse@engelschall.com>
** Copyright (c) 1999-2005 The OSSP Project <http://www.ossp.org/>
**
** This file is part of OSSP str, a string handling and manipulation
** library which can be found at http://www.ossp.org/pkg/lib/str/.
**
** Permission to use, copy, modify, and distribute this software for
** any purpose with or without fee is hereby granted, provided that
** the above copyright notice and this permission notice appear in all
** copies.
**
** THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
** USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
** OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
**
** str_format.c: formatting functions
*/
/*
* Copyright (c) 1995-1999 The Apache Group. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/)."
*
* 4. The names "Apache Server" and "Apache Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/)."
*
* THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This is a generic printf-style formatting code which is based on
* Apache's ap_snprintf which it turn is based on and used with the
* permission of, the SIO stdio-replacement strx_* functions by Panos
* Tsirigotis <panos@alumni.cs.colorado.edu> for xinetd. The IEEE
* floating point formatting routines are derived from an anchient
* FreeBSD version which took it from GNU libc-4.6.27 and modified it
* to be thread safe. The whole code was finally cleaned up, stripped
* and extended by Ralf S. Engelschall for use inside the Str library.
* Especially any Apache and network specific kludges were removed again
* and instead the formatting engine now can be extended by the caller
* on-the-fly.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include "str_p.h"
/* types which are locally use */
typedef long long_int;
typedef unsigned long u_long_int;
#if defined(SIZEOF_LONG_LONG) && (SIZEOF_LONG_LONG > 0)
typedef long long quad_int;
typedef unsigned long long u_quad_int;
#else
typedef long quad_int;
typedef unsigned long u_quad_int;
#endif
/* a few handy defines */
static char str_null[] = "(NULL)";
#define S_NULL str_null
#define S_NULL_LEN 6
#define FLOAT_DIGITS 6
#define EXPONENT_LENGTH 10
/* NUM_BUF_SIZE is the size of the buffer used for arithmetic
conversions. This is a magic number; do NOT decrease it! */
#define NUM_BUF_SIZE 512
#define NDIG 80
/* compatibility */
#if !defined(HAVE_ISNAN)
#define isnan(d) (0)
#endif
#if !defined(HAVE_ISINF)
#define isinf(d) (0)
#endif
/*
* Convert decimal number to its string representation. The number of
* digits is specified by ndigit decpt is set to the position of the
* decimal point sign is set to 0 for positive, 1 for negative. buf must
* have at least NDIG bytes.
*/
#define str_ecvt(arg,ndigits,decpt,sign,buf) \
str_cvt((arg), (ndigits), (decpt), (sign), 1, (buf))
#define str_fcvt(arg,ndigits,decpt,sign,buf) \
str_cvt((arg), (ndigits), (decpt), (sign), 0, (buf))
/* inlined modf(3) to avoid dependency to external libm on systems
(like Tru64, QNX, etc) where modf(3) is not part of libc. */
static double
str_modf(
double arg,
double *iptr)
{
double fraction;
double integral;
long truncated;
truncated = (long)arg;
integral = (double)truncated;
fraction = arg - integral;
if (iptr != NULL)
*iptr = integral;
return fraction;
}
static char *
str_cvt(
double arg,
int ndigits,
int *decpt,
BOOL *sign,
int eflag,
char *buf)
{
register int r2;
double fi, fj;
register char *p, *p1;
if (ndigits >= NDIG - 1)
ndigits = NDIG - 2;
r2 = 0;
*sign = FALSE;
p = &buf[0];
if (arg < 0) {
*sign = TRUE;
arg = -arg;
}
arg = str_modf(arg, &fi);
p1 = &buf[NDIG];
/* Do integer part */
if (fi != 0) {
p1 = &buf[NDIG];
while (fi != 0 && p1 > &buf[0]) {
fj = str_modf(fi / 10, &fi);
*--p1 = (int)((fj + .03) * 10) + '0';
r2++;
}
while (p1 < &buf[NDIG])
*p++ = *p1++;
}
else if (arg > 0) {
while ((fj = arg * 10) < 1) {
arg = fj;
r2--;
}
}
p1 = &buf[ndigits];
if (eflag == 0)
p1 += r2;
*decpt = r2;
if (p1 < &buf[0]) {
buf[0] = NUL;
return (buf);
}
while (p <= p1 && p < &buf[NDIG]) {
arg *= 10;
arg = str_modf(arg, &fj);
*p++ = (int) fj + '0';
}
if (p1 >= &buf[NDIG]) {
buf[NDIG - 1] = NUL;
return (buf);
}
p = p1;
*p1 += 5;
while (*p1 > '9') {
*p1 = '0';
if (p1 > buf)
++ * --p1;
else {
*p1 = '1';
(*decpt)++;
if (eflag == 0) {
if (p > buf)
*p = '0';
p++;
}
}
}
*p = NUL;
return buf;
}
static char *
str_gcvt(
double number,
int ndigit,
char *buf,
BOOL altform)
{
BOOL sign;
int decpt;
register char *p1, *p2;
register int i;
char buf1[NDIG];
p1 = str_ecvt(number, ndigit, &decpt, &sign, buf1);
p2 = buf;
if (sign)
*p2++ = '-';
for (i = ndigit - 1; i > 0 && p1[i] == '0'; i--)
ndigit--;
if ((decpt >= 0 && decpt - ndigit > 4)
|| (decpt < 0 && decpt < -3)) { /* use E-style */
decpt--;
*p2++ = *p1++;
*p2++ = '.';
for (i = 1; i < ndigit; i++)
*p2++ = *p1++;
*p2++ = 'e';
if (decpt < 0) {
decpt = -decpt;
*p2++ = '-';
}
else
*p2++ = '+';
if (decpt / 100 > 0)
*p2++ = decpt / 100 + '0';
if (decpt / 10 > 0)
*p2++ = (decpt % 100) / 10 + '0';
*p2++ = decpt % 10 + '0';
}
else {
if (decpt <= 0) {
if (*p1 != '0')
*p2++ = '.';
while (decpt < 0) {
decpt++;
*p2++ = '0';
}
}
for (i = 1; i <= ndigit; i++) {
*p2++ = *p1++;
if (i == decpt)
*p2++ = '.';
}
if (ndigit < decpt) {
while (ndigit++ < decpt)
*p2++ = '0';
*p2++ = '.';
}
}
if (p2[-1] == '.' && !altform)
p2--;
*p2 = NUL;
return buf;
}
/*
* The INS_CHAR macro inserts a character in the buffer and flushes the
* buffer if necessary. It uses the char pointers sp and bep: sp points
* to the next available character in the buffer, bep points to the
* end-of-buffer+1. While using this macro, note that the nextb pointer
* is NOT updated. NOTE: Evaluation of the c argument should not have
* any side-effects
*/
#define INS_CHAR(c, sp, bep, cc) { \
if (sp >= bep) { \
vbuff->curpos = sp; \
if (vbuff->flush(vbuff)) \
return -1; \
sp = vbuff->curpos; \
bep = vbuff->endpos; \
} \
*sp++ = (c); \
cc++; \
}
/*
* Convert a string to decimal value
*/
#define NUM(c) ((c) - '0')
#define STR_TO_DEC(str, num) { \
num = NUM(*str++); \
while (str_isdigit(*(str))) { \
num *= 10 ; \
num += NUM(*str++) ; \
} \
}
/*
* This macro does zero padding so that the precision requirement is
* satisfied. The padding is done by adding '0's to the left of the
* string that is going to be printed.
*/
#define FIX_PRECISION(adjust, precision, s, s_len) \
if (adjust) { \
while (s_len < precision) { \
*--s = '0'; \
s_len++; \
} \
}
/*
* This macro does padding.
* The padding is done by printing the character ch.
*/
#define PAD(width, len, ch) \
do { \
INS_CHAR(ch, sp, bep, cc); \
width-- ; \
} while (width > len)
/*
* Prefix the character ch to the string str
* Increase length. Set the has_prefix flag.
*/
#define PREFIX(str, length, ch) \
*--str = ch; \
length++; \
has_prefix = TRUE
/*
* Convert num to its decimal format.
* Return value:
* - a pointer to a string containing the number (no sign)
* - len contains the length of the string
* - is_negative is set to TRUE or FALSE depending on the sign
* of the number (always set to FALSE if is_unsigned is TRUE)
* The caller provides a buffer for the string: that is the buf_end argument
* which is a pointer to the END of the buffer + 1 (i.e. if the buffer
* is declared as buf[ 100 ], buf_end should be &buf[ 100 ])
* Note: we have 2 versions. One is used when we need to use quads
* (conv_10_quad), the other when we don't (conv_10). We're assuming the
* latter is faster.
*/
static char *
conv_10(
register long_int num,
register BOOL is_unsigned,
register BOOL *is_negative,
char *buf_end,
register int *len)
{
register char *p = buf_end;
register u_long_int magnitude;
if (is_unsigned) {
magnitude = (u_long_int)num;
*is_negative = FALSE;
}
else {
*is_negative = (num < 0);
/* On a 2's complement machine, negating the most negative integer
results in a number that cannot be represented as a signed integer.
Here is what we do to obtain the number's magnitude:
a. add 1 to the number
b. negate it (becomes positive)
c. convert it to unsigned
d. add 1 */
if (*is_negative) {
long_int t = num + 1;
magnitude = ((u_long_int) - t) + 1;
}
else
magnitude = (u_long_int)num;
}
/* We use a do-while loop so that we write at least 1 digit */
do {
register u_long_int new_magnitude = magnitude / 10;
*--p = (char) (magnitude - new_magnitude * 10 + '0');
magnitude = new_magnitude;
} while (magnitude);
*len = (int)(buf_end - p);
return p;
}
static char *
conv_10_quad(
quad_int num,
register BOOL is_unsigned,
register BOOL *is_negative,
char *buf_end,
register int *len)
{
register char *p = buf_end;
u_quad_int magnitude;
if (is_unsigned) {
magnitude = (u_quad_int)num;
*is_negative = FALSE;
}
else {
*is_negative = (num < 0);
/* On a 2's complement machine, negating the most negative integer
result in a number that cannot be represented as a signed integer.
Here is what we do to obtain the number's magnitude:
a. add 1 to the number
b. negate it (becomes positive)
c. convert it to unsigned
d. add 1 */
if (*is_negative) {
quad_int t = num + 1;
magnitude = ((u_quad_int) - t) + 1;
}
else
magnitude = (u_quad_int)num;
}
/* We use a do-while loop so that we write at least 1 digit */
do {
u_quad_int new_magnitude = magnitude / 10;
*--p = (char)(magnitude - new_magnitude * 10 + '0');
magnitude = new_magnitude;
} while (magnitude);
*len = (int)(buf_end - p);
return p;
}
/*
* Convert a floating point number to a string formats 'f', 'e' or 'E'.
* The result is placed in buf, and len denotes the length of the string
* The sign is returned in the is_negative argument (and is not placed
* in buf).
*/
static char *
conv_fp(
register char format,
register double num,
BOOL add_dp,
int precision,
BOOL *is_negative,
char *buf,
int *len)
{
register char *s = buf;
register char *p;
int decimal_point;
char buf1[NDIG];
if (format == 'f')
p = str_fcvt(num, precision, &decimal_point, is_negative, buf1);
else /* either e or E format */
p = str_ecvt(num, precision + 1, &decimal_point, is_negative, buf1);
/* Check for Infinity and NaN */
if (str_isalpha(*p)) {
*len = str_len(str_copy(buf, p, 0));
*is_negative = FALSE;
return (buf);
}
if (format == 'f') {
if (decimal_point <= 0) {
*s++ = '0';
if (precision > 0) {
*s++ = '.';
while (decimal_point++ < 0)
*s++ = '0';
}
else if (add_dp)
*s++ = '.';
}
else {
while (decimal_point-- > 0)
*s++ = *p++;
if (precision > 0 || add_dp)
*s++ = '.';
}
}
else {
*s++ = *p++;
if (precision > 0 || add_dp)
*s++ = '.';
}
/* copy the rest of p, the NUL is NOT copied */
while (*p)
*s++ = *p++;
if (format != 'f') {
char temp[EXPONENT_LENGTH]; /* for exponent conversion */
int t_len;
BOOL exponent_is_negative;
*s++ = format; /* either e or E */
decimal_point--;
if (decimal_point != 0) {
p = conv_10((long_int) decimal_point, FALSE, &exponent_is_negative,
&temp[EXPONENT_LENGTH], &t_len);
*s++ = exponent_is_negative ? '-' : '+';
/* Make sure the exponent has at least 2 digits */
if (t_len == 1)
*s++ = '0';
while (t_len--)
*s++ = *p++;
}
else {
*s++ = '+';
*s++ = '0';
*s++ = '0';
}
}
*len = (int)(s - buf);
return buf;
}
/*
* Convert num to a base X number where X is a power of 2. nbits determines X.
* For example, if nbits is 3, we do base 8 conversion
* Return value:
* a pointer to a string containing the number
* The caller provides a buffer for the string: that is the buf_end
* argument which is a pointer to the END of the buffer + 1 (i.e. if the
* buffer is declared as buf[100], buf_end should be &buf[100]) As with
* conv_10, we have a faster version which is used when the number isn't
* quad size.
*/
static const char low_digits[] = "0123456789abcdef";
static const char upper_digits[] = "0123456789ABCDEF";
static char *
conv_p2(
register u_long_int num,
register int nbits,
char format,
char *buf_end,
register int *len)
{
register int mask = (1 << nbits) - 1;
register char *p = buf_end;
register const char *digits = (format == 'X') ? upper_digits : low_digits;
do {
*--p = digits[num & mask];
num >>= nbits;
} while (num);
*len = (int)(buf_end - p);
return p;
}
static char *
conv_p2_quad(
u_quad_int num,
register int nbits,
char format,
char *buf_end,
register int *len)
{
register int mask = (1 << nbits) - 1;
register char *p = buf_end;
register const char *digits = (format == 'X') ? upper_digits : low_digits;
do {
*--p = digits[num & mask];
num >>= nbits;
} while (num);
*len = (int)(buf_end - p);
return p;
}
/*
* str_vformat(), the generic printf-style formatting routine
* and heart of this piece of source.
*/
int
str_vformat(
str_vformat_t *vbuff,
const char *fmt,
va_list ap)
{
register char *sp;
register char *bep;
register int cc = 0;
register int i;
register char *s = NULL;
char *q;
int s_len;
register int min_width = 0;
int precision = 0;
enum { LEFT, RIGHT } adjust;
char pad_char;
char prefix_char;
double fp_num;
quad_int i_quad = (quad_int)0;
u_quad_int ui_quad;
long_int i_num = (long_int)0;
u_long_int ui_num;
char num_buf[NUM_BUF_SIZE];
char char_buf[2]; /* for printing %% and %<unknown> */
enum var_type_enum { IS_QUAD, IS_LONG, IS_SHORT, IS_INT };
enum var_type_enum var_type = IS_INT;
BOOL alternate_form;
BOOL print_sign;
BOOL print_blank;
BOOL adjust_precision;
BOOL adjust_width;
BOOL is_negative;
char extinfo[20];
sp = vbuff->curpos;
bep = vbuff->endpos;
while (*fmt != NUL) {
if (*fmt != '%') {
INS_CHAR(*fmt, sp, bep, cc);
}
else {
/*
* Default variable settings
*/
adjust = RIGHT;
alternate_form = print_sign = print_blank = FALSE;
pad_char = ' ';
prefix_char = NUL;
extinfo[0] = NUL;
fmt++;
/*
* Try to avoid checking for flags, width or precision
*/
if (!str_islower(*fmt)) {
/*
* Recognize flags: -, #, BLANK, +
*/
for (;; fmt++) {
if (*fmt == '{') {
i = 0;
for (fmt++; *fmt != '}' && *fmt != NUL; fmt++) {
if (i < sizeof(extinfo)-1)
extinfo[i++] = *fmt;
}
extinfo[i] = NUL;
}
else if (*fmt == '-')
adjust = LEFT;
else if (*fmt == '+')
print_sign = TRUE;
else if (*fmt == '#')
alternate_form = TRUE;
else if (*fmt == ' ')
print_blank = TRUE;
else if (*fmt == '0')
pad_char = '0';
else
break;
}
/*
* Check if a width was specified
*/
if (str_isdigit(*fmt)) {
STR_TO_DEC(fmt, min_width);
adjust_width = TRUE;
}
else if (*fmt == '*') {
min_width = va_arg(ap, int);
fmt++;
adjust_width = TRUE;
if (min_width < 0) {
adjust = LEFT;
min_width = -min_width;
}
}
else
adjust_width = FALSE;
/*
* Check if a precision was specified
*
* XXX: an unreasonable amount of precision may be specified
* resulting in overflow of num_buf. Currently we
* ignore this possibility.
*/
if (*fmt == '.') {
adjust_precision = TRUE;
fmt++;
if (str_isdigit(*fmt)) {
STR_TO_DEC(fmt, precision);
}
else if (*fmt == '*') {
precision = va_arg(ap, int);
fmt++;
if (precision < 0)
precision = 0;
}
else
precision = 0;
}
else
adjust_precision = FALSE;
}
else
adjust_precision = adjust_width = FALSE;
/*
* Modifier check
*/
if (*fmt == 'q') {
var_type = IS_QUAD;
fmt++;
}
else if (*fmt == 'l') {
var_type = IS_LONG;
fmt++;
}
else if (*fmt == 'h') {
var_type = IS_SHORT;
fmt++;
}
else {
var_type = IS_INT;
}
/*
* Argument extraction and printing.
* First we determine the argument type.
* Then, we convert the argument to a string.
* On exit from the switch, s points to the string that
* must be printed, s_len has the length of the string
* The precision requirements, if any, are reflected in s_len.
*
* NOTE: pad_char may be set to '0' because of the 0 flag.
* It is reset to ' ' by non-numeric formats
*/
switch (*fmt) {
/* Unsigned Decimal Integer */
case 'u':
if (var_type == IS_QUAD) {
i_quad = va_arg(ap, u_quad_int);
s = conv_10_quad(i_quad, 1, &is_negative,
&num_buf[NUM_BUF_SIZE], &s_len);
}
else {
if (var_type == IS_LONG)
i_num = (long_int)va_arg(ap, u_long_int);
else if (var_type == IS_SHORT)
i_num = (long_int)(unsigned short)va_arg(ap, unsigned int);
else
i_num = (long_int)va_arg(ap, unsigned int);
s = conv_10(i_num, 1, &is_negative,
&num_buf[NUM_BUF_SIZE], &s_len);
}
FIX_PRECISION(adjust_precision, precision, s, s_len);
break;
/* Signed Decimal Integer */
case 'd':
case 'i':
if (var_type == IS_QUAD) {
i_quad = va_arg(ap, quad_int);
s = conv_10_quad(i_quad, 0, &is_negative,
&num_buf[NUM_BUF_SIZE], &s_len);
}
else {
if (var_type == IS_LONG)
i_num = (long_int)va_arg(ap, long_int);
else if (var_type == IS_SHORT)
i_num = (long_int)(short)va_arg(ap, int);
else
i_num = (long_int)va_arg(ap, int);
s = conv_10(i_num, 0, &is_negative,
&num_buf[NUM_BUF_SIZE], &s_len);
}
FIX_PRECISION(adjust_precision, precision, s, s_len);
if (is_negative)
prefix_char = '-';
else if (print_sign)
prefix_char = '+';
else if (print_blank)
prefix_char = ' ';
break;
/* Unsigned Octal Integer */
case 'o':
if (var_type == IS_QUAD) {
ui_quad = va_arg(ap, u_quad_int);
s = conv_p2_quad(ui_quad, 3, *fmt,
&num_buf[NUM_BUF_SIZE], &s_len);
}
else {
if (var_type == IS_LONG)
ui_num = (u_long_int) va_arg(ap, u_long_int);
else if (var_type == IS_SHORT)
ui_num = (u_long_int)(unsigned short)va_arg(ap, unsigned int);
else
ui_num = (u_long_int)va_arg(ap, unsigned int);
s = conv_p2(ui_num, 3, *fmt, &num_buf[NUM_BUF_SIZE], &s_len);
}
FIX_PRECISION(adjust_precision, precision, s, s_len);
if (alternate_form && *s != '0') {
*--s = '0';
s_len++;
}
break;
/* Unsigned Hexadecimal Integer */
case 'x':
case 'X':
if (var_type == IS_QUAD) {
ui_quad = va_arg(ap, u_quad_int);
s = conv_p2_quad(ui_quad, 4, *fmt,
&num_buf[NUM_BUF_SIZE], &s_len);
}
else {
if (var_type == IS_LONG)
ui_num = (u_long_int)va_arg(ap, u_long_int);
else if (var_type == IS_SHORT)
ui_num = (u_long_int)(unsigned short)va_arg(ap, unsigned int);
else
ui_num = (u_long_int)va_arg(ap, unsigned int);
s = conv_p2(ui_num, 4, *fmt, &num_buf[NUM_BUF_SIZE], &s_len);
}
FIX_PRECISION(adjust_precision, precision, s, s_len);
if (alternate_form && i_num != 0) {
*--s = *fmt; /* 'x' or 'X' */
*--s = '0';
s_len += 2;
}
break;
/* String */
case 's':
s = va_arg(ap, char *);
if (s != NULL) {
s_len = str_len(s);
if (adjust_precision && precision < s_len)
s_len = precision;
}
else {
s = S_NULL;
s_len = S_NULL_LEN;
}
pad_char = ' ';
break;
/* Double Floating Point (style 1) */
case 'f':
case 'e':
case 'E':
fp_num = va_arg(ap, double);
if (isnan(fp_num)) {
s = "NaN";
s_len = 3;
}
else if (isinf(fp_num)) {
s = "Inf";
s_len = 3;
}
else {
/* use &num_buf[1], so that we have room for the sign */
s = conv_fp(*fmt, fp_num, alternate_form,
(adjust_precision == FALSE) ? FLOAT_DIGITS : precision,
&is_negative, &num_buf[1], &s_len);
if (is_negative)
prefix_char = '-';
else if (print_sign)
prefix_char = '+';
else if (print_blank)
prefix_char = ' ';
}
break;
/* Double Floating Point (style 2) */
case 'g':
case 'G':
fp_num = va_arg(ap, double);
if (isnan(fp_num)) {
s = "NaN";
s_len = 3;
}
else if (isinf(fp_num)) {
s = "Inf";
s_len = 3;
}
else {
if (adjust_precision == FALSE)
precision = FLOAT_DIGITS;
else if (precision == 0)
precision = 1;
/* use &num_buf[1], so that we have room for the sign */
s = str_gcvt(fp_num, precision, &num_buf[1], alternate_form);
if (*s == '-')
prefix_char = *s++;
else if (print_sign)
prefix_char = '+';
else if (print_blank)
prefix_char = ' ';
s_len = str_len(s);
if (alternate_form && (q = str_span(s, 0, ".", 0)) == NULL) {
s[s_len++] = '.';
s[s_len] = NUL; /* delimit for following str_span() */
}
if (*fmt == 'G' && (q = str_span(s, 0, "e", 0)) != NULL)
*q = 'E';
}
break;
/* Single Character */
case 'c':
char_buf[0] = (char) (va_arg(ap, int));
s = &char_buf[0];
s_len = 1;
pad_char = ' ';
break;
/* The '%' Character */
case '%':
char_buf[0] = '%';
s = &char_buf[0];
s_len = 1;
pad_char = ' ';
break;
/* Special: Number of already written characters */
case 'n':
if (var_type == IS_QUAD)
*(va_arg(ap, quad_int *)) = cc;
else if (var_type == IS_LONG)
*(va_arg(ap, long *)) = cc;
else if (var_type == IS_SHORT)
*(va_arg(ap, short *)) = cc;
else
*(va_arg(ap, int *)) = cc;
break;
/*
* Pointer argument type.
*/
case 'p':
#if defined(SIZEOF_LONG_LONG) && (SIZEOF_LONG_LONG == SIZEOF_VOID_P)
ui_quad = (u_quad_int) va_arg(ap, void *);
s = conv_p2_quad(ui_quad, 4, 'x', &num_buf[NUM_BUF_SIZE], &s_len);
#else
ui_num = (u_long_int) va_arg(ap, void *);
s = conv_p2(ui_num, 4, 'x', &num_buf[NUM_BUF_SIZE], &s_len);
#endif
pad_char = ' ';
break;
case NUL:
/*
* The last character of the format string was %.
* We ignore it.
*/
continue;
/*
* The default case is for unrecognized %'s. We print
* %<char> to help the user identify what option is not
* understood. This is also useful in case the user
* wants to pass the output of format_converter to
* another function that understands some other %<char>
* (like syslog). Note that we can't point s inside fmt
* because the unknown <char> could be preceded by width
* etc.
*/
default:
s = NULL;
if (vbuff->format != NULL) {
s = vbuff->format(vbuff,
&prefix_char, &pad_char, &s_len,
num_buf, NUM_BUF_SIZE, extinfo, *fmt, ap);
}
if (s == NULL) {
char_buf[0] = '%';
char_buf[1] = *fmt;
s = char_buf;
s_len = 2;
pad_char = ' ';
}
break;
}
if (prefix_char != NUL && s != S_NULL && s != char_buf) {
*--s = prefix_char;
s_len++;
}
if (adjust_width && adjust == RIGHT && min_width > s_len) {
if (pad_char == '0' && prefix_char != NUL) {
INS_CHAR(*s, sp, bep, cc);
s++;
s_len--;
min_width--;
}
PAD(min_width, s_len, pad_char);
}
/*
* Print the string s.
*/
for (i = s_len; i != 0; i--) {
INS_CHAR(*s, sp, bep, cc);
s++;
}
if (adjust_width && adjust == LEFT && min_width > s_len)
PAD(min_width, s_len, pad_char);
}
fmt++;
}
vbuff->curpos = sp;
return cc;
}
/*
* str_format -- format a new string.
* This is inspired by POSIX sprintf(3), but mainly provides the
* following differences: first it is actually a snprintf(3) style, i.e.
* it allows one to specify the maximum number of characters which are
* allowed to write. Second, it allows one to just count the number of
* characters which have to be written.
*/
#define STR_FORMAT_BUFLEN 20
static int str_flush_fake(str_vformat_t *out_handle)
{
out_handle->data[1].i += out_handle->data[2].i;
out_handle->curpos = (char *)out_handle->data[0].p;
return 0;
}
static int str_flush_real(str_vformat_t *out_handle)
{
return -1;
}
int str_format_va(char *s, str_size_t n, const char *fmt, va_list ap)
{
str_vformat_t handle;
char buf[STR_FORMAT_BUFLEN];
int rv;
if (n == 0)
return 0;
if (s == NULL) {
/* fake formatting, i.e., calculate output length only */
handle.curpos = buf;
handle.endpos = buf + sizeof(buf) - 1;
handle.flush = str_flush_fake;
handle.format = NULL;
handle.data[0].p = buf;
handle.data[1].i = 0;
handle.data[2].i = sizeof(buf);
rv = str_vformat(&handle, fmt, ap);
if (rv == -1)
rv = (int)n;
}
else {
/* real formatting, i.e., create output */
handle.curpos = s;
handle.endpos = s + n - 1;
handle.flush = str_flush_real;
handle.format = NULL;
rv = str_vformat(&handle, fmt, ap);
*(handle.curpos) = NUL;
if (rv == -1)
rv = (int)n;
}
return rv;
}
int str_format(char *s, str_size_t n, const char *fmt, ...)
{
va_list ap;
int rv;
va_start(ap, fmt);
rv = str_format_va(s, n, fmt, ap);
va_end(ap);
return rv;
}
#if 0 /* still don't know whether it fits into API */
char *str_aprintf(const char *fmt, ...)
{
va_list ap;
char *s;
int rv;
va_start(ap, fmt);
rv = str_format_va(NULL, -1, fmt, ap);
va_end(ap);
if (rv == -1)
return NULL;
if ((s = malloc(rv+1)) == NULL)
return NULL;
va_start(ap, fmt);
rv = str_format_va(s, rv+1, fmt, ap);
va_end(ap);
if (rv == -1)
return NULL;
return s;
}
#endif