569 lines
18 KiB
C
569 lines
18 KiB
C
/*
|
|
* This file contains the actual formatting code used by various public interface functions.
|
|
*
|
|
* There are three macros defined by the parent function which control how this code writes the
|
|
* formatted string:
|
|
* * ENSURE_BUFFER - This macro is passed the number of bytes required whenever characters need to
|
|
* be written to the buffer. The macro can resize the buffer if needed.
|
|
* * OK_BUFFER - This macro returns TRUE if it is OK to write to the buffer, otherwise FALSE when
|
|
* the buffer is too large, is not specified, or some other error has occurred.
|
|
* * ADVANCE_BUFFER - This macro is passed the number of bytes written to the buffer and should
|
|
* increment the "buffer" pointer and "usedLength" counter.
|
|
* In addition to these macros, the "buffer" and "usedLength" variables are assumed to be present.
|
|
*
|
|
* The below code defines many macros; this is so that composite formatting types can be constructed
|
|
* (e.g. the "size" type).
|
|
*/
|
|
|
|
{
|
|
if (PhBeginInitOnce(&PhpFormatInitOnce))
|
|
{
|
|
WCHAR localeBuffer[4];
|
|
|
|
if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, localeBuffer, 4) &&
|
|
(localeBuffer[0] != 0 && localeBuffer[1] == 0))
|
|
{
|
|
PhpFormatDecimalSeparator = localeBuffer[0];
|
|
}
|
|
|
|
if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, localeBuffer, 4) &&
|
|
(localeBuffer[0] != 0 && localeBuffer[1] == 0))
|
|
{
|
|
PhpFormatThousandSeparator = localeBuffer[0];
|
|
}
|
|
|
|
if (PhpFormatDecimalSeparator != '.')
|
|
PhpFormatUserLocale = _create_locale(LC_ALL, "");
|
|
|
|
PhEndInitOnce(&PhpFormatInitOnce);
|
|
}
|
|
|
|
while (Count--)
|
|
{
|
|
PPH_FORMAT format;
|
|
SIZE_T partLength;
|
|
WCHAR tempBuffer[BUFFER_SIZE];
|
|
ULONG flags;
|
|
ULONG int32;
|
|
ULONG64 int64;
|
|
|
|
format = Format++;
|
|
|
|
// Save the currently used length so we can compute the part length later.
|
|
partLength = usedLength;
|
|
|
|
flags = 0;
|
|
|
|
switch (format->Type & FormatTypeMask)
|
|
{
|
|
|
|
// Characters and Strings
|
|
|
|
case CharFormatType:
|
|
ENSURE_BUFFER(sizeof(WCHAR));
|
|
if (OK_BUFFER)
|
|
*buffer = format->u.Char;
|
|
ADVANCE_BUFFER(sizeof(WCHAR));
|
|
break;
|
|
case StringFormatType:
|
|
ENSURE_BUFFER(format->u.String.Length);
|
|
if (OK_BUFFER)
|
|
memcpy(buffer, format->u.String.Buffer, format->u.String.Length);
|
|
ADVANCE_BUFFER(format->u.String.Length);
|
|
break;
|
|
case StringZFormatType:
|
|
{
|
|
SIZE_T count;
|
|
|
|
count = PhCountStringZ(format->u.StringZ);
|
|
ENSURE_BUFFER(count * sizeof(WCHAR));
|
|
if (OK_BUFFER)
|
|
memcpy(buffer, format->u.StringZ, count * sizeof(WCHAR));
|
|
ADVANCE_BUFFER(count * sizeof(WCHAR));
|
|
}
|
|
break;
|
|
case MultiByteStringFormatType:
|
|
case MultiByteStringZFormatType:
|
|
{
|
|
ULONG bytesInUnicodeString;
|
|
PSTR multiByteBuffer;
|
|
SIZE_T multiByteLength;
|
|
|
|
if (format->Type == MultiByteStringFormatType)
|
|
{
|
|
multiByteBuffer = format->u.MultiByteString.Buffer;
|
|
multiByteLength = format->u.MultiByteString.Length;
|
|
}
|
|
else
|
|
{
|
|
multiByteBuffer = format->u.MultiByteStringZ;
|
|
multiByteLength = strlen(multiByteBuffer);
|
|
}
|
|
|
|
if (NT_SUCCESS(RtlMultiByteToUnicodeSize(
|
|
&bytesInUnicodeString,
|
|
multiByteBuffer,
|
|
(ULONG)multiByteLength
|
|
)))
|
|
{
|
|
ENSURE_BUFFER(bytesInUnicodeString);
|
|
|
|
if (!OK_BUFFER || NT_SUCCESS(RtlMultiByteToUnicodeN(
|
|
buffer,
|
|
bytesInUnicodeString,
|
|
NULL,
|
|
multiByteBuffer,
|
|
(ULONG)multiByteLength
|
|
)))
|
|
{
|
|
ADVANCE_BUFFER(bytesInUnicodeString);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
// Integers
|
|
|
|
#define PROCESS_DIGIT(Input) \
|
|
do { \
|
|
r = (ULONG)(Input % radix); \
|
|
Input /= radix; \
|
|
*temp-- = integerToChar[r]; \
|
|
tempCount++; \
|
|
} while (0)
|
|
|
|
#define COMMON_INTEGER_FORMAT(Input, Format) \
|
|
do { \
|
|
ULONG radix; \
|
|
PCHAR integerToChar; \
|
|
PWSTR temp; \
|
|
ULONG tempCount; \
|
|
ULONG r; \
|
|
ULONG preCount; \
|
|
ULONG padCount; \
|
|
\
|
|
radix = 10; \
|
|
if (((Format)->Type & FormatUseRadix) && (Format)->Radix >= 2 && (Format)->Radix <= 69) \
|
|
radix = (Format)->Radix; \
|
|
integerToChar = PhIntegerToChar; \
|
|
if ((Format)->Type & FormatUpperCase) \
|
|
integerToChar = PhIntegerToCharUpper; \
|
|
temp = tempBuffer + BUFFER_SIZE - 1; \
|
|
tempCount = 0; \
|
|
\
|
|
if (Input != 0) \
|
|
{ \
|
|
if ((Format)->Type & FormatGroupDigits) \
|
|
{ \
|
|
ULONG needsSep = 0; \
|
|
\
|
|
do \
|
|
{ \
|
|
PROCESS_DIGIT(Input); \
|
|
\
|
|
if (++needsSep == 3 && Input != 0) /* get rid of trailing separator */ \
|
|
{ \
|
|
*temp-- = PhpFormatThousandSeparator; \
|
|
tempCount++; \
|
|
needsSep = 0; \
|
|
} \
|
|
} while (Input != 0); \
|
|
} \
|
|
else \
|
|
{ \
|
|
do \
|
|
{ \
|
|
PROCESS_DIGIT(Input); \
|
|
} while (Input != 0); \
|
|
} \
|
|
} \
|
|
else \
|
|
{ \
|
|
*temp-- = '0'; \
|
|
tempCount++; \
|
|
} \
|
|
\
|
|
preCount = 0; \
|
|
\
|
|
if (flags & PHP_FORMAT_NEGATIVE) \
|
|
preCount++; \
|
|
else if ((Format)->Type & FormatPrefixSign) \
|
|
preCount++; \
|
|
\
|
|
if (((Format)->Type & FormatPadZeros) && !((Format)->Type & FormatGroupDigits)) \
|
|
{ \
|
|
if (preCount + tempCount < (Format)->Width) \
|
|
{ \
|
|
flags |= PHP_FORMAT_PAD; \
|
|
padCount = (Format)->Width - (preCount + tempCount); \
|
|
preCount += padCount; \
|
|
} \
|
|
} \
|
|
\
|
|
temp++; \
|
|
ENSURE_BUFFER((preCount + tempCount) * sizeof(WCHAR)); \
|
|
if (OK_BUFFER) \
|
|
{ \
|
|
if (flags & PHP_FORMAT_NEGATIVE) \
|
|
*buffer++ = '-'; \
|
|
else if ((Format)->Type & FormatPrefixSign) \
|
|
*buffer++ = '+'; \
|
|
\
|
|
if (flags & PHP_FORMAT_PAD) \
|
|
{ \
|
|
wmemset(buffer, '0', padCount); \
|
|
buffer += padCount; \
|
|
} \
|
|
\
|
|
memcpy(buffer, temp, tempCount * sizeof(WCHAR)); \
|
|
buffer += tempCount; \
|
|
} \
|
|
usedLength += (preCount + tempCount) * sizeof(WCHAR); \
|
|
} while (0)
|
|
|
|
#ifndef _WIN64
|
|
case IntPtrFormatType:
|
|
int32 = format->u.IntPtr;
|
|
goto CommonMaybeNegativeInt32Format;
|
|
#endif
|
|
case Int32FormatType:
|
|
int32 = format->u.Int32;
|
|
|
|
#ifndef _WIN64
|
|
CommonMaybeNegativeInt32Format:
|
|
#endif
|
|
if ((LONG)int32 < 0)
|
|
{
|
|
int32 = -(LONG)int32;
|
|
flags |= PHP_FORMAT_NEGATIVE;
|
|
}
|
|
|
|
goto CommonInt32Format;
|
|
#ifndef _WIN64
|
|
case UIntPtrFormatType:
|
|
int32 = format->u.UIntPtr;
|
|
goto CommonInt32Format;
|
|
#endif
|
|
case UInt32FormatType:
|
|
int32 = format->u.UInt32;
|
|
CommonInt32Format:
|
|
COMMON_INTEGER_FORMAT(int32, format);
|
|
break;
|
|
#ifdef _WIN64
|
|
case IntPtrFormatType:
|
|
int64 = format->u.IntPtr;
|
|
goto CommonMaybeNegativeInt64Format;
|
|
#endif
|
|
case Int64FormatType:
|
|
int64 = format->u.Int64;
|
|
|
|
#ifdef _WIN64
|
|
CommonMaybeNegativeInt64Format:
|
|
#endif
|
|
if ((LONG64)int64 < 0)
|
|
{
|
|
int64 = -(LONG64)int64;
|
|
flags |= PHP_FORMAT_NEGATIVE;
|
|
}
|
|
|
|
goto CommonInt64Format;
|
|
#ifdef _WIN64
|
|
case UIntPtrFormatType:
|
|
int64 = format->u.UIntPtr;
|
|
goto CommonInt64Format;
|
|
#endif
|
|
case UInt64FormatType:
|
|
int64 = format->u.UInt64;
|
|
CommonInt64Format:
|
|
COMMON_INTEGER_FORMAT(int64, format);
|
|
break;
|
|
|
|
// Floating point numbers
|
|
|
|
#define COMMON_DOUBLE_FORMAT(Format) \
|
|
do { \
|
|
ULONG precision; \
|
|
DOUBLE value; \
|
|
CHAR c; \
|
|
PSTR temp; \
|
|
ULONG length; \
|
|
\
|
|
if ((Format)->Type & FormatUsePrecision) \
|
|
{ \
|
|
precision = (Format)->Precision; \
|
|
\
|
|
if (precision > BUFFER_SIZE - 1 - _CVTBUFSIZE) \
|
|
precision = BUFFER_SIZE - 1 - _CVTBUFSIZE; \
|
|
} \
|
|
else \
|
|
{ \
|
|
precision = 6; \
|
|
} \
|
|
\
|
|
c = 'f'; \
|
|
\
|
|
if ((Format)->Type & FormatStandardForm) \
|
|
c = 'e'; \
|
|
else if ((Format)->Type & FormatHexadecimalForm) \
|
|
c = 'a'; \
|
|
\
|
|
/* Use MS CRT routines to do the work. */ \
|
|
\
|
|
value = (Format)->u.Double; \
|
|
temp = (PSTR)tempBuffer + 1; /* leave one character so we can insert a prefix if needed */ \
|
|
_cfltcvt_l( \
|
|
&value, \
|
|
temp, \
|
|
sizeof(tempBuffer) - 1, \
|
|
c, \
|
|
precision, \
|
|
!!((Format)->Type & FormatUpperCase), \
|
|
PhpFormatUserLocale \
|
|
); \
|
|
\
|
|
/* if (((Format)->Type & FormatForceDecimalPoint) && precision == 0) */ \
|
|
/* _forcdecpt_l(tempBufferAnsi, PhpFormatUserLocale); */ \
|
|
if ((Format)->Type & FormatCropZeros) \
|
|
PhpCropZeros(temp, PhpFormatUserLocale); \
|
|
\
|
|
length = (ULONG)strlen(temp); \
|
|
\
|
|
if (temp[0] == '-') \
|
|
{ \
|
|
flags |= PHP_FORMAT_NEGATIVE; \
|
|
temp++; \
|
|
length--; \
|
|
} \
|
|
else if ((Format)->Type & FormatPrefixSign) \
|
|
{ \
|
|
flags |= PHP_FORMAT_POSITIVE; \
|
|
} \
|
|
\
|
|
if (((Format)->Type & FormatGroupDigits) && !((Format)->Type & (FormatStandardForm | FormatHexadecimalForm))) \
|
|
{ \
|
|
PSTR whole; \
|
|
PSTR decimalPoint; \
|
|
ULONG wholeCount; \
|
|
ULONG sepsCount; \
|
|
ULONG ensureLength; \
|
|
ULONG copyCount; \
|
|
ULONG needsSep; \
|
|
\
|
|
/* Find the first non-digit character and assume that is the */ \
|
|
/* decimal point (or the end of the string). */ \
|
|
\
|
|
whole = temp; \
|
|
decimalPoint = temp; \
|
|
\
|
|
while ((UCHAR)(*decimalPoint - '0') < 10) \
|
|
decimalPoint++; \
|
|
\
|
|
/* Copy the characters to the output buffer, and at the same time */ \
|
|
/* insert the separators. */ \
|
|
\
|
|
wholeCount = (ULONG)(decimalPoint - temp); \
|
|
\
|
|
if (wholeCount != 0) \
|
|
sepsCount = (wholeCount + 2) / 3 - 1; \
|
|
else \
|
|
sepsCount = 0; \
|
|
\
|
|
ensureLength = (length + sepsCount) * sizeof(WCHAR); \
|
|
if (flags & (PHP_FORMAT_NEGATIVE | PHP_FORMAT_POSITIVE)) \
|
|
ensureLength += sizeof(WCHAR); \
|
|
ENSURE_BUFFER(ensureLength); \
|
|
\
|
|
copyCount = wholeCount; \
|
|
needsSep = (wholeCount + 2) % 3; \
|
|
\
|
|
if (OK_BUFFER) \
|
|
{ \
|
|
if (flags & PHP_FORMAT_NEGATIVE) \
|
|
*buffer++ = '-'; \
|
|
else if (flags & PHP_FORMAT_POSITIVE) \
|
|
*buffer++ = '+'; \
|
|
\
|
|
while (copyCount--) \
|
|
{ \
|
|
*buffer++ = *whole++; \
|
|
\
|
|
if (needsSep-- == 0 && copyCount != 0) /* get rid of trailing separator */ \
|
|
{ \
|
|
*buffer++ = PhpFormatThousandSeparator; \
|
|
needsSep = 2; \
|
|
} \
|
|
} \
|
|
} \
|
|
\
|
|
if (flags & (PHP_FORMAT_NEGATIVE | PHP_FORMAT_POSITIVE)) \
|
|
usedLength += sizeof(WCHAR); \
|
|
usedLength += (wholeCount + sepsCount) * sizeof(WCHAR); \
|
|
\
|
|
/* Copy the rest. */ \
|
|
\
|
|
copyCount = length - wholeCount; \
|
|
\
|
|
if (OK_BUFFER) \
|
|
{ \
|
|
PhZeroExtendToUtf16Buffer(decimalPoint, copyCount, buffer); \
|
|
ADVANCE_BUFFER(copyCount * sizeof(WCHAR)); \
|
|
} \
|
|
} \
|
|
else \
|
|
{ \
|
|
SIZE_T preLength; \
|
|
SIZE_T padLength; \
|
|
\
|
|
/* Take care of the sign and zero padding. */ \
|
|
preLength = 0; \
|
|
\
|
|
if (flags & (PHP_FORMAT_NEGATIVE | PHP_FORMAT_POSITIVE)) \
|
|
preLength++; \
|
|
\
|
|
if ((Format)->Type & FormatPadZeros) \
|
|
{ \
|
|
if (preLength + length < (Format)->Width) \
|
|
{ \
|
|
flags |= PHP_FORMAT_PAD; \
|
|
padLength = (Format)->Width - (preLength + length); \
|
|
preLength += padLength; \
|
|
} \
|
|
} \
|
|
/* We don't need to group digits, so directly copy the characters */ \
|
|
/* to the output buffer. */ \
|
|
\
|
|
ENSURE_BUFFER((preLength + length) * sizeof(WCHAR)); \
|
|
\
|
|
if (OK_BUFFER) \
|
|
{ \
|
|
if (flags & PHP_FORMAT_NEGATIVE) \
|
|
*buffer++ = '-'; \
|
|
else if (flags & PHP_FORMAT_POSITIVE) \
|
|
*buffer++ = '+'; \
|
|
\
|
|
if (flags & PHP_FORMAT_PAD) \
|
|
{ \
|
|
wmemset(buffer, '0', padLength); \
|
|
buffer += padLength; \
|
|
} \
|
|
} \
|
|
\
|
|
usedLength += preLength * sizeof(WCHAR); \
|
|
\
|
|
if (OK_BUFFER) \
|
|
{ \
|
|
PhZeroExtendToUtf16Buffer((PSTR)temp, length, buffer); \
|
|
ADVANCE_BUFFER(length * sizeof(WCHAR)); \
|
|
} \
|
|
} \
|
|
} while (0)
|
|
|
|
case DoubleFormatType:
|
|
flags = 0;
|
|
COMMON_DOUBLE_FORMAT(format);
|
|
break;
|
|
|
|
// Additional types
|
|
|
|
case SizeFormatType:
|
|
{
|
|
ULONG i = 0;
|
|
ULONG maxSizeUnit;
|
|
DOUBLE s;
|
|
PH_FORMAT doubleFormat;
|
|
|
|
s = (DOUBLE)format->u.Size;
|
|
|
|
if (format->u.Size == 0)
|
|
{
|
|
ENSURE_BUFFER(sizeof(WCHAR));
|
|
if (OK_BUFFER)
|
|
*buffer = '0';
|
|
ADVANCE_BUFFER(sizeof(WCHAR));
|
|
goto ContinueLoop;
|
|
}
|
|
|
|
if (format->Type & FormatUseRadix)
|
|
maxSizeUnit = format->Radix;
|
|
else
|
|
maxSizeUnit = PhMaxSizeUnit;
|
|
|
|
while (
|
|
s >= 1000 &&
|
|
i < sizeof(PhpSizeUnitNamesCounted) / sizeof(PH_STRINGREF) &&
|
|
i < maxSizeUnit
|
|
)
|
|
{
|
|
s /= 1024;
|
|
i++;
|
|
}
|
|
|
|
// Format the number, then append the unit name.
|
|
|
|
doubleFormat.Type = DoubleFormatType | FormatUsePrecision | FormatCropZeros | FormatGroupDigits;
|
|
doubleFormat.Precision = (format->Type & FormatUsePrecision) ? format->Precision : 2;
|
|
doubleFormat.Width = 0; // stupid compiler
|
|
doubleFormat.u.Double = s;
|
|
flags = 0;
|
|
COMMON_DOUBLE_FORMAT(&doubleFormat);
|
|
|
|
ENSURE_BUFFER(sizeof(WCHAR) + PhpSizeUnitNamesCounted[i].Length);
|
|
if (OK_BUFFER)
|
|
{
|
|
*buffer = ' ';
|
|
memcpy(buffer + 1, PhpSizeUnitNamesCounted[i].Buffer, PhpSizeUnitNamesCounted[i].Length);
|
|
}
|
|
ADVANCE_BUFFER(sizeof(WCHAR) + PhpSizeUnitNamesCounted[i].Length);
|
|
}
|
|
break;
|
|
}
|
|
|
|
ContinueLoop:
|
|
partLength = usedLength - partLength;
|
|
|
|
if (format->Type & (FormatLeftAlign | FormatRightAlign))
|
|
{
|
|
SIZE_T newLength;
|
|
SIZE_T addLength;
|
|
|
|
newLength = format->Width * sizeof(WCHAR);
|
|
|
|
// We only pad and never truncate.
|
|
if (partLength < newLength)
|
|
{
|
|
addLength = newLength - partLength;
|
|
ENSURE_BUFFER(addLength);
|
|
|
|
if (OK_BUFFER)
|
|
{
|
|
WCHAR pad;
|
|
|
|
if (format->Type & FormatUsePad)
|
|
pad = format->Pad;
|
|
else
|
|
pad = ' ';
|
|
|
|
if (format->Type & FormatLeftAlign)
|
|
{
|
|
// Left alignment is easy; we just fill the remaining space with the pad
|
|
// character.
|
|
wmemset(buffer, pad, addLength / sizeof(WCHAR));
|
|
}
|
|
else
|
|
{
|
|
PWSTR start;
|
|
|
|
// Right alignment is much slower and involves moving the text forward, then
|
|
// filling in the space before it.
|
|
start = buffer - partLength / sizeof(WCHAR);
|
|
memmove(start + addLength / sizeof(WCHAR), start, partLength);
|
|
wmemset(start, pad, addLength / sizeof(WCHAR));
|
|
}
|
|
}
|
|
|
|
ADVANCE_BUFFER(addLength);
|
|
}
|
|
}
|
|
}
|
|
}
|