/*** message.c - Message Manager
*
* Microsoft Confidential
* Copyright (C) Microsoft Corporation 1993-1994
* All Rights Reserved.
*
* Author:
* Benjamin W. Slivka
*
* History:
* 10-Aug-1993 bens Initial version
* 13-Aug-1993 bens Implemented message formatting
* 21-Feb-1994 bens Return length of formatted string
*/
#include <ctype.h>
#include <memory.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "types.h"
#include "asrt.h"
#include "mem.h"
#include "message.h"
#include "message.msg"
#ifdef BIT16
//** 16-bit build
#ifndef HUGE
#define HUGE huge
#endif
#ifndef FAR
#define FAR far
#endif
#else // !BIT16
//** Define away for 32-bit (NT/Chicago) build
#ifndef HUGE
#define HUGE
#endif
#ifndef FAR
#define FAR
#endif
#endif // !BIT16
typedef enum {
atBAD,
atSHORT,
atINT,
atLONG,
atFLOAT,
atDOUBLE,
atLONGDOUBLE,
atSTRING,
atFARSTRING,
} ARGTYPE; /* at */
int addCommas(char *pszStart);
ARGTYPE ATFromFormatSpecifier(char *pch);
int doFinalSubstitution(char *ach, char *pszMsg, char *apszValue[]);
int getHighestParmNumber(char *pszMsg);
/*** MsgSet - Set a message
*
* NOTE: See message.h for entry/exit conditions.
*/
int __cdecl MsgSet(char *ach, char *pszMsg, ...)
{
int cch;
va_list marker; // For walking through function arguments
char *pszFmtList; // Format string
Assert(ach!=NULL);
Assert(pszMsg!=NULL);
va_start(marker,pszMsg); // Initialize variable arguments
pszFmtList = (char *)va_arg(marker,char *); // Assume format string
cch = MsgSetWorker(ach,pszMsg,pszFmtList,marker);
va_end(marker); // Done with variable arguments
return cch;
}
/*** MsgSetWorker - Set Message after va_start already called
*
* NOTE: See message.h for entry/exit conditions.
*
* Technique:
* 1) Find highest parameter number in pszMsg
*
* If at least one parameter:
* 2) Parse 3rd argument to get sprintf() format strings.
* 3) Pick up each argument and format with sprintf into array
*
* Regardless of parameter count:
* 4) Copy bytes from pszMsg to ach, replacing %N by corresponding
* formatted parameter.
*/
int MsgSetWorker(char *ach, char *pszMsg, char *pszFmtList, va_list marker)
{
char achFmt[32]; // Temp buffer for single format specifier
char achValues[cbMSG_MAX]; // Buffer of formatted values
ARGTYPE at; // Argument type
char *apszValue[cMSG_PARM_MAX]; // Pointers into achValues
int cch; // Length of format specifier
int cParm; // Highest parameter number
BOOL fCommas; // TRUE=>use Commas
int iParm; // Parameter index
char *pch; // Last character of format specifier
char *pchFmtStart; // Start of single format specifier
char *pszNextValue; // Location in achValues for next value
char *pszStart;
//** (1) See if we have parameters to retrieve and format
cParm = getHighestParmNumber(pszMsg);
if (cParm > 0) { // Need to get values
//** (2) Parse 3rd argument to get sprintf() format strings.
pszNextValue = achValues; // Start filling at front
pch = pszFmtList; // Start at front of format specifiers
for (iParm=0; iParm<cParm; iParm++) { // Retrieve and format values
apszValue[iParm] = pszNextValue; // Store pointer to formatted value
pchFmtStart = pch; // Remember start of specifier
if (*pch != '%') { // Did not get a format specifier
// Only way to report problem is in output message buffer
strcpy(ach,pszMSGERR_BAD_FORMAT_SPECIFIER);
AssertErrPath(pszMSGERR_BAD_FORMAT_SPECIFIER,__FILE__,__LINE__);
return 0; // Failure
}
//** Find end of specifier
pch++;
while ((*pch != '\0') && (*pch != chMSG)) {
pch++;
}
cch = pch - pchFmtStart; // Length of specifier
if (cch < 2) { // Need at least % and one char for valid specifier
// Only way to report problem is in output message buffer
strcpy(ach,pszMSGERR_SPECIFIER_TOO_SHORT);
AssertErrPath(pszMSGERR_SPECIFIER_TOO_SHORT,__FILE__,__LINE__);
return 0; // Failure
}
//** (3) Pick up each argument and format with sprintf into array
//** Get specifier for sprintf() - we need a NULL terminator
fCommas = pchFmtStart[1] == ',';
if (fCommas) { // Copy format, deleting comma
achFmt[0] = pchFmtStart[0]; // Copy '%'
memcpy(achFmt+1,pchFmtStart+2,cch-2); // Get rest after ','
achFmt[cch-1] = '\0'; // Terminate specifier
}
else {
memcpy(achFmt,pchFmtStart,cch); // Copy to specifier buffer
achFmt[cch] = '\0'; // Terminate specifier
}
//** Format value, based on last character of format specifier
at = ATFromFormatSpecifier(pch-1); // Get argument type
pszStart = pszNextValue; // Save start of value (for commas)
switch (at) {
case atSHORT: pszNextValue += sprintf(pszNextValue,achFmt,
va_arg(marker,unsigned short)) + 1;
break;
case atINT: pszNextValue += sprintf(pszNextValue,achFmt,
va_arg(marker,unsigned int)) + 1;
break;
case atLONG: pszNextValue += sprintf(pszNextValue,achFmt,
va_arg(marker,unsigned long)) + 1;
break;
case atLONGDOUBLE: pszNextValue += sprintf(pszNextValue,achFmt,
#ifdef BIT16
va_arg(marker,long double)) + 1;
#else // !BIT16
//** in 32-bit mode, long double == double
va_arg(marker,double)) + 1;
#endif // !BIT16
break;
case atDOUBLE: pszNextValue += sprintf(pszNextValue,achFmt,
va_arg(marker,double)) + 1;
break;
case atSTRING: pszNextValue += sprintf(pszNextValue,achFmt,
va_arg(marker,char *)) + 1;
break;
case atFARSTRING: pszNextValue += sprintf(pszNextValue,achFmt,
va_arg(marker,char FAR *)) + 1;
break;
default:
strcpy(ach,pszMSGERR_UNKNOWN_FORMAT_SPECIFIER);
AssertErrPath(pszMSGERR_UNKNOWN_FORMAT_SPECIFIER,__FILE__,__LINE__);
return 0; // Failure
} /* switch */
//**
if (fCommas) {
switch (at) {
case atSHORT:
case atINT:
case atLONG:
pszNextValue += addCommas(pszStart);
break;
}
}
} /* for */
} /* if - parameters were present */
//** (4) Copy bytes from pszMsg to ach, replacing %N parameters with values
return doFinalSubstitution(ach,pszMsg,apszValue);
}
/*** addCommas - Add thousand separators to a number
*
* Entry:
* pszStart - Buffer with number at end (NULL terminated)
* NOTE: White space preceding or following number are
* assumed to be part of the field width, and will
* be consumed for use by any commas that are
* added. If there are not enough blanks to account
* for the commas, all the blanks will be consumed,
* and the field will be effectively widened to
* accomodate all of the commas.
* Exit:
* Returns number of commas added (0 or more)
*/
int addCommas(char *pszStart)
{
char ach[20]; // Buffer for number
int cb;
int cbBlanksBefore;
int cbBlanksAfter;
int cbFirst;
int cCommas;
char *psz;
char *pszSrc;
char *pszDst;
//** Figure out if there are any blanks
cbBlanksBefore = strspn(pszStart," "); // Count blanks before number
psz = strpbrk(pszStart+cbBlanksBefore," "); // Skip over number
if (psz) {
cbBlanksAfter = strspn(psz," "); // Count blanks after number
cb = psz - (pszStart+cbBlanksBefore); // Length of number itself
}
else {
cbBlanksAfter = 0; // No blanks after number
cb = strlen(pszStart+cbBlanksBefore); // Length of number itself
}
//** Quick out if we don't need to add commas
if (cb <= 3) {
return 0;
}
//** Figure out how many commas we need to add
Assert(cb < sizeof(ach));
strncpy(ach,pszStart+cbBlanksBefore,cb); // Move number to a safe place
cCommas = (cb - 1) / 3; // Number of commas we need to add
//** Figure out where to place modified number in buffer
if ((cbBlanksBefore > 0) && (cbBlanksBefore >= cCommas)) {
//** Eat some (but not all) blanks at front of buffer
pszDst = pszStart + cbBlanksBefore - cCommas;
}
else {
pszDst = pszStart; // Have to start number at front of buffer
}
//** Add commas to the number
cbFirst = cb % 3; // Number of digits before first comma
if (cbFirst == 0) {
cbFirst = 3;
}
pszSrc = ach;
strncpy(pszDst,pszSrc,cbFirst);
cb -= cbFirst;
pszDst += cbFirst;
pszSrc += cbFirst;
while (cb > 0) {
*pszDst++ = chTHOUSAND_SEPARATOR; // Place comma
strncpy(pszDst,pszSrc,3); // Copy next 3 digits
cb -= 3;
pszDst += 3;
pszSrc += 3;
}
//** Figure out if we need to add trailing NUL
if (cbBlanksBefore+cbBlanksAfter <= cCommas) {
//** There were no trailing blanks to preserve, so we need to
// make sure the string is terminated.
*pszDst++ = '\0'; // Terminate string
}
//** Success
return cCommas;
} /* addCommas() */
/*** ATFromFormatSpecifier - Determine argument type from sprintf format
*
* Entry:
* pch - points to last character (type) of sprintf format specifier
*
* Exit-Success:
* Returns ARGTYPE indicated by format specifier.
*
* Exit-Failure:
* Returns atBAD -- could not determine type.
*/
ARGTYPE ATFromFormatSpecifier(char *pch)
{
switch (*pch) {
case 'c':
case 'd':
case 'i':
case 'u':
case 'o':
case 'x':
case 'X':
// Check argument size character
switch (*(pch-1)) {
case 'h': return atSHORT;
case 'l': return atLONG;
default: return atINT;
}
break;
case 'f':
case 'e':
case 'E':
case 'g':
case 'G':
// Check argument size character
switch (*(pch-1)) {
case 'L': return atLONGDOUBLE;
default: // double size
//BUGBUG 13-Aug-1993 bens Should "%f" take a float, and "%lf" take a double?
// The VC++ docs say that "%f" takes a double, but the "l" description says double,
// and that omitting it cause float. I'm confused!
return atDOUBLE;
}
break;
case 's':
// Check argument size character
switch (*(pch-1)) {
case 'F': return atFARSTRING;
case 'N': return atSTRING;
default: return atSTRING;
}
break;
default:
return atBAD;
} /* switch */
} /* ATFromFormatSpecifier */
/*** doFinalSubstitution - Replace %1, %2, etc. with formatted values
*
* Entry:
* ach - Buffer to receive final output
* pszMsg - Message string, possibly with %1, %2, etc.
* apszValue - Values for %1, %2, etc.
*
* Exit-Success:
* Returns length of final text (not including NUL terminator);
* ach filled in with substituted final text.
*
* Exit-Failure:
* ach filled in with explanation of problem.
*/
int doFinalSubstitution(char *ach, char *pszMsg, char *apszValue[])
{
int i;
char *pch;
char *pszOut;
Assert(ach!=NULL);
Assert(pszMsg!=NULL);
pch = pszMsg; // Start scanning message at front
pszOut = ach; // Fill output buffer from front
while (*pch != '\0') {
if (*pch == chMSG) { // Could be the start of a parameter
pch++; // Skip %
if (isdigit(*pch)) { // We have a parameter!
i = atoi(pch); // Get number
while ( (*pch != '\0') && // Skip to end of string
isdigit(*pch) ) { // or end of number
pch++; // Skip parameter
}
strcpy(pszOut,apszValue[i-1]); // Copy value
pszOut += strlen(apszValue[i-1]); // Advance to end of value
}
else { // Not a digit
*pszOut++ = chMSG; // Copy %
if (*pch == chMSG) { // "%%"
pch++; // Replace "%%" with single "%"
}
else { // Some other character
*pszOut++ = *pch++; // Copy it
}
}
}
else { // Not a parameter
*pszOut++ = *pch++; // Copy character
}
}
*pszOut = '\0'; // Terminate output buffer
return pszOut-ach; // Size of final string (minus NUL)
}
/*** getHighestParmNumber - Get number of highest %N string
*
* Entry:
* pszMsg - String which may contain %N (%0, %1, etc.) strings
*
* Exit-Success:
* Returns highest N found in %N string.
*/
int getHighestParmNumber(char *pszMsg)
{
int i;
int iMax;
char *pch;
Assert(pszMsg!=NULL);
iMax = 0; // No parameter seen so far
pch = pszMsg;
while (*pch != '\0') {
if (*pch == chMSG) { // Could be the start of a parameter
pch++; // Skip %
if (isdigit(*pch)) { // We have a parameter!
i = atoi(pch); // Get number
if (i > iMax) // Remember highest parameter number
iMax = i;
while ( (*pch != '\0') && // Skip to end of string
isdigit(*pch) ) { // or end of number
pch++; // Skip parameter
}
}
else { // Not a digit
pch++; // Skip it
}
}
else { // Not a parameter
pch++; // Skip it
}
}
return iMax; // Return highest parameter seen
}