/*===========================================================================*
* *
* sflcvsn.c - *
* *
* Copyright (c) 1991-2003 iMatix Corporation *
* *
* ------------------ GPL Licensed Source Code ------------------ *
* iMatix makes this software available under the GNU General *
* Public License (GPL) license for open source projects. For *
* details of the GPL license please see www.gnu.org or read the *
* file license.gpl provided in this package. *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public *
* License along with this program in the file 'license.gpl'; if *
* not, write to the Free Software Foundation, Inc., 59 Temple *
* Place - Suite 330, Boston, MA 02111-1307, USA. *
* *
* You can also license this software under iMatix's General Terms *
* of Business (GTB) for commercial projects. If you have not *
* explicitly licensed this software under the iMatix GTB you may *
* only use it under the terms of the GNU General Public License. *
* *
* For more information, send an email to info@imatix.com. *
* -------------------------------------------------------------- *
*===========================================================================*/
#include "prelude.h" /* Universal header file */
#include "sflconv.h" /* Prototypes for functions */
/* ---------------------------------------------------------------------[<]-
Function: conv_str_number
Synopsis: Converts a string to a number. The number format is defined
by one or more of these flags (you add them to get a flags argument):
FLAG_N_SIGNED Number is signed.
FLAG_N_DECIMALS Number has decimals.
FLAG_N_ZERO_FILL Number has leading zeros.
FLAG_N_THOUSANDS Number has thousands-separators.
The input string may contain digits, decimal point, thousand separators
and a sign character or indicator. Formatting characters are only
accepted if they correspond to the number format. A blank string is
accepted as zero. A space following digits ends the number; anything
further is ignored.
Returns a string of width digits, including leading sign if that is
required. Zeroes are signed with a space. Width must be at least 1.
If the flag FLAG_N_DECIMALS is set, the last X digits are decimals,
where X is the value of the decimals argument. Decimals are then
accepted or rejected depending on the dec_format:
DECS_SHOW_ALL Accept decimals.
DECS_DROP_ZEROS Accept decimals.
DECS_HIDE_ALL Reject decimals.
DECS_SCIENTIFIC Accept decimals.
If the flag FLAG_N_SIGNED is set, accepts a leading or trailing sign,
or a financial negative like this: (123).
Returns a pointer to the formatted string, or null if the string was
rejected. These are the possible reasons for rejection:
- input number is too large for specified width;
- input number is signed when no sign is allowed;
- input number decimals when none are allowed;
- input number has more decimals than are allowed;
- more than one sign character in number;
- malformed financial negative '(123)';
- more than one decimal point in number;
- thousand seps when FLAG_N_THOUSANDS is cleared or FLAG_N_ZERO_FILL
is set (this overrides FLAG_N_THOUSANDS);
- junk in input string (unrecognised character).
---------------------------------------------------------------------[>]-*/
char *
conv_str_number (
const char *string, /* String to convert */
int flags, /* Number field flags */
char dec_point, /* Decimal point: '.' or ',' */
int decimals, /* Number of decimals, or 0 */
int dec_format, /* How are decimals shown */
int width /* Output field width, > 0 */
)
{
static char
number [FORMAT_MAX + 1]; /* Cleaned-up return string */
int
digits, /* Number of digits read so far */
decs_wanted = decimals; /* Number of decimals wanted */
char
*dest, /* Store formatted number here */
sign_char, /* Number's sign: ' ', '+', '-' */
sep_char, /* Thousands separator '.' or ',' */
decs_seen, /* Number of decimals output */
ch; /* Next character in picture */
Bool
have_point, /* Have we seen a decimal point */
have_zero, /* TRUE if number is all zero */
end_loop; /* Flag to break out of scan loop */
ASSERT (width <= FORMAT_MAX);
ASSERT (width > 0);
ASSERT (dec_point == '.' || dec_point == ',');
conv_reason = 0; /* No conversion errors so far */
/* --------------------------------- Prepare to copy digits ---------*/
if ((flags & FLAG_N_THOUSANDS) && !(flags & FLAG_N_ZERO_FILL))
sep_char = dec_point == '.'? ',': '.';
else
sep_char = ' '; /* Reject any thousands separator */
/* --------------------------------- Copy the digits ----------------*/
digits = 0; /* No digits loaded yet */
decs_seen = 0; /* No decimals output yet */
sign_char = ' '; /* Final sign character '+' or '-' */
end_loop = FALSE; /* Flag to break out of scan loop */
have_point = FALSE; /* No decimal point seen */
have_zero = TRUE; /* So far, it's zero */
dest = number; /* Scan through number */
while (*string)
{
ch = *string++;
switch (ch)
{
case '9':
case '8':
case '7':
case '6':
case '5':
case '4':
case '3':
case '2':
case '1':
have_zero = FALSE;
case '0':
digits++;
*dest++ = ch;
if (have_point)
++decs_seen;
break;
case '-':
case '+':
case '(':
if (sign_char != ' ')
{
conv_reason = CONV_ERR_MULTIPLE_SIGN;
return (NULL); /* More than one sign char */
}
else
if (flags & FLAG_N_SIGNED)
sign_char = ch;
else
{
conv_reason = CONV_ERR_SIGN_REJECTED;
return (NULL); /* Number may not be signed */
}
break;
case ')':
if (sign_char == '(')
sign_char = '-';
else
{
conv_reason = CONV_ERR_SIGN_BAD_FIN;
return (NULL); /* Malformed financial negative */
}
break;
case ' ': /* Space ends number after digits */
end_loop = (digits > 0);
break;
default:
if (ch == dec_point)
{
if (have_point)
{
conv_reason = CONV_ERR_MULTIPLE_POINT;
return (NULL); /* More than one decimal point */
}
else
if (flags & FLAG_N_DECIMALS)
have_point = TRUE;
else
{
conv_reason = CONV_ERR_DECS_REJECTED;
return (NULL); /* No decimals are allowed */
}
}
else
if (ch != sep_char) /* We allow sep chars anywhere */
{
conv_reason = CONV_ERR_INVALID_INPUT;
return (NULL); /* else we have junk */
}
}
if (end_loop)
break;
}
/* --------------------------------- Post-format the result ---------*/
if (flags & FLAG_N_DECIMALS)
{
ASSERT (width > decs_wanted); /* At least decimals + 1 digit */
if (dec_format == DECS_HIDE_ALL)
{
if (have_point)
{
conv_reason = CONV_ERR_DECS_HIDDEN;
return (NULL); /* No decimals are allowed */
}
}
while (decs_seen < decs_wanted) /* Supply missing decimals */
{
digits++;
*dest++ = '0';
decs_seen++;
}
if (decs_seen > decs_wanted)
{
conv_reason = CONV_ERR_DECS_OVERFLOW;
return (NULL); /* More decimals than allowed */
}
}
else
decs_wanted = 0;
*dest = 0; /* Terminate the string nicely */
if (digits > width)
{
conv_reason = CONV_ERR_TOO_MANY_DIGITS;
return (NULL); /* Overflow -- number too large */
}
/* Supply leading zeroes */
if (digits < width)
{
/* Shift number and null to right of field */
memmove (number + (width - digits), number, digits + 1);
memset (number, '0', width - digits);
}
/* Store sign if necessary */
if (flags & FLAG_N_SIGNED)
{
ASSERT (width > 1); /* At least sign + 1 digit */
if (number [0] != '0')
{
conv_reason = CONV_ERR_TOO_MANY_DIGITS;
return (NULL); /* Overflow -- no room for sign */
}
if (sign_char == '(')
{
conv_reason = CONV_ERR_SIGN_BAD_FIN;
return (NULL); /* Malformed financial negative */
}
else
if (sign_char == ' ')
sign_char = '+';
if (have_zero)
number [0] = ' '; /* Store sign */
else
number [0] = sign_char;
}
return (number);
}