// © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html /* ****************************************************************************** * * Copyright (C) 1998-2016, International Business Machines * Corporation and others. All Rights Reserved. * ****************************************************************************** * * File uprntf_p.c * * Modification History: * * Date Name Description * 11/23/98 stephen Creation. * 03/12/99 stephen Modified for new C API. * 08/07/2003 george Reunify printf implementations ****************************************************************************** */ #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING && !UCONFIG_NO_CONVERSION #include "unicode/ustring.h" #include "unicode/utf16.h" #include "uprintf.h" #include "ufmt_cmn.h" #include "cmemory.h" #include "putilimp.h" /* ANSI style formatting */ /* Use US-ASCII characters only for formatting */ /* % */ #define UFMT_SIMPLE_PERCENT {ufmt_simple_percent, u_printf_simple_percent_handler} /* s */ #define UFMT_STRING {ufmt_string, u_printf_string_handler} /* c */ #define UFMT_CHAR {ufmt_char, u_printf_char_handler} /* d, i */ #define UFMT_INT {ufmt_int, u_printf_integer_handler} /* u */ #define UFMT_UINT {ufmt_int, u_printf_uinteger_handler} /* o */ #define UFMT_OCTAL {ufmt_int, u_printf_octal_handler} /* x, X */ #define UFMT_HEX {ufmt_int, u_printf_hex_handler} /* f */ #define UFMT_DOUBLE {ufmt_double, u_printf_double_handler} /* e, E */ #define UFMT_SCIENTIFIC {ufmt_double, u_printf_scientific_handler} /* g, G */ #define UFMT_SCIDBL {ufmt_double, u_printf_scidbl_handler} /* n */ #define UFMT_COUNT {ufmt_count, u_printf_count_handler} /* non-ANSI extensions */ /* Use US-ASCII characters only for formatting */ /* p */ #define UFMT_POINTER {ufmt_pointer, u_printf_pointer_handler} /* V */ #define UFMT_SPELLOUT {ufmt_double, u_printf_spellout_handler} /* P */ #define UFMT_PERCENT {ufmt_double, u_printf_percent_handler} /* C K is old format */ #define UFMT_UCHAR {ufmt_uchar, u_printf_uchar_handler} /* S U is old format */ #define UFMT_USTRING {ufmt_ustring, u_printf_ustring_handler} #define UFMT_EMPTY {ufmt_empty, nullptr} /** * A u_printf handler function. * A u_printf handler is responsible for handling a single u_printf * format specification, for example 'd' or 's'. * @param stream The UFILE to which to write output. * @param info A pointer to a u_printf_spec_info struct containing * information on the format specification. * @param args A pointer to the argument data * @return The number of Unicode characters written to stream. */ typedef int32_t U_EXPORT2 u_printf_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args); typedef struct u_printf_info { ufmt_type_info info; u_printf_handler *handler; } u_printf_info; /** * Struct encapsulating a single uprintf format specification. */ typedef struct u_printf_spec { u_printf_spec_info fInfo; /* Information on this spec */ int32_t fWidthPos; /* Position of width in arg list */ int32_t fPrecisionPos; /* Position of precision in arg list */ int32_t fArgPos; /* Position of data in arg list */ } u_printf_spec; #define UPRINTF_NUM_FMT_HANDLERS 108 /* We do not use handlers for 0-0x1f */ #define UPRINTF_BASE_FMT_HANDLERS 0x20 /* buffer size for formatting */ #define UPRINTF_BUFFER_SIZE 1024 #define UPRINTF_SYMBOL_BUFFER_SIZE 8 static const char16_t gNullStr[] = {0x28, 0x6E, 0x75, 0x6C, 0x6C, 0x29, 0}; /* "(null)" */ static const char16_t gSpaceStr[] = {0x20, 0}; /* " " */ /* Sets the sign of a format based on u_printf_spec_info */ /* TODO: Is setting the prefix symbol to a positive sign a good idea in all locales? */ static void u_printf_set_sign(UNumberFormat *format, const u_printf_spec_info *info, char16_t *prefixBuffer, int32_t *prefixBufLen, UErrorCode *status) { if(info->fShowSign) { *prefixBufLen = unum_getTextAttribute(format, UNUM_POSITIVE_PREFIX, prefixBuffer, *prefixBufLen, status); if (info->fSpace) { /* Setting UNUM_PLUS_SIGN_SYMBOL affects the exponent too. */ /* unum_setSymbol(format, UNUM_PLUS_SIGN_SYMBOL, gSpaceStr, 1, &status); */ unum_setTextAttribute(format, UNUM_POSITIVE_PREFIX, gSpaceStr, 1, status); } else { char16_t plusSymbol[UPRINTF_SYMBOL_BUFFER_SIZE]; int32_t symbolLen; symbolLen = unum_getSymbol(format, UNUM_PLUS_SIGN_SYMBOL, plusSymbol, UPRV_LENGTHOF(plusSymbol), status); unum_setTextAttribute(format, UNUM_POSITIVE_PREFIX, plusSymbol, symbolLen, status); } } else { *prefixBufLen = 0; } } static void u_printf_reset_sign(UNumberFormat *format, const u_printf_spec_info *info, char16_t *prefixBuffer, int32_t *prefixBufLen, UErrorCode *status) { if(info->fShowSign) { unum_setTextAttribute(format, UNUM_POSITIVE_PREFIX, prefixBuffer, *prefixBufLen, status); } } /* handle a '%' */ static int32_t u_printf_simple_percent_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args) { (void)formatBundle; (void)info; (void)args; static const char16_t PERCENT[] = { UP_PERCENT }; /* put a single '%' onto the output */ return handler->write(context, PERCENT, 1); } /* handle 's' */ static int32_t u_printf_string_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args) { (void)formatBundle; char16_t *s; char16_t buffer[UFMT_DEFAULT_BUFFER_SIZE]; int32_t len, written; int32_t argSize; const char *arg = (const char*)(args[0].ptrValue); /* convert from the default codepage to Unicode */ if (arg) { argSize = (int32_t)strlen(arg) + 1; if (argSize >= MAX_UCHAR_BUFFER_SIZE(buffer)) { s = ufmt_defaultCPToUnicode(arg, argSize, (char16_t *)uprv_malloc(MAX_UCHAR_BUFFER_NEEDED(argSize)), MAX_UCHAR_BUFFER_NEEDED(argSize)); if(s == nullptr) { return 0; } } else { s = ufmt_defaultCPToUnicode(arg, argSize, buffer, UPRV_LENGTHOF(buffer)); } } else { s = (char16_t *)gNullStr; } len = u_strlen(s); /* width = minimum # of characters to write */ /* precision = maximum # of characters to write */ if (info->fPrecision != -1 && info->fPrecision < len) { len = info->fPrecision; } written = handler->pad_and_justify(context, info, s, len); /* clean up */ if (gNullStr != s && buffer != s) { uprv_free(s); } return written; } static int32_t u_printf_char_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args) { (void)formatBundle; char16_t s[U16_MAX_LENGTH+1]; int32_t len = 1, written; unsigned char arg = (unsigned char)(args[0].int64Value); /* convert from default codepage to Unicode */ ufmt_defaultCPToUnicode((const char *)&arg, 2, s, UPRV_LENGTHOF(s)); /* Remember that this may be an MBCS character */ if (arg != 0) { len = u_strlen(s); } /* width = minimum # of characters to write */ /* precision = maximum # of characters to write */ /* precision is ignored when handling a char */ written = handler->pad_and_justify(context, info, s, len); return written; } static int32_t u_printf_double_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args) { double num = (double) (args[0].doubleValue); UNumberFormat *format; char16_t result[UPRINTF_BUFFER_SIZE]; char16_t prefixBuffer[UPRINTF_BUFFER_SIZE]; int32_t prefixBufferLen = sizeof(prefixBuffer); int32_t minDecimalDigits; int32_t maxDecimalDigits; int32_t resultLen; UErrorCode status = U_ZERO_ERROR; prefixBuffer[0] = 0; /* mask off any necessary bits */ /* if(! info->fIsLongDouble) num &= DBL_MAX;*/ /* get the formatter */ format = u_locbund_getNumberFormat(formatBundle, UNUM_DECIMAL); /* handle error */ if (format == nullptr) return 0; /* save the formatter's state */ minDecimalDigits = unum_getAttribute(format, UNUM_MIN_FRACTION_DIGITS); maxDecimalDigits = unum_getAttribute(format, UNUM_MAX_FRACTION_DIGITS); /* set the appropriate flags and number of decimal digits on the formatter */ if(info->fPrecision != -1) { /* set the # of decimal digits */ unum_setAttribute(format, UNUM_FRACTION_DIGITS, info->fPrecision); } else if(info->fAlt) { /* '#' means always show decimal point */ /* copy of printf behavior on Solaris - '#' shows 6 digits */ unum_setAttribute(format, UNUM_FRACTION_DIGITS, 6); } else { /* # of decimal digits is 6 if precision not specified regardless of locale */ unum_setAttribute(format, UNUM_FRACTION_DIGITS, 6); } /* set whether to show the sign */ if (info->fShowSign) { u_printf_set_sign(format, info, prefixBuffer, &prefixBufferLen, &status); } /* format the number */ resultLen = unum_formatDouble(format, num, result, UPRINTF_BUFFER_SIZE, nullptr, &status); if (U_FAILURE(status)) { resultLen = 0; } /* restore the number format */ /* TODO: Is this needed? */ unum_setAttribute(format, UNUM_MIN_FRACTION_DIGITS, minDecimalDigits); unum_setAttribute(format, UNUM_MAX_FRACTION_DIGITS, maxDecimalDigits); if (info->fShowSign) { /* Reset back to original value regardless of what the error was */ UErrorCode localStatus = U_ZERO_ERROR; u_printf_reset_sign(format, info, prefixBuffer, &prefixBufferLen, &localStatus); } return handler->pad_and_justify(context, info, result, resultLen); } /* HSYS */ static int32_t u_printf_integer_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args) { int64_t num = args[0].int64Value; UNumberFormat *format; char16_t result[UPRINTF_BUFFER_SIZE]; char16_t prefixBuffer[UPRINTF_BUFFER_SIZE]; int32_t prefixBufferLen = sizeof(prefixBuffer); int32_t minDigits = -1; int32_t resultLen; UErrorCode status = U_ZERO_ERROR; prefixBuffer[0] = 0; /* mask off any necessary bits */ if (info->fIsShort) num = (int16_t)num; else if (!info->fIsLongLong) num = (int32_t)num; /* get the formatter */ format = u_locbund_getNumberFormat(formatBundle, UNUM_DECIMAL); /* handle error */ if (format == nullptr) return 0; /* set the appropriate flags on the formatter */ /* set the minimum integer digits */ if(info->fPrecision != -1) { /* set the minimum # of digits */ minDigits = unum_getAttribute(format, UNUM_MIN_INTEGER_DIGITS); unum_setAttribute(format, UNUM_MIN_INTEGER_DIGITS, info->fPrecision); } /* set whether to show the sign */ if(info->fShowSign) { u_printf_set_sign(format, info, prefixBuffer, &prefixBufferLen, &status); } /* format the number */ resultLen = unum_formatInt64(format, num, result, UPRINTF_BUFFER_SIZE, nullptr, &status); if (U_FAILURE(status)) { resultLen = 0; } /* restore the number format */ if (minDigits != -1) { unum_setAttribute(format, UNUM_MIN_INTEGER_DIGITS, minDigits); } if (info->fShowSign) { /* Reset back to original value regardless of what the error was */ UErrorCode localStatus = U_ZERO_ERROR; u_printf_reset_sign(format, info, prefixBuffer, &prefixBufferLen, &localStatus); } return handler->pad_and_justify(context, info, result, resultLen); } static int32_t u_printf_hex_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args) { (void)formatBundle; int64_t num = args[0].int64Value; char16_t result[UPRINTF_BUFFER_SIZE]; int32_t len = UPRINTF_BUFFER_SIZE; /* mask off any necessary bits */ if (info->fIsShort) num &= UINT16_MAX; else if (!info->fIsLongLong) num &= UINT32_MAX; /* format the number, preserving the minimum # of digits */ ufmt_64tou(result, &len, num, 16, (UBool)(info->fSpec == 0x0078), (info->fPrecision == -1 && info->fZero) ? info->fWidth : info->fPrecision); /* convert to alt form, if desired */ if(num != 0 && info->fAlt && len < UPRINTF_BUFFER_SIZE - 2) { /* shift the formatted string right by 2 chars */ memmove(result + 2, result, len * sizeof(char16_t)); result[0] = 0x0030; result[1] = info->fSpec; len += 2; } return handler->pad_and_justify(context, info, result, len); } static int32_t u_printf_octal_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args) { (void)formatBundle; int64_t num = args[0].int64Value; char16_t result[UPRINTF_BUFFER_SIZE]; int32_t len = UPRINTF_BUFFER_SIZE; /* mask off any necessary bits */ if (info->fIsShort) num &= UINT16_MAX; else if (!info->fIsLongLong) num &= UINT32_MAX; /* format the number, preserving the minimum # of digits */ ufmt_64tou(result, &len, num, 8, false, /* doesn't matter for octal */ info->fPrecision == -1 && info->fZero ? info->fWidth : info->fPrecision); /* convert to alt form, if desired */ if(info->fAlt && result[0] != 0x0030 && len < UPRINTF_BUFFER_SIZE - 1) { /* shift the formatted string right by 1 char */ memmove(result + 1, result, len * sizeof(char16_t)); result[0] = 0x0030; len += 1; } return handler->pad_and_justify(context, info, result, len); } static int32_t u_printf_uinteger_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args) { int64_t num = args[0].int64Value; UNumberFormat *format; char16_t result[UPRINTF_BUFFER_SIZE]; int32_t minDigits = -1; int32_t resultLen; UErrorCode status = U_ZERO_ERROR; /* TODO: Fix this once uint64_t can be formatted. */ if (info->fIsShort) num &= UINT16_MAX; else if (!info->fIsLongLong) num &= UINT32_MAX; /* get the formatter */ format = u_locbund_getNumberFormat(formatBundle, UNUM_DECIMAL); /* handle error */ if (format == nullptr) return 0; /* set the appropriate flags on the formatter */ /* set the minimum integer digits */ if(info->fPrecision != -1) { /* set the minimum # of digits */ minDigits = unum_getAttribute(format, UNUM_MIN_INTEGER_DIGITS); unum_setAttribute(format, UNUM_MIN_INTEGER_DIGITS, info->fPrecision); } /* To mirror other stdio implementations, we ignore the sign argument */ /* format the number */ resultLen = unum_formatInt64(format, num, result, UPRINTF_BUFFER_SIZE, nullptr, &status); if (U_FAILURE(status)) { resultLen = 0; } /* restore the number format */ if (minDigits != -1) { unum_setAttribute(format, UNUM_MIN_INTEGER_DIGITS, minDigits); } return handler->pad_and_justify(context, info, result, resultLen); } static int32_t u_printf_pointer_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args) { (void)formatBundle; char16_t result[UPRINTF_BUFFER_SIZE]; int32_t len = UPRINTF_BUFFER_SIZE; /* format the pointer in hex */ ufmt_ptou(result, &len, args[0].ptrValue, true/*, info->fPrecision*/); return handler->pad_and_justify(context, info, result, len); } static int32_t u_printf_scientific_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args) { double num = (double) (args[0].doubleValue); UNumberFormat *format; char16_t result[UPRINTF_BUFFER_SIZE]; char16_t prefixBuffer[UPRINTF_BUFFER_SIZE]; int32_t prefixBufferLen = sizeof(prefixBuffer); int32_t minDecimalDigits; int32_t maxDecimalDigits; UErrorCode status = U_ZERO_ERROR; char16_t srcExpBuf[UPRINTF_SYMBOL_BUFFER_SIZE]; int32_t srcLen, expLen; int32_t resultLen; char16_t expBuf[UPRINTF_SYMBOL_BUFFER_SIZE]; prefixBuffer[0] = 0; /* mask off any necessary bits */ /* if(! info->fIsLongDouble) num &= DBL_MAX;*/ /* get the formatter */ format = u_locbund_getNumberFormat(formatBundle, UNUM_SCIENTIFIC); /* handle error */ if (format == nullptr) return 0; /* set the appropriate flags on the formatter */ srcLen = unum_getSymbol(format, UNUM_EXPONENTIAL_SYMBOL, srcExpBuf, sizeof(srcExpBuf), &status); /* Upper/lower case the e */ if (info->fSpec == (char16_t)0x65 /* e */) { expLen = u_strToLower(expBuf, (int32_t)sizeof(expBuf), srcExpBuf, srcLen, formatBundle->fLocale, &status); } else { expLen = u_strToUpper(expBuf, (int32_t)sizeof(expBuf), srcExpBuf, srcLen, formatBundle->fLocale, &status); } unum_setSymbol(format, UNUM_EXPONENTIAL_SYMBOL, expBuf, expLen, &status); /* save the formatter's state */ minDecimalDigits = unum_getAttribute(format, UNUM_MIN_FRACTION_DIGITS); maxDecimalDigits = unum_getAttribute(format, UNUM_MAX_FRACTION_DIGITS); /* set the appropriate flags and number of decimal digits on the formatter */ if(info->fPrecision != -1) { /* set the # of decimal digits */ if (info->fOrigSpec == (char16_t)0x65 /* e */ || info->fOrigSpec == (char16_t)0x45 /* E */) { unum_setAttribute(format, UNUM_FRACTION_DIGITS, info->fPrecision); } else { unum_setAttribute(format, UNUM_MIN_FRACTION_DIGITS, 1); unum_setAttribute(format, UNUM_MAX_FRACTION_DIGITS, info->fPrecision); } } else if(info->fAlt) { /* '#' means always show decimal point */ /* copy of printf behavior on Solaris - '#' shows 6 digits */ unum_setAttribute(format, UNUM_FRACTION_DIGITS, 6); } else { /* # of decimal digits is 6 if precision not specified */ unum_setAttribute(format, UNUM_FRACTION_DIGITS, 6); } /* set whether to show the sign */ if (info->fShowSign) { u_printf_set_sign(format, info, prefixBuffer, &prefixBufferLen, &status); } /* format the number */ resultLen = unum_formatDouble(format, num, result, UPRINTF_BUFFER_SIZE, nullptr, &status); if (U_FAILURE(status)) { resultLen = 0; } /* restore the number format */ /* TODO: Is this needed? */ unum_setAttribute(format, UNUM_MIN_FRACTION_DIGITS, minDecimalDigits); unum_setAttribute(format, UNUM_MAX_FRACTION_DIGITS, maxDecimalDigits); /* Since we're the only one using the scientific format, we don't need to save the old exponent value. */ /*unum_setSymbol(format, UNUM_EXPONENTIAL_SYMBOL, srcExpBuf, srcLen, &status);*/ if (info->fShowSign) { /* Reset back to original value regardless of what the error was */ UErrorCode localStatus = U_ZERO_ERROR; u_printf_reset_sign(format, info, prefixBuffer, &prefixBufferLen, &localStatus); } return handler->pad_and_justify(context, info, result, resultLen); } static int32_t u_printf_percent_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args) { double num = (double) (args[0].doubleValue); UNumberFormat *format; char16_t result[UPRINTF_BUFFER_SIZE]; char16_t prefixBuffer[UPRINTF_BUFFER_SIZE]; int32_t prefixBufferLen = sizeof(prefixBuffer); int32_t minDecimalDigits; int32_t maxDecimalDigits; int32_t resultLen; UErrorCode status = U_ZERO_ERROR; prefixBuffer[0] = 0; /* mask off any necessary bits */ /* if(! info->fIsLongDouble) num &= DBL_MAX;*/ /* get the formatter */ format = u_locbund_getNumberFormat(formatBundle, UNUM_PERCENT); /* handle error */ if (format == nullptr) return 0; /* save the formatter's state */ minDecimalDigits = unum_getAttribute(format, UNUM_MIN_FRACTION_DIGITS); maxDecimalDigits = unum_getAttribute(format, UNUM_MAX_FRACTION_DIGITS); /* set the appropriate flags and number of decimal digits on the formatter */ if(info->fPrecision != -1) { /* set the # of decimal digits */ unum_setAttribute(format, UNUM_FRACTION_DIGITS, info->fPrecision); } else if(info->fAlt) { /* '#' means always show decimal point */ /* copy of printf behavior on Solaris - '#' shows 6 digits */ unum_setAttribute(format, UNUM_FRACTION_DIGITS, 6); } else { /* # of decimal digits is 6 if precision not specified */ unum_setAttribute(format, UNUM_FRACTION_DIGITS, 6); } /* set whether to show the sign */ if (info->fShowSign) { u_printf_set_sign(format, info, prefixBuffer, &prefixBufferLen, &status); } /* format the number */ resultLen = unum_formatDouble(format, num, result, UPRINTF_BUFFER_SIZE, nullptr, &status); if (U_FAILURE(status)) { resultLen = 0; } /* restore the number format */ /* TODO: Is this needed? */ unum_setAttribute(format, UNUM_MIN_FRACTION_DIGITS, minDecimalDigits); unum_setAttribute(format, UNUM_MAX_FRACTION_DIGITS, maxDecimalDigits); if (info->fShowSign) { /* Reset back to original value regardless of what the error was */ UErrorCode localStatus = U_ZERO_ERROR; u_printf_reset_sign(format, info, prefixBuffer, &prefixBufferLen, &localStatus); } return handler->pad_and_justify(context, info, result, resultLen); } static int32_t u_printf_ustring_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args) { (void)formatBundle; int32_t len, written; const char16_t *arg = (const char16_t*)(args[0].ptrValue); /* allocate enough space for the buffer */ if (arg == nullptr) { arg = gNullStr; } len = u_strlen(arg); /* width = minimum # of characters to write */ /* precision = maximum # of characters to write */ if (info->fPrecision != -1 && info->fPrecision < len) { len = info->fPrecision; } /* determine if the string should be padded */ written = handler->pad_and_justify(context, info, arg, len); return written; } static int32_t u_printf_uchar_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args) { (void)formatBundle; int32_t written = 0; char16_t arg = (char16_t)(args[0].int64Value); /* width = minimum # of characters to write */ /* precision = maximum # of characters to write */ /* precision is ignored when handling a uchar */ /* determine if the string should be padded */ written = handler->pad_and_justify(context, info, &arg, 1); return written; } static int32_t u_printf_scidbl_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args) { u_printf_spec_info scidbl_info; double num = args[0].doubleValue; int32_t retVal; UNumberFormat *format; int32_t maxSigDecimalDigits, significantDigits; memcpy(&scidbl_info, info, sizeof(u_printf_spec_info)); /* determine whether to use 'd', 'e' or 'f' notation */ if (scidbl_info.fPrecision == -1 && num == uprv_trunc(num)) { /* use 'f' notation */ scidbl_info.fSpec = 0x0066; scidbl_info.fPrecision = 0; /* call the double handler */ retVal = u_printf_double_handler(handler, context, formatBundle, &scidbl_info, args); } else if(num < 0.0001 || (scidbl_info.fPrecision < 1 && 1000000.0 <= num) || (scidbl_info.fPrecision != -1 && num > uprv_pow10(scidbl_info.fPrecision))) { /* use 'e' or 'E' notation */ scidbl_info.fSpec = scidbl_info.fSpec - 2; if (scidbl_info.fPrecision == -1) { scidbl_info.fPrecision = 5; } /* call the scientific handler */ retVal = u_printf_scientific_handler(handler, context, formatBundle, &scidbl_info, args); } else { format = u_locbund_getNumberFormat(formatBundle, UNUM_DECIMAL); /* Check for null pointer */ if (format == nullptr) { return 0; } maxSigDecimalDigits = unum_getAttribute(format, UNUM_MAX_SIGNIFICANT_DIGITS); significantDigits = scidbl_info.fPrecision; /* use 'f' notation */ scidbl_info.fSpec = 0x0066; if (significantDigits == -1) { significantDigits = 6; } unum_setAttribute(format, UNUM_SIGNIFICANT_DIGITS_USED, true); unum_setAttribute(format, UNUM_MAX_SIGNIFICANT_DIGITS, significantDigits); /* call the double handler */ retVal = u_printf_double_handler(handler, context, formatBundle, &scidbl_info, args); unum_setAttribute(format, UNUM_MAX_SIGNIFICANT_DIGITS, maxSigDecimalDigits); unum_setAttribute(format, UNUM_SIGNIFICANT_DIGITS_USED, false); } return retVal; } static int32_t u_printf_count_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args) { (void)handler; (void)context; (void)formatBundle; int32_t *count = (int32_t*)(args[0].ptrValue); /* in the special case of count, the u_printf_spec_info's width */ /* will contain the # of chars written thus far */ *count = info->fWidth; return 0; } static int32_t u_printf_spellout_handler(const u_printf_stream_handler *handler, void *context, ULocaleBundle *formatBundle, const u_printf_spec_info *info, const ufmt_args *args) { double num = (double) (args[0].doubleValue); UNumberFormat *format; char16_t result[UPRINTF_BUFFER_SIZE]; char16_t prefixBuffer[UPRINTF_BUFFER_SIZE]; int32_t prefixBufferLen = sizeof(prefixBuffer); int32_t minDecimalDigits; int32_t maxDecimalDigits; int32_t resultLen; UErrorCode status = U_ZERO_ERROR; prefixBuffer[0] = 0; /* mask off any necessary bits */ /* if(! info->fIsLongDouble) num &= DBL_MAX;*/ /* get the formatter */ format = u_locbund_getNumberFormat(formatBundle, UNUM_SPELLOUT); /* handle error */ if (format == nullptr) return 0; /* save the formatter's state */ minDecimalDigits = unum_getAttribute(format, UNUM_MIN_FRACTION_DIGITS); maxDecimalDigits = unum_getAttribute(format, UNUM_MAX_FRACTION_DIGITS); /* set the appropriate flags and number of decimal digits on the formatter */ if(info->fPrecision != -1) { /* set the # of decimal digits */ unum_setAttribute(format, UNUM_FRACTION_DIGITS, info->fPrecision); } else if(info->fAlt) { /* '#' means always show decimal point */ /* copy of printf behavior on Solaris - '#' shows 6 digits */ unum_setAttribute(format, UNUM_FRACTION_DIGITS, 6); } else { /* # of decimal digits is 6 if precision not specified */ unum_setAttribute(format, UNUM_FRACTION_DIGITS, 6); } /* set whether to show the sign */ if (info->fShowSign) { u_printf_set_sign(format, info, prefixBuffer, &prefixBufferLen, &status); } /* format the number */ resultLen = unum_formatDouble(format, num, result, UPRINTF_BUFFER_SIZE, nullptr, &status); if (U_FAILURE(status)) { resultLen = 0; } /* restore the number format */ /* TODO: Is this needed? */ unum_setAttribute(format, UNUM_MIN_FRACTION_DIGITS, minDecimalDigits); unum_setAttribute(format, UNUM_MAX_FRACTION_DIGITS, maxDecimalDigits); if (info->fShowSign) { /* Reset back to original value regardless of what the error was */ UErrorCode localStatus = U_ZERO_ERROR; u_printf_reset_sign(format, info, prefixBuffer, &prefixBufferLen, &localStatus); } return handler->pad_and_justify(context, info, result, resultLen); } /* Use US-ASCII characters only for formatting. Most codepages have characters 20-7F from Unicode. Using any other codepage specific characters will make it very difficult to format the string on non-Unicode machines */ static const u_printf_info g_u_printf_infos[UPRINTF_NUM_FMT_HANDLERS] = { /* 0x20 */ UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_SIMPLE_PERCENT,UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, /* 0x30 */ UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, /* 0x40 */ UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_UCHAR, UFMT_EMPTY, UFMT_SCIENTIFIC, UFMT_EMPTY, UFMT_SCIDBL, #ifdef U_USE_OBSOLETE_IO_FORMATTING UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_UCHAR/*deprecated*/, #else UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, #endif UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, /* 0x50 */ UFMT_PERCENT, UFMT_EMPTY, UFMT_EMPTY, UFMT_USTRING, #ifdef U_USE_OBSOLETE_IO_FORMATTING UFMT_EMPTY, UFMT_USTRING/*deprecated*/,UFMT_SPELLOUT, UFMT_EMPTY, #else UFMT_EMPTY, UFMT_EMPTY, UFMT_SPELLOUT, UFMT_EMPTY, #endif UFMT_HEX, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, /* 0x60 */ UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_CHAR, UFMT_INT, UFMT_SCIENTIFIC, UFMT_DOUBLE, UFMT_SCIDBL, UFMT_EMPTY, UFMT_INT, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_COUNT, UFMT_OCTAL, /* 0x70 */ UFMT_POINTER, UFMT_EMPTY, UFMT_EMPTY, UFMT_STRING, UFMT_EMPTY, UFMT_UINT, UFMT_EMPTY, UFMT_EMPTY, UFMT_HEX, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, UFMT_EMPTY, }; /* flag characters for uprintf */ #define FLAG_MINUS 0x002D #define FLAG_PLUS 0x002B #define FLAG_SPACE 0x0020 #define FLAG_POUND 0x0023 #define FLAG_ZERO 0x0030 #define FLAG_PAREN 0x0028 #define ISFLAG(s) (s) == FLAG_MINUS || \ (s) == FLAG_PLUS || \ (s) == FLAG_SPACE || \ (s) == FLAG_POUND || \ (s) == FLAG_ZERO || \ (s) == FLAG_PAREN /* special characters for uprintf */ #define SPEC_ASTERISK 0x002A #define SPEC_DOLLARSIGN 0x0024 #define SPEC_PERIOD 0x002E #define SPEC_PERCENT 0x0025 /* unicode digits */ #define DIGIT_ZERO 0x0030 #define DIGIT_ONE 0x0031 #define DIGIT_TWO 0x0032 #define DIGIT_THREE 0x0033 #define DIGIT_FOUR 0x0034 #define DIGIT_FIVE 0x0035 #define DIGIT_SIX 0x0036 #define DIGIT_SEVEN 0x0037 #define DIGIT_EIGHT 0x0038 #define DIGIT_NINE 0x0039 #define ISDIGIT(s) (s) == DIGIT_ZERO || \ (s) == DIGIT_ONE || \ (s) == DIGIT_TWO || \ (s) == DIGIT_THREE || \ (s) == DIGIT_FOUR || \ (s) == DIGIT_FIVE || \ (s) == DIGIT_SIX || \ (s) == DIGIT_SEVEN || \ (s) == DIGIT_EIGHT || \ (s) == DIGIT_NINE /* u_printf modifiers */ #define MOD_H 0x0068 #define MOD_LOWERL 0x006C #define MOD_L 0x004C #define ISMOD(s) (s) == MOD_H || \ (s) == MOD_LOWERL || \ (s) == MOD_L /* Returns an array of the parsed argument type given in the format string. */ static ufmt_args* parseArguments(const char16_t *alias, va_list ap, UErrorCode *status) { ufmt_args *arglist = nullptr; ufmt_type_info *typelist = nullptr; UBool *islonglong = nullptr; int32_t size = 0; int32_t pos = 0; char16_t type; uint16_t handlerNum; const char16_t *aliasStart = alias; /* get maximum number of arguments */ for(;;) { /* find % */ while(*alias != UP_PERCENT && *alias != 0x0000) { alias++; } if(*alias == 0x0000) { break; } alias++; /* handle the pos number */ if(ISDIGIT(*alias)) { /* handle positional parameters */ if(ISDIGIT(*alias)) { pos = (int) (*alias++ - DIGIT_ZERO); while(ISDIGIT(*alias)) { pos *= 10; pos += (int) (*alias++ - DIGIT_ZERO); } } /* if there is no '$', don't read anything */ if(*alias != SPEC_DOLLARSIGN) { return nullptr; } } else { return nullptr; } if (pos > size) { size = pos; } } /* create the parsed argument list */ typelist = (ufmt_type_info*)uprv_malloc(sizeof(ufmt_type_info) * size); islonglong = (UBool*)uprv_malloc(sizeof(UBool) * size); arglist = (ufmt_args*)uprv_malloc(sizeof(ufmt_args) * size); /* If malloc failed, return nullptr */ if (!typelist || !islonglong || !arglist) { if (typelist) { uprv_free(typelist); } if (islonglong) { uprv_free(islonglong); } if (arglist) { uprv_free(arglist); } *status = U_MEMORY_ALLOCATION_ERROR; return nullptr; } /* reset alias back to the beginning */ alias = aliasStart; for(;;) { /* find % */ while(*alias != UP_PERCENT && *alias != 0x0000) { alias++; } if(*alias == 0x0000) { break; } alias++; /* handle positional parameters */ if(ISDIGIT(*alias)) { pos = (int) (*alias++ - DIGIT_ZERO); while(ISDIGIT(*alias)) { pos *= 10; pos += (int) (*alias++ - DIGIT_ZERO); } } /* offset position by 1 */ pos--; /* skip over everything except for the type */ while (ISMOD(*alias) || ISFLAG(*alias) || ISDIGIT(*alias) || *alias == SPEC_ASTERISK || *alias == SPEC_PERIOD || *alias == SPEC_DOLLARSIGN) { islonglong[pos] = false; if (ISMOD(*alias)) { alias++; if (*alias == MOD_LOWERL) { islonglong[pos] = true; } } alias++; } type = *alias; /* store the argument type in the correct position of the parsed argument list */ handlerNum = (uint16_t)(type - UPRINTF_BASE_FMT_HANDLERS); if (handlerNum < UPRINTF_NUM_FMT_HANDLERS) { typelist[pos] = g_u_printf_infos[ handlerNum ].info; } else { typelist[pos] = ufmt_empty; } } /* store argument in arglist */ for (pos = 0; pos < size; pos++) { switch (typelist[pos]) { case ufmt_string: case ufmt_ustring: case ufmt_pointer: arglist[pos].ptrValue = va_arg(ap, void*); break; case ufmt_char: case ufmt_uchar: case ufmt_int: if (islonglong[pos]) { arglist[pos].int64Value = va_arg(ap, int64_t); } else { arglist[pos].int64Value = va_arg(ap, int32_t); } break; case ufmt_float: arglist[pos].floatValue = (float) va_arg(ap, double); break; case ufmt_double: arglist[pos].doubleValue = va_arg(ap, double); break; default: /* else args is ignored */ arglist[pos].ptrValue = nullptr; break; } } uprv_free(typelist); uprv_free(islonglong); return arglist; } /* We parse the argument list in Unicode */ U_CFUNC int32_t u_printf_parse(const u_printf_stream_handler *streamHandler, const char16_t *fmt, void *context, u_localized_print_string *locStringContext, ULocaleBundle *formatBundle, int32_t *written, va_list ap) { uint16_t handlerNum; ufmt_args args; ufmt_type_info argType; u_printf_handler *handler; u_printf_spec spec; u_printf_spec_info *info = &(spec.fInfo); const char16_t *alias = fmt; const char16_t *backup; const char16_t *lastAlias; const char16_t *orgAlias = fmt; /* parsed argument list */ ufmt_args *arglist = nullptr; /* initialized it to avoid compiler warnings */ UErrorCode status = U_ZERO_ERROR; if (!locStringContext || locStringContext->available >= 0) { /* get the parsed list of argument types */ arglist = parseArguments(orgAlias, ap, &status); /* Return error if parsing failed. */ if (U_FAILURE(status)) { return -1; } } /* iterate through the pattern */ while(!locStringContext || locStringContext->available >= 0) { /* find the next '%' */ lastAlias = alias; while(*alias != UP_PERCENT && *alias != 0x0000) { alias++; } /* write any characters before the '%' */ if(alias > lastAlias) { *written += (streamHandler->write)(context, lastAlias, (int32_t)(alias - lastAlias)); } /* break if at end of string */ if(*alias == 0x0000) { break; } /* initialize spec to default values */ spec.fWidthPos = -1; spec.fPrecisionPos = -1; spec.fArgPos = -1; uprv_memset(info, 0, sizeof(*info)); info->fPrecision = -1; info->fWidth = -1; info->fPadChar = 0x0020; /* skip over the initial '%' */ alias++; /* Check for positional argument */ if(ISDIGIT(*alias)) { /* Save the current position */ backup = alias; /* handle positional parameters */ if(ISDIGIT(*alias)) { spec.fArgPos = (int) (*alias++ - DIGIT_ZERO); while(ISDIGIT(*alias)) { spec.fArgPos *= 10; spec.fArgPos += (int) (*alias++ - DIGIT_ZERO); } } /* if there is no '$', don't read anything */ if(*alias != SPEC_DOLLARSIGN) { spec.fArgPos = -1; alias = backup; } /* munge the '$' */ else alias++; } /* Get any format flags */ while(ISFLAG(*alias)) { switch(*alias++) { /* left justify */ case FLAG_MINUS: info->fLeft = true; break; /* always show sign */ case FLAG_PLUS: info->fShowSign = true; break; /* use space if no sign present */ case FLAG_SPACE: info->fShowSign = true; info->fSpace = true; break; /* use alternate form */ case FLAG_POUND: info->fAlt = true; break; /* pad with leading zeroes */ case FLAG_ZERO: info->fZero = true; info->fPadChar = 0x0030; break; /* pad character specified */ case FLAG_PAREN: /* TODO test that all four are numbers */ /* first four characters are hex values for pad char */ info->fPadChar = (char16_t)ufmt_digitvalue(*alias++); info->fPadChar = (char16_t)((info->fPadChar * 16) + ufmt_digitvalue(*alias++)); info->fPadChar = (char16_t)((info->fPadChar * 16) + ufmt_digitvalue(*alias++)); info->fPadChar = (char16_t)((info->fPadChar * 16) + ufmt_digitvalue(*alias++)); /* final character is ignored */ alias++; break; } } /* Get the width */ /* width is specified out of line */ if(*alias == SPEC_ASTERISK) { info->fWidth = -2; /* Skip the '*' */ alias++; /* Save the current position */ backup = alias; /* handle positional parameters */ if(ISDIGIT(*alias)) { spec.fWidthPos = (int) (*alias++ - DIGIT_ZERO); while(ISDIGIT(*alias)) { spec.fWidthPos *= 10; spec.fWidthPos += (int) (*alias++ - DIGIT_ZERO); } } /* if there is no '$', don't read anything */ if(*alias != SPEC_DOLLARSIGN) { spec.fWidthPos = -1; alias = backup; } /* munge the '$' */ else alias++; } /* read the width, if present */ else if(ISDIGIT(*alias)){ info->fWidth = (int) (*alias++ - DIGIT_ZERO); while(ISDIGIT(*alias)) { info->fWidth *= 10; info->fWidth += (int) (*alias++ - DIGIT_ZERO); } } /* Get the precision */ if(*alias == SPEC_PERIOD) { /* eat up the '.' */ alias++; /* precision is specified out of line */ if(*alias == SPEC_ASTERISK) { info->fPrecision = -2; /* Skip the '*' */ alias++; /* save the current position */ backup = alias; /* handle positional parameters */ if(ISDIGIT(*alias)) { spec.fPrecisionPos = (int) (*alias++ - DIGIT_ZERO); while(ISDIGIT(*alias)) { spec.fPrecisionPos *= 10; spec.fPrecisionPos += (int) (*alias++ - DIGIT_ZERO); } /* if there is no '$', don't read anything */ if(*alias != SPEC_DOLLARSIGN) { spec.fPrecisionPos = -1; alias = backup; } else { /* munge the '$' */ alias++; } } } /* read the precision */ else if(ISDIGIT(*alias)){ info->fPrecision = (int) (*alias++ - DIGIT_ZERO); while(ISDIGIT(*alias)) { info->fPrecision *= 10; info->fPrecision += (int) (*alias++ - DIGIT_ZERO); } } } /* Get any modifiers */ if(ISMOD(*alias)) { switch(*alias++) { /* short */ case MOD_H: info->fIsShort = true; break; /* long or long long */ case MOD_LOWERL: if(*alias == MOD_LOWERL) { info->fIsLongLong = true; /* skip over the next 'l' */ alias++; } else info->fIsLong = true; break; /* long double */ case MOD_L: info->fIsLongDouble = true; break; } } /* finally, get the specifier letter */ info->fSpec = *alias++; info->fOrigSpec = info->fSpec; /* fill in the precision and width, if specified out of line */ /* width specified out of line */ if(spec.fInfo.fWidth == -2) { if(spec.fWidthPos == -1) { /* read the width from the argument list */ info->fWidth = va_arg(ap, int32_t); } /* else handle positional parameter */ /* if it's negative, take the absolute value and set left alignment */ if(info->fWidth < 0) { info->fWidth *= -1; /* Make positive */ info->fLeft = true; } } /* precision specified out of line */ if(info->fPrecision == -2) { if(spec.fPrecisionPos == -1) { /* read the precision from the argument list */ info->fPrecision = va_arg(ap, int32_t); } /* else handle positional parameter */ /* if it's negative, set it to zero */ if(info->fPrecision < 0) info->fPrecision = 0; } handlerNum = (uint16_t)(info->fSpec - UPRINTF_BASE_FMT_HANDLERS); if (handlerNum < UPRINTF_NUM_FMT_HANDLERS) { /* query the info function for argument information */ argType = g_u_printf_infos[ handlerNum ].info; /* goto the correct argument on arg_list if position is specified */ if (spec.fArgPos > 0) { /* offset position by 1 */ spec.fArgPos--; switch(argType) { case ufmt_count: /* set the spec's width to the # of chars written */ info->fWidth = *written; /* fall through to set the pointer */ U_FALLTHROUGH; case ufmt_string: case ufmt_ustring: case ufmt_pointer: args.ptrValue = arglist[spec.fArgPos].ptrValue; break; case ufmt_char: case ufmt_uchar: case ufmt_int: args.int64Value = arglist[spec.fArgPos].int64Value; break; case ufmt_float: args.floatValue = arglist[spec.fArgPos].floatValue; break; case ufmt_double: args.doubleValue = arglist[spec.fArgPos].doubleValue; break; default: /* else args is ignored */ args.ptrValue = nullptr; break; } } else { /* no positional argument specified */ switch(argType) { case ufmt_count: /* set the spec's width to the # of chars written */ info->fWidth = *written; /* fall through to set the pointer */ U_FALLTHROUGH; case ufmt_string: case ufmt_ustring: case ufmt_pointer: args.ptrValue = va_arg(ap, void*); break; case ufmt_char: case ufmt_uchar: case ufmt_int: if (info->fIsLongLong) { args.int64Value = va_arg(ap, int64_t); } else { args.int64Value = va_arg(ap, int32_t); } break; case ufmt_float: args.floatValue = (float) va_arg(ap, double); break; case ufmt_double: args.doubleValue = va_arg(ap, double); break; default: /* else args is ignored */ args.ptrValue = nullptr; break; } } /* call the handler function */ handler = g_u_printf_infos[ handlerNum ].handler; if (handler != nullptr) { *written += (*handler)(streamHandler, context, formatBundle, info, &args); } else { /* just echo unknown tags */ *written += (streamHandler->write)(context, fmt, (int32_t)(alias - lastAlias)); } } else { /* just echo unknown tags */ *written += (streamHandler->write)(context, fmt, (int32_t)(alias - lastAlias)); } } /* delete parsed argument list */ if (arglist != nullptr) { uprv_free(arglist); } /* return # of characters in this format that have been parsed. */ return (int32_t)(alias - fmt); } #endif /* #if !UCONFIG_NO_FORMATTING */