Commit d537c955 authored by Jon Griffiths's avatar Jon Griffiths Committed by Alexandre Julliard

Implemented the variant formatting functions.

Bool->bstr makes a number str unless [ALPHA|LOCAL]BOOL is passed.
parent f9bca5ea
......@@ -26,6 +26,7 @@ C_SRCS = \
tmarshal.c \
typelib.c \
usrmarshal.c \
varformat.c \
variant.c
C_SRCS16 = \
......
......@@ -104,7 +104,7 @@
104 stdcall VarCyFromStr(ptr long long ptr)
105 stub VarCyFromDisp
106 stdcall VarCyFromBool(long ptr)
107 stub VarFormatNumber # stdcall (ptr long long long long long ptr)
107 stdcall VarFormatNumber(ptr long long long long long ptr)
108 stdcall VarBstrFromUI1(long long long ptr)
109 stdcall VarBstrFromI2(long long long ptr)
110 stdcall VarBstrFromI4(long long long ptr)
......@@ -114,7 +114,7 @@
114 stdcall VarBstrFromDate(double long long ptr)
115 stub VarBstrFromDisp
116 stdcall VarBstrFromBool(long long long ptr)
117 stub VarFormatPercent # stdcall (ptr long long long long long ptr)
117 stdcall VarFormatPercent(ptr long long long long long ptr)
118 stdcall VarBoolFromUI1(long ptr)
119 stdcall VarBoolFromI2(long ptr)
120 stdcall VarBoolFromI4(long ptr)
......
/*
* Variant formatting functions
*
* Copyright 2003 Jon Griffiths
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* NOTES
* Since the formatting functions aren't properly documented, I used the
* Visual Basic documentation as a guide to implementing these functions. This
* means that some named or user-defined formats may work slightly differently.
* Please submit a test case if you find a difference.
*/
#include "config.h"
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#define NONAMELESSUNION
#define NONAMELESSSTRUCT
#include "windef.h"
#include "winbase.h"
#include "oleauto.h"
#include "wine/debug.h"
#include "wine/unicode.h"
#include "winerror.h"
#include "variant.h"
WINE_DEFAULT_DEBUG_CHANNEL(ole);
/* Make sure internal conversions to strings use the '.','+'/'-' and ','
* format chars from the US locale. This enables us to parse the created
* strings to determine the number of decimal places, exponent, etc.
*/
#define LCID_US MAKELCID(MAKELANGID(LANG_ENGLISH,SUBLANG_ENGLISH_US),SORT_DEFAULT)
static const WCHAR szPercent_d[] = { '%','d','\0' };
static const WCHAR szPercentZeroTwo_d[] = { '%','0','2','d','\0' };
static const WCHAR szPercentZeroFour_d[] = { '%','0','4','d','\0' };
static const WCHAR szPercentZeroStar_d[] = { '%','0','*','d','\0' };
#if 0
#define dump_tokens(rgb) do { \
int i_; TRACE("Tokens->{ \n"); \
for (i_ = 0; i_ < rgb[0]; i_++) \
TRACE("%s0x%02x", i_?",":"",rgb[i_]); \
TRACE(" }\n"); \
} while(0)
#endif
/******************************************************************************
* Variant-Formats {OLEAUT32}
*
* NOTES
* When formatting a variant a variety of format strings may be used to generate
* different kinds of formatted output. A format string consists of either a named
* format, or a user-defined format.
*
* The following named formats are defined:
*| Name Description
*| ---- -----------
*| General Date Display Date, and time for non-integer values
*| Short Date Short date format as defined by locale settings
*| Medium Date Medium date format as defined by locale settings
*| Long Date Long date format as defined by locale settings
*| Short Time Short Time format as defined by locale settings
*| Medium Time Medium time format as defined by locale settings
*| Long Time Long time format as defined by locale settings
*| True/False Localised text of "True" or "False"
*| Yes/No Localised text of "Yes" or "No"
*| On/Off Localised text of "On" or "Off"
*| General Number No thousands seperator. No decimal points for integers
*| Currency General currency format using localised characters
*| Fixed At least one whole and two fractional digits
*| Standard As for 'Fixed', but including decimal seperators
*| Percent Multiply by 100 and dispaly a traling '%' character
*| Scientific Display with exponent
*
* User-defined formats consist of a combination of tokens and literal
* characters. Literal characters are copied unmodified to the formatted
* output at the position they occupy in the format string. Any character
* that is not recognised as a token is treated as a literal. A literal can
* also be specified by preceeding it with a backslash character
* (e.g. "\L\i\t\e\r\a\l") or enclosing it in double quotes.
*
* A user-defined format can have up to 4 sections, depending on the type of
* format, The following table lists sections and their meaning:
*| Format Type Sections Meaning
*| ----------- -------- -------
*| Number 1 Use the same format for all numbers
*| Number 2 Use format 1 for positive and 2 for negative numbers
*| Number 3 Use format 1 for positive, 2 for zero, and 3
*| for negative numbers.
*| Number 4 Use format 1 for positive, 2 for zero, 3 for
*| negative, and 4 for null numbers.
*| String 1 Use the same format for all strings
*| String 2 Use format 2 for null and empty strings, otherwise
*| use format 1.
*| Date 1 Use the same format for all dates
*
* The formatting tokens fall into several categories depending on the type
* of formatted output. For more information on each type, see
* VarFormat-Dates(), VarFormat-Strings() and VarFormat-Numbers().
*
* SEE ALSO
* VarTokenizeFormatString(), VarFormatFromTokens(), VarFormat(),
* VarFormatDateTime(), VarFormatNumber(), VarFormatCurrency().
*/
/******************************************************************************
* VarFormat-Strings {OLEAUT32}
*
* NOTES
* When formatting a variant as a string, it is first converted to a VT_BSTR.
* The user-format string defines which characters are copied into which
* positions in the output string. Literals may be inserted in the format
* string. When creating the formatted string, excess characters in the string
* (those not consumed by a token) are appended to the end of the output. If
* there are more tokens than characters in the string to format, spaces will
* be inserted at the start of the string if the '@' token was used.
*
* By default strings are converted to lowercase, or uppercase if the '>' token
* is encountered. This applies to the whole string: it is not possible to
* generate a mixed-case output string.
*
* In user-defined string formats, the following tokens are recognised:
*| Token Description
*| ----- -----------
*| '@' Copy a char from the source, or a space if no chars are left.
*| '&' Copy a char from the source, or write nothing if no chars are left.
*| '<' Output the whole string as lower-case (the default).
*| '>' Output the whole string as upper-case.
*| '!' MSDN indicates that this character should cause right-to-left
*| copying, however tests show that it is tokenised but not processed.
*/
/*
* Common format definitions
*/
/* Fomat types */
#define FMT_TYPE_UNKNOWN 0x0
#define FMT_TYPE_GENERAL 0x1
#define FMT_TYPE_NUMBER 0x2
#define FMT_TYPE_DATE 0x3
#define FMT_TYPE_STRING 0x4
#define FMT_TO_STRING 0x0 /* If header->size == this, act like VB's Str() fn */
typedef struct tagFMT_SHORT_HEADER
{
BYTE size; /* Size of tokenised block (including header), or FMT_TO_STRING */
BYTE type; /* Allowable types (FMT_TYPE_*) */
BYTE offset[1]; /* Offset of the first (and only) format section */
} FMT_SHORT_HEADER;
typedef struct tagFMT_HEADER
{
BYTE size; /* Total size of the whole tokenised block (including header) */
BYTE type; /* Allowable types (FMT_TYPE_*) */
BYTE starts[4]; /* Offset of each of the 4 format sections, or 0 if none */
} FMT_HEADER;
#define FmtGetPositive(x) (x->starts[0])
#define FmtGetNegative(x) (x->starts[1] ? x->starts[1] : x->starts[0])
#define FmtGetZero(x) (x->starts[2] ? x->starts[2] : x->starts[0])
#define FmtGetNull(x) (x->starts[3] ? x->starts[3] : x->starts[0])
/*
* String formats
*/
#define FMT_FLAG_LT 0x1 /* Has '<' (lower case) */
#define FMT_FLAG_GT 0x2 /* Has '>' (upper case) */
#define FMT_FLAG_RTL 0x4 /* Has '!' (Copy right to left) */
typedef struct tagFMT_STRING_HEADER
{
BYTE flags; /* LT, GT, RTL */
BYTE unknown1;
BYTE unknown2;
BYTE copy_chars; /* Number of chars to be copied */
BYTE unknown3;
} FMT_STRING_HEADER;
/*
* Number formats
*/
#define FMT_FLAG_PERCENT 0x1 /* Has '%' (Percentage) */
#define FMT_FLAG_EXPONENT 0x2 /* Has 'e' (Exponent/Scientific notation) */
#define FMT_FLAG_THOUSANDS 0x4 /* Has ',' (Standard use of the thousands seperator) */
#define FMT_FLAG_BOOL 0x20 /* Boolean format */
typedef struct tagFMT_NUMBER_HEADER
{
BYTE flags; /* PERCENT, EXPONENT, THOUSANDS, BOOL */
BYTE multiplier; /* Multiplier, 100 for percentages */
BYTE divisor; /* Divisor, 1000 if '%%' was used */
BYTE whole; /* Number of digits before the decimal point */
BYTE fractional; /* Number of digits after the decimal point */
} FMT_NUMBER_HEADER;
/*
* Date Formats
*/
typedef struct tagFMT_DATE_HEADER
{
BYTE flags;
BYTE unknown1;
BYTE unknown2;
BYTE unknown3;
BYTE unknown4;
} FMT_DATE_HEADER;
/*
* Format token values
*/
#define FMT_GEN_COPY 0x00 /* \n, "lit" => 0,pos,len: Copy len chars from input+pos */
#define FMT_GEN_INLINE 0x01 /* => 1,len,[chars]: Copy len chars from token stream */
#define FMT_GEN_END 0x02 /* \0,; => 2: End of the tokenised format */
#define FMT_DATE_TIME_SEP 0x03 /* Time seperator char */
#define FMT_DATE_DATE_SEP 0x04 /* Date seperator char */
#define FMT_DATE_GENERAL 0x05 /* General format date */
#define FMT_DATE_QUARTER 0x06 /* Quarter of the year from 1-4 */
#define FMT_DATE_TIME_SYS 0x07 /* System long time format */
#define FMT_DATE_DAY 0x08 /* Day with no leading 0 */
#define FMT_DATE_DAY_0 0x09 /* Day with leading 0 */
#define FMT_DATE_DAY_SHORT 0x0A /* Short day name */
#define FMT_DATE_DAY_LONG 0x0B /* Long day name */
#define FMT_DATE_SHORT 0x0C /* Short date format */
#define FMT_DATE_LONG 0x0D /* Long date format */
#define FMT_DATE_MEDIUM 0x0E /* Medium date format */
#define FMT_DATE_DAY_WEEK 0x0F /* First day of the week */
#define FMT_DATE_WEEK_YEAR 0x10 /* First week of the year */
#define FMT_DATE_MON 0x11 /* Month with no leading 0 */
#define FMT_DATE_MON_0 0x12 /* Month with leading 0 */
#define FMT_DATE_MON_SHORT 0x13 /* Short month name */
#define FMT_DATE_MON_LONG 0x14 /* Long month name */
#define FMT_DATE_YEAR_DOY 0x15 /* Day of the year with no leading 0 */
#define FMT_DATE_YEAR_0 0x16 /* 2 digit year with leading 0 */
/* NOTE: token 0x17 is not defined, 'yyy' is not valid */
#define FMT_DATE_YEAR_LONG 0x18 /* 4 digit year */
#define FMT_DATE_MIN 0x1A /* Minutes with no leading 0 */
#define FMT_DATE_MIN_0 0x1B /* Minutes with leading 0 */
#define FMT_DATE_SEC 0x1C /* Seconds with no leading 0 */
#define FMT_DATE_SEC_0 0x1D /* Seconds with leading 0 */
#define FMT_DATE_HOUR 0x1E /* Hours with no leading 0 */
#define FMT_DATE_HOUR_0 0x1F /* Hours with leading 0 */
#define FMT_DATE_HOUR_12 0x20 /* Hours with no leading 0, 12 hour clock */
#define FMT_DATE_HOUR_12_0 0x21 /* Hours with leading 0, 12 hour clock */
#define FMT_DATE_TIME_UNK2 0x23
/* FIXME: probably missing some here */
#define FMT_DATE_AMPM_SYS1 0x2E /* AM/PM as defined by system settings */
#define FMT_DATE_AMPM_UPPER 0x2F /* Upper-case AM or PM */
#define FMT_DATE_A_UPPER 0x30 /* Upper-case A or P */
#define FMT_DATE_AMPM_SYS2 0x31 /* AM/PM as defined by system settings */
#define FMT_DATE_AMPM_LOWER 0x32 /* Lower-case AM or PM */
#define FMT_DATE_A_LOWER 0x33 /* Lower-case A or P */
#define FMT_NUM_COPY_ZERO 0x34 /* Copy 1 digit or 0 if no digit */
#define FMT_NUM_COPY_SKIP 0x35 /* Copy 1 digit or skip if no digit */
#define FMT_NUM_DECIMAL 0x36 /* Decimal seperator */
#define FMT_NUM_EXP_POS_U 0x37 /* Scientific notation, uppercase, + sign */
#define FMT_NUM_EXP_NEG_U 0x38 /* Scientific notation, lowercase, - sign */
#define FMT_NUM_EXP_POS_L 0x39 /* Scientific notation, uppercase, + sign */
#define FMT_NUM_EXP_NEG_L 0x3A /* Scientific notation, lowercase, - sign */
#define FMT_NUM_CURRENCY 0x3B /* Currency symbol */
#define FMT_NUM_TRUE_FALSE 0x3D /* Convert to "True" or "False" */
#define FMT_NUM_YES_NO 0x3E /* Convert to "Yes" or "No" */
#define FMT_NUM_ON_OFF 0x3F /* Convert to "On" or "Off" */
#define FMT_STR_COPY_SPACE 0x40 /* Copy len chars with space if no char */
#define FMT_STR_COPY_SKIP 0x41 /* Copy len chars or skip if no char */
/* Wine additions */
#define FMT_WINE_HOURS_12 0x81 /* Hours using 12 hour clockhourCopy len chars or skip if no char */
/* Named Formats and their tokenised values */
static const WCHAR szGeneralDate[] = { 'G','e','n','e','r','a','l',' ','D','a','t','e','\0' };
static const BYTE fmtGeneralDate[0x0a] =
{
0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
0x0,0x0,0x0,0x0,0x0,
FMT_DATE_GENERAL,FMT_GEN_END
};
static const WCHAR szShortDate[] = { 'S','h','o','r','t',' ','D','a','t','e','\0' };
static const BYTE fmtShortDate[0x0a] =
{
0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
0x0,0x0,0x0,0x0,0x0,
FMT_DATE_SHORT,FMT_GEN_END
};
static const WCHAR szMediumDate[] = { 'M','e','d','i','u','m',' ','D','a','t','e','\0' };
static const BYTE fmtMediumDate[0x0a] =
{
0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
0x0,0x0,0x0,0x0,0x0,
FMT_DATE_MEDIUM,FMT_GEN_END
};
static const WCHAR szLongDate[] = { 'L','o','n','g',' ','D','a','t','e','\0' };
static const BYTE fmtLongDate[0x0a] =
{
0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
0x0,0x0,0x0,0x0,0x0,
FMT_DATE_LONG,FMT_GEN_END
};
static const WCHAR szShortTime[] = { 'S','h','o','r','t',' ','T','i','m','e','\0' };
static const BYTE fmtShortTime[0x0c] =
{
0x0c,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
0x0,0x0,0x0,0x0,0x0,
FMT_DATE_TIME_UNK2,FMT_DATE_TIME_SEP,FMT_DATE_MIN_0,FMT_GEN_END
};
static const WCHAR szMediumTime[] = { 'M','e','d','i','u','m',' ','T','i','m','e','\0' };
static const BYTE fmtMediumTime[0x11] =
{
0x11,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
0x0,0x0,0x0,0x0,0x0,
FMT_DATE_HOUR_12_0,FMT_DATE_TIME_SEP,FMT_DATE_MIN_0,
FMT_GEN_INLINE,0x01,' ','\0',FMT_DATE_AMPM_SYS1,FMT_GEN_END
};
static const WCHAR szLongTime[] = { 'L','o','n','g',' ','T','i','m','e','\0' };
static const BYTE fmtLongTime[0x0d] =
{
0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
0x0,0x0,0x0,0x0,0x0,
FMT_DATE_TIME_SYS,FMT_GEN_END
};
static const WCHAR szTrueFalse[] = { 'T','r','u','e','/','F','a','l','s','e','\0' };
static const BYTE fmtTrueFalse[0x0d] =
{
0x0d,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
FMT_FLAG_BOOL,0x0,0x0,0x0,0x0,
FMT_NUM_TRUE_FALSE,FMT_GEN_END
};
static const WCHAR szYesNo[] = { 'Y','e','s','/','N','o','\0' };
static const BYTE fmtYesNo[0x0d] =
{
0x0d,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
FMT_FLAG_BOOL,0x0,0x0,0x0,0x0,
FMT_NUM_YES_NO,FMT_GEN_END
};
static const WCHAR szOnOff[] = { 'O','n','/','O','f','f','\0' };
static const BYTE fmtOnOff[0x0d] =
{
0x0d,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
FMT_FLAG_BOOL,0x0,0x0,0x0,0x0,
FMT_NUM_ON_OFF,FMT_GEN_END
};
static const WCHAR szGeneralNumber[] = { 'G','e','n','e','r','a','l',' ','N','u','m','b','e','r','\0' };
static const BYTE fmtGeneralNumber[sizeof(FMT_HEADER)] =
{
sizeof(FMT_HEADER),FMT_TYPE_GENERAL,sizeof(FMT_HEADER),0x0,0x0,0x0
};
static const WCHAR szCurrency[] = { 'C','u','r','r','e','n','c','y','\0' };
static const BYTE fmtCurrency[0x26] =
{
0x26,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x12,0x0,0x0,
/* Positive numbers */
FMT_FLAG_THOUSANDS,0xcc,0x0,0x1,0x2,
FMT_NUM_CURRENCY,FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,
FMT_GEN_END,
/* Negative numbers */
FMT_FLAG_THOUSANDS,0xcc,0x0,0x1,0x2,
FMT_GEN_INLINE,0x1,'(','\0',FMT_NUM_CURRENCY,FMT_NUM_COPY_ZERO,0x1,
FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,FMT_GEN_INLINE,0x1,')','\0',
FMT_GEN_END
};
static const WCHAR szFixed[] = { 'F','i','x','e','d','\0' };
static const BYTE fmtFixed[0x11] =
{
0x11,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
0x0,0x0,0x0,0x1,0x2,
FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,FMT_GEN_END
};
static const WCHAR szStandard[] = { 'S','t','a','n','d','a','r','d','\0' };
static const BYTE fmtStandard[0x11] =
{
0x11,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
FMT_FLAG_THOUSANDS,0x0,0x0,0x1,0x2,
FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,FMT_GEN_END
};
static const WCHAR szPercent[] = { 'P','e','r','c','e','n','t','\0' };
static const BYTE fmtPercent[0x15] =
{
0x15,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
FMT_FLAG_PERCENT,0x1,0x0,0x1,0x2,
FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,
FMT_GEN_INLINE,0x1,'%','\0',FMT_GEN_END
};
static const WCHAR szScientific[] = { 'S','c','i','e','n','t','i','f','i','c','\0' };
static const BYTE fmtScientific[0x13] =
{
0x13,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
FMT_FLAG_EXPONENT,0x0,0x0,0x1,0x2,
FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,FMT_NUM_EXP_POS_U,0x2,FMT_GEN_END
};
typedef struct tagNAMED_FORMAT
{
LPCWSTR name;
const BYTE* format;
} NAMED_FORMAT;
/* Format name to tokenised format. Must be kept sorted by name */
static const NAMED_FORMAT VARIANT_NamedFormats[] =
{
{ szCurrency, fmtCurrency },
{ szFixed, fmtFixed },
{ szGeneralDate, fmtGeneralDate },
{ szGeneralNumber, fmtGeneralNumber },
{ szLongDate, fmtLongDate },
{ szLongTime, fmtLongTime },
{ szMediumDate, fmtMediumDate },
{ szMediumTime, fmtMediumTime },
{ szOnOff, fmtOnOff },
{ szPercent, fmtPercent },
{ szScientific, fmtScientific },
{ szShortDate, fmtShortDate },
{ szShortTime, fmtShortTime },
{ szStandard, fmtStandard },
{ szTrueFalse, fmtTrueFalse },
{ szYesNo, fmtYesNo }
};
typedef const NAMED_FORMAT *LPCNAMED_FORMAT;
static int FormatCompareFn(const void *l, const void *r)
{
return strcmpiW(((LPCNAMED_FORMAT)l)->name, ((LPCNAMED_FORMAT)r)->name);
}
static inline const BYTE *VARIANT_GetNamedFormat(LPCWSTR lpszFormat)
{
NAMED_FORMAT key;
LPCNAMED_FORMAT fmt;
key.name = lpszFormat;
fmt = (LPCNAMED_FORMAT)bsearch(&key, VARIANT_NamedFormats,
sizeof(VARIANT_NamedFormats)/sizeof(NAMED_FORMAT),
sizeof(NAMED_FORMAT), FormatCompareFn);
return fmt ? fmt->format : NULL;
}
/* Return an error if the token for the value will not fit in the destination */
#define NEED_SPACE(x) if (cbTok < (int)(x)) return TYPE_E_BUFFERTOOSMALL; cbTok -= (x)
/* Non-zero if the format is unknown or a given type */
#define COULD_BE(typ) ((!fmt_number && header->type==FMT_TYPE_UNKNOWN)||header->type==typ)
/* State during tokenising */
#define FMT_STATE_OPEN_COPY 0x1 /* Last token written was a copy */
#define FMT_STATE_WROTE_DECIMAL 0x2 /* Already wrote a decimal seperator */
#define FMT_STATE_SEEN_HOURS 0x4 /* See the hh specifier */
#define FMT_STATE_WROTE_MINUTES 0x8 /* Wrote minutes */
/**********************************************************************
* VarTokenizeFormatString [OLEAUT32.140]
*
* Convert a format string into tokenised form.
*
* PARAMS
* lpszFormat [I] Format string to tokenise
* rgbTok [O] Destination for tokenised format
* cbTok [I] Size of rgbTok in bytes
* nFirstDay [I] First day of the week (1-7, or 0 for current system default)
* nFirstWeek [I] How to treat the first week (see notes)
* lcid [I] Locale Id of the format string
* pcbActual [O] If non-NULL, filled with the first token generated
*
* RETURNS
* Success: S_OK. rgbTok contains the tokenised format.
* Failure: E_INVALIDARG, if any argument is invalid.
* TYPE_E_BUFFERTOOSMALL, if rgbTok is not large enough.
*
* NOTES
* Valid values for the nFirstWeek parameter are:
*| Value Meaning
*| ----- -------
*| 0 Use the current system default
*| 1 The first week is that containing Jan 1
*| 2 Four or more days of the first week are in the current year
*| 3 The first week is 7 days long
* See Variant-Formats(), VarFormatFromTokens().
*/
HRESULT WINAPI VarTokenizeFormatString(LPOLESTR lpszFormat, LPBYTE rgbTok,
int cbTok, int nFirstDay, int nFirstWeek,
LCID lcid, int *pcbActual)
{
/* Note: none of these strings should be NUL terminated */
static const WCHAR szTTTTT[] = { 't','t','t','t','t' };
static const WCHAR szAMPM[] = { 'A','M','P','M' };
static const WCHAR szampm[] = { 'a','m','p','m' };
static const WCHAR szAMSlashPM[] = { 'A','M','/','P','M' };
static const WCHAR szamSlashpm[] = { 'a','m','/','p','m' };
const BYTE *namedFmt;
FMT_HEADER *header = (FMT_HEADER*)rgbTok;
FMT_STRING_HEADER *str_header = (FMT_STRING_HEADER*)(rgbTok + sizeof(FMT_HEADER));
FMT_NUMBER_HEADER *num_header = (FMT_NUMBER_HEADER*)str_header;
FMT_DATE_HEADER *date_header = (FMT_DATE_HEADER*)str_header;
BYTE* pOut = rgbTok + sizeof(FMT_HEADER) + sizeof(FMT_STRING_HEADER);
BYTE* pLastHours = NULL;
BYTE fmt_number = 0;
DWORD fmt_state = 0;
LPCWSTR pFormat = lpszFormat;
TRACE("(%s,%p,%d,%d,%d,0x%08lx,%p)\n", debugstr_w(lpszFormat), rgbTok, cbTok,
nFirstDay, nFirstWeek, lcid, pcbActual);
if (!rgbTok ||
nFirstDay < 0 || nFirstDay > 7 || nFirstWeek < 0 || nFirstWeek > 3)
return E_INVALIDARG;
if (!lpszFormat || !*lpszFormat)
{
/* An empty string means 'general format' */
NEED_SPACE(sizeof(BYTE));
*rgbTok = FMT_TO_STRING;
if (pcbActual)
*pcbActual = FMT_TO_STRING;
return S_OK;
}
if (cbTok > 255)
cbTok = 255; /* Ensure we error instead of wrapping */
/* Named formats */
namedFmt = VARIANT_GetNamedFormat(lpszFormat);
if (namedFmt)
{
NEED_SPACE(namedFmt[0]);
memcpy(rgbTok, namedFmt, namedFmt[0]);
TRACE("Using pre-tokenised named format %s\n", debugstr_w(lpszFormat));
/* FIXME: pcbActual */
return S_OK;
}
/* Insert header */
NEED_SPACE(sizeof(FMT_HEADER) + sizeof(FMT_STRING_HEADER));
memset(header, 0, sizeof(FMT_HEADER));
memset(str_header, 0, sizeof(FMT_STRING_HEADER));
header->starts[fmt_number] = sizeof(FMT_HEADER);
while (*pFormat)
{
/* --------------
* General tokens
* --------------
*/
if (*pFormat == ';')
{
while (*pFormat == ';')
{
TRACE(";\n");
if (++fmt_number > 3)
return E_INVALIDARG; /* too many formats */
pFormat++;
}
if (*pFormat)
{
TRACE("New header\n");
NEED_SPACE(sizeof(BYTE) + sizeof(FMT_STRING_HEADER));
*pOut++ = FMT_GEN_END;
header->starts[fmt_number] = pOut - rgbTok;
str_header = (FMT_STRING_HEADER*)pOut;
num_header = (FMT_NUMBER_HEADER*)pOut;
date_header = (FMT_DATE_HEADER*)pOut;
memset(str_header, 0, sizeof(FMT_STRING_HEADER));
pOut += sizeof(FMT_STRING_HEADER);
fmt_state = 0;
pLastHours = NULL;
}
}
else if (*pFormat == '\\')
{
/* Escaped character */
if (pFormat[1])
{
NEED_SPACE(3 * sizeof(BYTE));
pFormat++;
*pOut++ = FMT_GEN_COPY;
*pOut++ = pFormat - lpszFormat;
*pOut++ = 0x1;
fmt_state |= FMT_STATE_OPEN_COPY;
TRACE("'\\'\n");
}
else
fmt_state &= ~FMT_STATE_OPEN_COPY;
pFormat++;
}
else if (*pFormat == '"')
{
/* Escaped string
* Note: Native encodes "" as a copy of length zero. Thats just dumb, so
* here we avoid encoding anything in this case.
*/
if (!pFormat[1])
pFormat++;
else if (pFormat[1] == '"')
{
pFormat += 2;
}
else
{
LPCWSTR start = ++pFormat;
while (*pFormat && *pFormat != '"')
pFormat++;
NEED_SPACE(3 * sizeof(BYTE));
*pOut++ = FMT_GEN_COPY;
*pOut++ = start - lpszFormat;
*pOut++ = pFormat - start;
if (*pFormat == '"')
pFormat++;
TRACE("Quoted string pos %d, len %d\n", pOut[-2], pOut[-1]);
}
fmt_state &= ~FMT_STATE_OPEN_COPY;
}
/* -------------
* Number tokens
* -------------
*/
else if (*pFormat == '0' && COULD_BE(FMT_TYPE_NUMBER))
{
/* Number formats: Digit from number or '0' if no digits
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_NUMBER;
NEED_SPACE(2 * sizeof(BYTE));
*pOut++ = FMT_NUM_COPY_ZERO;
*pOut = 0x0;
while (*pFormat == '0')
{
*pOut = *pOut + 1;
pFormat++;
}
if (fmt_state & FMT_STATE_WROTE_DECIMAL)
num_header->fractional += *pOut;
else
num_header->whole += *pOut;
TRACE("%d 0's\n", *pOut);
pOut++;
fmt_state &= ~FMT_STATE_OPEN_COPY;
}
else if (*pFormat == '#' && COULD_BE(FMT_TYPE_NUMBER))
{
/* Number formats: Digit from number or blank if no digits
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_NUMBER;
NEED_SPACE(2 * sizeof(BYTE));
*pOut++ = FMT_NUM_COPY_SKIP;
*pOut = 0x0;
while (*pFormat == '#')
{
*pOut = *pOut + 1;
pFormat++;
}
if (fmt_state & FMT_STATE_WROTE_DECIMAL)
num_header->fractional += *pOut;
else
num_header->whole += *pOut;
TRACE("%d #'s\n", *pOut);
pOut++;
fmt_state &= ~FMT_STATE_OPEN_COPY;
}
else if (*pFormat == '.' && COULD_BE(FMT_TYPE_NUMBER) &&
!(fmt_state & FMT_STATE_WROTE_DECIMAL))
{
/* Number formats: Decimal seperator when 1st seen, literal thereafter
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_NUMBER;
NEED_SPACE(sizeof(BYTE));
*pOut++ = FMT_NUM_DECIMAL;
fmt_state |= FMT_STATE_WROTE_DECIMAL;
fmt_state &= ~FMT_STATE_OPEN_COPY;
pFormat++;
TRACE("decimal sep\n");
}
/* FIXME: E+ E- e+ e- => Exponent */
/* FIXME: %% => Divide by 1000 */
else if (*pFormat == ',' && header->type == FMT_TYPE_NUMBER)
{
/* Number formats: Use the thousands seperator
* Other formats: Literal
*/
num_header->flags |= FMT_FLAG_THOUSANDS;
pFormat++;
fmt_state &= ~FMT_STATE_OPEN_COPY;
TRACE("thousands sep\n");
}
/* -----------
* Date tokens
* -----------
*/
else if (*pFormat == '/' && COULD_BE(FMT_TYPE_DATE))
{
/* Date formats: Date seperator
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_DATE;
NEED_SPACE(sizeof(BYTE));
*pOut++ = FMT_DATE_DATE_SEP;
pFormat++;
fmt_state &= ~FMT_STATE_OPEN_COPY;
TRACE("date sep\n");
}
else if (*pFormat == ':' && COULD_BE(FMT_TYPE_DATE))
{
/* Date formats: Time seperator
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_DATE;
NEED_SPACE(sizeof(BYTE));
*pOut++ = FMT_DATE_TIME_SEP;
pFormat++;
fmt_state &= ~FMT_STATE_OPEN_COPY;
TRACE("time sep\n");
}
else if ((*pFormat == 'a' || *pFormat == 'A') &&
!strncmpiW(pFormat, szAMPM, sizeof(szAMPM)/sizeof(WCHAR)))
{
/* Date formats: System AM/PM designation
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_DATE;
NEED_SPACE(sizeof(BYTE));
pFormat += sizeof(szAMPM)/sizeof(WCHAR);
if (!strncmpW(pFormat, szampm, sizeof(szampm)/sizeof(WCHAR)))
*pOut++ = FMT_DATE_AMPM_SYS2;
else
*pOut++ = FMT_DATE_AMPM_SYS1;
if (pLastHours)
*pLastHours = *pLastHours + 2;
TRACE("ampm\n");
}
else if (*pFormat == 'a' && pFormat[1] == '/' &&
(pFormat[2] == 'p' || pFormat[2] == 'P'))
{
/* Date formats: lowercase a or p designation
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_DATE;
NEED_SPACE(sizeof(BYTE));
pFormat += 3;
*pOut++ = FMT_DATE_A_LOWER;
if (pLastHours)
*pLastHours = *pLastHours + 2;
TRACE("a/p\n");
}
else if (*pFormat == 'A' && pFormat[1] == '/' &&
(pFormat[2] == 'p' || pFormat[2] == 'P'))
{
/* Date formats: Uppercase a or p designation
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_DATE;
NEED_SPACE(sizeof(BYTE));
pFormat += 3;
*pOut++ = FMT_DATE_A_UPPER;
if (pLastHours)
*pLastHours = *pLastHours + 2;
TRACE("A/P\n");
}
else if (*pFormat == 'a' &&
!strncmpW(pFormat, szamSlashpm, sizeof(szamSlashpm)/sizeof(WCHAR)))
{
/* Date formats: lowercase AM or PM designation
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_DATE;
NEED_SPACE(sizeof(BYTE));
pFormat += sizeof(szamSlashpm)/sizeof(WCHAR);
*pOut++ = FMT_DATE_AMPM_LOWER;
if (pLastHours)
*pLastHours = *pLastHours + 2;
TRACE("AM/PM\n");
}
else if (*pFormat == 'A' &&
!strncmpW(pFormat, szAMSlashPM, sizeof(szAMSlashPM)/sizeof(WCHAR)))
{
/* Date formats: Uppercase AM or PM designation
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_DATE;
NEED_SPACE(sizeof(BYTE));
pFormat += sizeof(szAMSlashPM)/sizeof(WCHAR);
*pOut++ = FMT_DATE_AMPM_UPPER;
TRACE("AM/PM\n");
}
else if (*pFormat == 'c' || *pFormat == 'C')
{
/* Date formats: General date format
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_DATE;
NEED_SPACE(sizeof(BYTE));
pFormat += sizeof(szAMSlashPM)/sizeof(WCHAR);
*pOut++ = FMT_DATE_GENERAL;
TRACE("gen date\n");
}
else if ((*pFormat == 'd' || *pFormat == 'D') && COULD_BE(FMT_TYPE_DATE))
{
/* Date formats: Day specifier
* Other formats: Literal
* Types the format if found
*/
int count = -1;
header->type = FMT_TYPE_DATE;
while ((*pFormat == 'd' || *pFormat == 'D') && count < 6)
{
pFormat++;
count++;
}
NEED_SPACE(sizeof(BYTE));
*pOut++ = FMT_DATE_DAY + count;
fmt_state &= ~FMT_STATE_OPEN_COPY;
/* When we find the days token, reset the seen hours state so that
* 'mm' is again written as month when encountered.
*/
fmt_state &= ~FMT_STATE_SEEN_HOURS;
TRACE("%d d's\n", count + 1);
}
else if ((*pFormat == 'h' || *pFormat == 'H') && COULD_BE(FMT_TYPE_DATE))
{
/* Date formats: Hour specifier
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_DATE;
NEED_SPACE(sizeof(BYTE));
pFormat++;
/* Record the position of the hours specifier - if we encounter
* an am/pm specifier we will change the hours from 24 to 12.
*/
pLastHours = pOut;
if (*pFormat == 'h' || *pFormat == 'H')
{
pFormat++;
*pOut++ = FMT_DATE_HOUR_0;
TRACE("hh\n");
}
else
{
*pOut++ = FMT_DATE_HOUR;
TRACE("h\n");
}
fmt_state &= ~FMT_STATE_OPEN_COPY;
/* Note that now we have seen an hours token, the next occurence of
* 'mm' indicates minutes, not months.
*/
fmt_state |= FMT_STATE_SEEN_HOURS;
}
else if ((*pFormat == 'm' || *pFormat == 'M') && COULD_BE(FMT_TYPE_DATE))
{
/* Date formats: Month specifier (or Minute specifier, after hour specifier)
* Other formats: Literal
* Types the format if found
*/
int count = -1;
header->type = FMT_TYPE_DATE;
while ((*pFormat == 'm' || *pFormat == 'M') && count < 4)
{
pFormat++;
count++;
}
NEED_SPACE(sizeof(BYTE));
if (count <= 1 && fmt_state & FMT_STATE_SEEN_HOURS &&
!(fmt_state & FMT_STATE_WROTE_MINUTES))
{
/* We have seen an hours specifier and not yet written a minutes
* specifier. Write this as minutes and thereafter as months.
*/
*pOut++ = count == 1 ? FMT_DATE_MIN_0 : FMT_DATE_MIN;
fmt_state |= FMT_STATE_WROTE_MINUTES; /* Hereafter write months */
}
else
*pOut++ = FMT_DATE_MON + count; /* Months */
fmt_state &= ~FMT_STATE_OPEN_COPY;
TRACE("%d m's\n", count + 1);
}
else if ((*pFormat == 'n' || *pFormat == 'N') && COULD_BE(FMT_TYPE_DATE))
{
/* Date formats: Minute specifier
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_DATE;
NEED_SPACE(sizeof(BYTE));
pFormat++;
if (*pFormat == 'n' || *pFormat == 'N')
{
pFormat++;
*pOut++ = FMT_DATE_MIN_0;
TRACE("nn\n");
}
else
{
*pOut++ = FMT_DATE_MIN;
TRACE("n\n");
}
fmt_state &= ~FMT_STATE_OPEN_COPY;
}
else if ((*pFormat == 'q' || *pFormat == 'q') && COULD_BE(FMT_TYPE_DATE))
{
/* Date formats: Quarter specifier
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_DATE;
NEED_SPACE(sizeof(BYTE));
*pOut++ = FMT_DATE_QUARTER;
pFormat++;
fmt_state &= ~FMT_STATE_OPEN_COPY;
TRACE("quarter\n");
}
else if ((*pFormat == 's' || *pFormat == 'S') && COULD_BE(FMT_TYPE_DATE))
{
/* Date formats: Second specifier
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_DATE;
NEED_SPACE(sizeof(BYTE));
pFormat++;
if (*pFormat == 's' || *pFormat == 'S')
{
pFormat++;
*pOut++ = FMT_DATE_SEC_0;
TRACE("ss\n");
}
else
{
*pOut++ = FMT_DATE_SEC;
TRACE("s\n");
}
fmt_state &= ~FMT_STATE_OPEN_COPY;
}
else if ((*pFormat == 't' || *pFormat == 'T') &&
!strncmpiW(pFormat, szTTTTT, sizeof(szTTTTT)/sizeof(WCHAR)))
{
/* Date formats: System time specifier
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_DATE;
pFormat += sizeof(szTTTTT)/sizeof(WCHAR);
NEED_SPACE(sizeof(BYTE));
*pOut++ = FMT_DATE_TIME_SYS;
fmt_state &= ~FMT_STATE_OPEN_COPY;
}
else if ((*pFormat == 'w' || *pFormat == 'W') && COULD_BE(FMT_TYPE_DATE))
{
/* Date formats: Week of the year/Day of the week
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_DATE;
pFormat++;
if (*pFormat == 'w' || *pFormat == 'W')
{
NEED_SPACE(3 * sizeof(BYTE));
pFormat++;
*pOut++ = FMT_DATE_WEEK_YEAR;
*pOut++ = nFirstDay;
*pOut++ = nFirstWeek;
TRACE("ww\n");
}
else
{
NEED_SPACE(2 * sizeof(BYTE));
*pOut++ = FMT_DATE_DAY_WEEK;
*pOut++ = nFirstDay;
TRACE("w\n");
}
fmt_state &= ~FMT_STATE_OPEN_COPY;
}
else if ((*pFormat == 'y' || *pFormat == 'Y') && COULD_BE(FMT_TYPE_DATE))
{
/* Date formats: Day of year/Year specifier
* Other formats: Literal
* Types the format if found
*/
int count = -1;
header->type = FMT_TYPE_DATE;
while ((*pFormat == 'y' || *pFormat == 'Y') && count < 4)
{
pFormat++;
count++;
}
if (count == 2)
{
count--; /* 'yyy' has no meaning, despite what MSDN says */
pFormat--;
}
NEED_SPACE(sizeof(BYTE));
*pOut++ = FMT_DATE_YEAR_DOY + count;
fmt_state &= ~FMT_STATE_OPEN_COPY;
TRACE("%d y's\n", count + 1);
}
/* -------------
* String tokens
* -------------
*/
else if (*pFormat == '@' && COULD_BE(FMT_TYPE_STRING))
{
/* String formats: Character from string or space if no char
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_STRING;
NEED_SPACE(2 * sizeof(BYTE));
*pOut++ = FMT_STR_COPY_SPACE;
*pOut = 0x0;
while (*pFormat == '@')
{
*pOut = *pOut + 1;
str_header->copy_chars++;
pFormat++;
}
TRACE("%d @'s\n", *pOut);
pOut++;
fmt_state &= ~FMT_STATE_OPEN_COPY;
}
else if (*pFormat == '&' && COULD_BE(FMT_TYPE_STRING))
{
/* String formats: Character from string or skip if no char
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_STRING;
NEED_SPACE(2 * sizeof(BYTE));
*pOut++ = FMT_STR_COPY_SKIP;
*pOut = 0x0;
while (*pFormat == '&')
{
*pOut = *pOut + 1;
str_header->copy_chars++;
pFormat++;
}
TRACE("%d &'s\n", *pOut);
pOut++;
fmt_state &= ~FMT_STATE_OPEN_COPY;
}
else if ((*pFormat == '<' || *pFormat == '>') && COULD_BE(FMT_TYPE_STRING))
{
/* String formats: Use upper/lower case
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_STRING;
if (*pFormat == '<')
str_header->flags |= FMT_FLAG_LT;
else
str_header->flags |= FMT_FLAG_GT;
TRACE("to %s case\n", *pFormat == '<' ? "lower" : "upper");
pFormat++;
fmt_state &= ~FMT_STATE_OPEN_COPY;
}
else if (*pFormat == '!' && COULD_BE(FMT_TYPE_STRING))
{
/* String formats: Copy right to left
* Other formats: Literal
* Types the format if found
*/
header->type = FMT_TYPE_STRING;
str_header->flags |= FMT_FLAG_RTL;
pFormat++;
fmt_state &= ~FMT_STATE_OPEN_COPY;
TRACE("copy right-to-left\n");
}
/* --------
* Literals
* --------
*/
/* FIXME: [ seems to be ignored */
else
{
if (*pFormat == '%' && header->type == FMT_TYPE_NUMBER)
{
/* Number formats: Percentage indicator, also a literal
* Other formats: Literal
* Doesn't type the format
*/
num_header->flags |= FMT_FLAG_PERCENT;
}
if (fmt_state & FMT_STATE_OPEN_COPY)
{
pOut[-1] = pOut[-1] + 1; /* Increase the length of the open copy */
TRACE("extend copy (char '%c'), length now %d\n", *pFormat, pOut[-1]);
}
else
{
/* Create a new open copy */
TRACE("New copy (char '%c')\n", *pFormat);
NEED_SPACE(3 * sizeof(BYTE));
*pOut++ = FMT_GEN_COPY;
*pOut++ = pFormat - lpszFormat;
*pOut++ = 0x1;
fmt_state |= FMT_STATE_OPEN_COPY;
}
pFormat++;
}
}
*pOut++ = FMT_GEN_END;
header->size = pOut - rgbTok;
if (pcbActual)
*pcbActual = header->size;
return S_OK;
}
/* Number formatting state flags */
#define NUM_WROTE_DEC 0x01 /* Written the decimal seperator */
/* Format a variant using a number format */
static HRESULT VARIANT_FormatNumber(LPVARIANT pVarIn, LPOLESTR lpszFormat,
LPBYTE rgbTok, ULONG dwFlags,
BSTR *pbstrOut, LCID lcid)
{
BYTE rgbDig[256];
NUMPARSE np;
int wholeNumberDigits, fractionalDigits, divisor10 = 0, multiplier10 = 0;
WCHAR buff[256], *pBuff = buff;
VARIANT vString, vBool;
DWORD dwState = 0;
FMT_HEADER *header = (FMT_HEADER*)rgbTok;
FMT_NUMBER_HEADER *numHeader;
const BYTE* pToken = NULL;
HRESULT hRes = S_OK;
TRACE("(%p->(%s%s),%s,%p,0x%08lx,%p,0x%08lx)\n", pVarIn, debugstr_VT(pVarIn),
debugstr_VF(pVarIn), debugstr_w(lpszFormat), rgbTok, dwFlags, pbstrOut,
lcid);
V_VT(&vString) = VT_EMPTY;
V_VT(&vBool) = VT_BOOL;
if (V_TYPE(pVarIn) == VT_EMPTY || V_TYPE(pVarIn) == VT_NULL)
{
wholeNumberDigits = fractionalDigits = 0;
numHeader = (FMT_NUMBER_HEADER*)(rgbTok + FmtGetNull(header));
V_BOOL(&vBool) = VARIANT_FALSE;
}
else
{
/* Get a number string from pVarIn, and parse it */
hRes = VariantChangeTypeEx(&vString, pVarIn, LCID_US, VARIANT_NOUSEROVERRIDE, VT_BSTR);
if (FAILED(hRes))
return hRes;
np.cDig = sizeof(rgbDig);
np.dwInFlags = NUMPRS_STD;
hRes = VarParseNumFromStr(V_BSTR(&vString), LCID_US, 0, &np, rgbDig);
if (FAILED(hRes))
return hRes;
if (np.nPwr10 < 0)
{
if (-np.nPwr10 >= np.cDig)
{
/* A real number < +/- 1.0 e.g. 0.1024 or 0.01024 */
wholeNumberDigits = 0;
fractionalDigits = np.cDig;
divisor10 = -np.nPwr10;
}
else
{
/* An exactly represented real number e.g. 1.024 */
wholeNumberDigits = np.cDig + np.nPwr10;
fractionalDigits = np.cDig - wholeNumberDigits;
divisor10 = np.cDig - wholeNumberDigits;
}
}
else if (np.nPwr10 == 0)
{
/* An exactly represented whole number e.g. 1024 */
wholeNumberDigits = np.cDig;
fractionalDigits = 0;
}
else /* np.nPwr10 > 0 */
{
/* A whole number followed by nPwr10 0's e.g. 102400 */
wholeNumberDigits = np.cDig;
fractionalDigits = 0;
multiplier10 = np.nPwr10;
}
/* Figure out which format to use */
if (np.dwOutFlags & NUMPRS_NEG)
{
numHeader = (FMT_NUMBER_HEADER*)(rgbTok + FmtGetNegative(header));
V_BOOL(&vBool) = VARIANT_TRUE;
}
else if (wholeNumberDigits == 1 && !fractionalDigits && !multiplier10 &&
!divisor10 && rgbDig[0] == 0)
{
numHeader = (FMT_NUMBER_HEADER*)(rgbTok + FmtGetZero(header));
V_BOOL(&vBool) = VARIANT_FALSE;
}
else
{
numHeader = (FMT_NUMBER_HEADER*)(rgbTok + FmtGetPositive(header));
V_BOOL(&vBool) = VARIANT_TRUE;
}
TRACE("num header: flags = 0x%x, mult=%d, div=%d, whole=%d, fract=%d\n",
numHeader->flags, numHeader->multiplier, numHeader->divisor,
numHeader->whole, numHeader->fractional);
if (numHeader->flags & FMT_FLAG_PERCENT &&
!(wholeNumberDigits == 1 && !fractionalDigits && !multiplier10 &&
!divisor10 && rgbDig[0] == 0))
{
/* *100 for %'s. Try to 'steal' fractional digits if we can */
TRACE("Fraction - multiply by 100\n");
if (!fractionalDigits)
multiplier10 += 2;
else
{
fractionalDigits--;
wholeNumberDigits++;
if (!fractionalDigits)
multiplier10++;
else
{
fractionalDigits--;
wholeNumberDigits++;
}
}
}
TRACE("cDig %d; nPwr10 %d, whole %d, frac %d ", np.cDig,
np.nPwr10, wholeNumberDigits, fractionalDigits);
TRACE("mult %d; div %d\n", multiplier10, divisor10);
}
pToken = (const BYTE*)numHeader + sizeof(FMT_NUMBER_HEADER);
while (SUCCEEDED(hRes) && *pToken != FMT_GEN_END)
{
WCHAR defaultChar = '?';
DWORD boolFlag, localeValue = 0;
if (pToken - rgbTok > header->size)
{
ERR("Ran off the end of the format!\n");
hRes = E_INVALIDARG;
goto VARIANT_FormatNumber_Exit;
}
switch (*pToken)
{
case FMT_GEN_COPY:
TRACE("copy %s\n", debugstr_wn(lpszFormat + pToken[1], pToken[2]));
memcpy(pBuff, lpszFormat + pToken[1], pToken[2] * sizeof(WCHAR));
pBuff += pToken[2];
pToken += 2;
break;
case FMT_GEN_INLINE:
pToken += 2;
TRACE("copy %s\n", debugstr_a(pToken));
while (*pToken)
*pBuff++ = *pToken++;
break;
case FMT_NUM_YES_NO:
boolFlag = VAR_BOOLYESNO;
goto VARIANT_FormatNumber_Bool;
break;
case FMT_NUM_ON_OFF:
boolFlag = VAR_BOOLONOFF;
goto VARIANT_FormatNumber_Bool;
break;
case FMT_NUM_TRUE_FALSE:
boolFlag = VAR_LOCALBOOL;
VARIANT_FormatNumber_Bool:
{
BSTR boolStr = NULL;
if (pToken[1] != FMT_GEN_END)
{
ERR("Boolean token not at end of format!\n");
hRes = E_INVALIDARG;
goto VARIANT_FormatNumber_Exit;
}
hRes = VarBstrFromBool(V_BOOL(&vBool), lcid, boolFlag, &boolStr);
if (SUCCEEDED(hRes))
{
strcpyW(pBuff, boolStr);
SysFreeString(boolStr);
while (*pBuff)
pBuff++;
}
}
break;
case FMT_NUM_DECIMAL:
TRACE("write decimal seperator\n");
localeValue = LOCALE_SDECIMAL;
defaultChar = '.';
dwState |= NUM_WROTE_DEC;
break;
case FMT_NUM_CURRENCY:
TRACE("write currency symbol\n");
localeValue = LOCALE_SCURRENCY;
defaultChar = '$';
break;
case FMT_NUM_EXP_POS_U:
case FMT_NUM_EXP_POS_L:
case FMT_NUM_EXP_NEG_U:
case FMT_NUM_EXP_NEG_L:
if (*pToken == FMT_NUM_EXP_POS_L || *pToken == FMT_NUM_EXP_NEG_L)
*pBuff++ = 'e';
else
*pBuff++ = 'E';
if (divisor10)
{
*pBuff++ = '-';
sprintfW(pBuff, szPercentZeroStar_d, pToken[1], divisor10);
}
else
{
if (*pToken == FMT_NUM_EXP_POS_L || *pToken == FMT_NUM_EXP_POS_U)
*pBuff++ = '+';
sprintfW(pBuff, szPercentZeroStar_d, pToken[1], multiplier10);
}
while (*pBuff)
pBuff++;
pToken++;
break;
case FMT_NUM_COPY_SKIP:
if (dwState & NUM_WROTE_DEC)
{
int count;
TRACE("write %d fractional digits or skip\n", pToken[1]);
for (count = 0; count < fractionalDigits; count++)
pBuff[count] = rgbDig[wholeNumberDigits + count];
pBuff += fractionalDigits;
}
else
{
int count;
TRACE("write %d digits or skip\n", pToken[1]);
if (wholeNumberDigits > 1 || rgbDig[0] > 0)
{
TRACE("write %d whole number digits\n", wholeNumberDigits);
for (count = 0; count < wholeNumberDigits; count++)
*pBuff++ = '0' + rgbDig[count];
TRACE("write %d whole trailing 0's\n", multiplier10);
for (count = 0; count < multiplier10; count++)
*pBuff++ = '0'; /* Write trailing zeros for multiplied values */
}
}
pToken++;
break;
case FMT_NUM_COPY_ZERO:
if (dwState & NUM_WROTE_DEC)
{
int count;
TRACE("write %d fractional digits or 0's\n", pToken[1]);
for (count = 0; count < fractionalDigits; count++)
pBuff[count] = rgbDig[wholeNumberDigits + count];
pBuff += fractionalDigits;
if (pToken[1] > fractionalDigits)
{
count = pToken[1] - fractionalDigits;
while (count--)
*pBuff++ = '0'; /* Write trailing zeros for missing digits */
}
}
else
{
int count;
TRACE("write %d digits or 0's\n", pToken[1]);
if (pToken[1] > (wholeNumberDigits + multiplier10))
{
count = pToken[1] - (wholeNumberDigits + multiplier10);
TRACE("write %d leading zeros\n", count);
while(count--)
*pBuff++ = '0'; /* Write leading zeros for missing digits */
}
TRACE("write %d whole number digits\n", wholeNumberDigits);
for (count = 0; count < wholeNumberDigits; count++)
*pBuff++ = '0' + rgbDig[count];
TRACE("write %d whole trailing 0's\n", multiplier10);
for (count = 0; count < multiplier10; count++)
*pBuff++ = '0'; /* Write trailing zeros for multiplied values */
}
pToken++;
break;
default:
ERR("Unknown token 0x%02x!\n", *pToken);
hRes = E_INVALIDARG;
goto VARIANT_FormatNumber_Exit;
break;
}
if (localeValue)
{
if (GetLocaleInfoW(lcid, localeValue, pBuff,
sizeof(buff)/sizeof(WCHAR)-(pBuff-buff)))
{
TRACE("added %s\n", debugstr_w(pBuff));
while (*pBuff)
pBuff++;
}
else
{
TRACE("added %d '%c'\n", defaultChar, defaultChar);
*pBuff++ = defaultChar;
}
}
pToken++;
}
VARIANT_FormatNumber_Exit:
VariantClear(&vString);
*pBuff = '\0';
TRACE("buff is %s\n", debugstr_w(buff));
if (SUCCEEDED(hRes))
{
*pbstrOut = SysAllocString(buff);
if (!*pbstrOut)
hRes = E_OUTOFMEMORY;
}
return hRes;
}
/* Format a variant using a date format */
static HRESULT VARIANT_FormatDate(LPVARIANT pVarIn, LPOLESTR lpszFormat,
LPBYTE rgbTok, ULONG dwFlags,
BSTR *pbstrOut, LCID lcid)
{
WCHAR buff[256], *pBuff = buff;
VARIANT vDate;
UDATE udate;
FMT_HEADER *header = (FMT_HEADER*)rgbTok;
FMT_DATE_HEADER *dateHeader;
const BYTE* pToken = NULL;
HRESULT hRes;
TRACE("(%p->(%s%s),%s,%p,0x%08lx,%p,0x%08lx)\n", pVarIn, debugstr_VT(pVarIn),
debugstr_VF(pVarIn), debugstr_w(lpszFormat), rgbTok, dwFlags, pbstrOut,
lcid);
V_VT(&vDate) = VT_EMPTY;
if (V_TYPE(pVarIn) == VT_EMPTY || V_TYPE(pVarIn) == VT_NULL)
{
dateHeader = (FMT_DATE_HEADER*)(rgbTok + FmtGetNegative(header));
V_DATE(&vDate) = 0;
}
else
{
USHORT usFlags = dwFlags & VARIANT_CALENDAR_HIJRI ? VAR_CALENDAR_HIJRI : 0;
hRes = VariantChangeTypeEx(&vDate, pVarIn, LCID_US, usFlags, VT_DATE);
if (FAILED(hRes))
return hRes;
dateHeader = (FMT_DATE_HEADER*)(rgbTok + FmtGetPositive(header));
}
hRes = VarUdateFromDate(V_DATE(&vDate), 0 /* FIXME: flags? */, &udate);
if (FAILED(hRes))
return hRes;
pToken = (const BYTE*)dateHeader + sizeof(FMT_DATE_HEADER);
while (*pToken != FMT_GEN_END)
{
DWORD dwVal = 0, localeValue = 0, dwFmt = 0;
LPCWSTR szPrintFmt = NULL;
WCHAR defaultChar = '?';
if (pToken - rgbTok > header->size)
{
ERR("Ran off the end of the format!\n");
hRes = E_INVALIDARG;
goto VARIANT_FormatDate_Exit;
}
switch (*pToken)
{
case FMT_GEN_COPY:
TRACE("copy %s\n", debugstr_wn(lpszFormat + pToken[1], pToken[2]));
memcpy(pBuff, lpszFormat + pToken[1], pToken[2] * sizeof(WCHAR));
pBuff += pToken[2];
pToken += 2;
break;
case FMT_DATE_TIME_SEP:
TRACE("time seperator\n");
localeValue = LOCALE_STIME;
defaultChar = ':';
break;
case FMT_DATE_DATE_SEP:
TRACE("date seperator\n");
localeValue = LOCALE_SDATE;
defaultChar = '/';
break;
case FMT_DATE_GENERAL:
{
BSTR date = NULL;
WCHAR *pDate = date;
hRes = VarBstrFromDate(V_DATE(&vDate), lcid, 0, pbstrOut);
if (FAILED(hRes))
goto VARIANT_FormatDate_Exit;
while (*pDate)
*pBuff++ = *pDate++;
SysFreeString(date);
}
break;
case FMT_DATE_QUARTER:
if (udate.st.wMonth <= 3)
*pBuff++ = '1';
else if (udate.st.wMonth <= 6)
*pBuff++ = '2';
else if (udate.st.wMonth <= 9)
*pBuff++ = '3';
else
*pBuff++ = '4';
break;
case FMT_DATE_TIME_SYS:
{
/* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
BSTR date = NULL;
WCHAR *pDate = date;
hRes = VarBstrFromDate(V_DATE(&vDate), lcid, VAR_TIMEVALUEONLY, pbstrOut);
if (FAILED(hRes))
goto VARIANT_FormatDate_Exit;
while (*pDate)
*pBuff++ = *pDate++;
SysFreeString(date);
}
break;
case FMT_DATE_DAY:
szPrintFmt = szPercent_d;
dwVal = udate.st.wDay;
break;
case FMT_DATE_DAY_0:
szPrintFmt = szPercentZeroTwo_d;
dwVal = udate.st.wDay;
break;
case FMT_DATE_DAY_SHORT:
/* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
TRACE("short day\n");
localeValue = LOCALE_SABBREVDAYNAME1 + udate.st.wMonth - 1;
defaultChar = '?';
break;
case FMT_DATE_DAY_LONG:
/* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
TRACE("long day\n");
localeValue = LOCALE_SDAYNAME1 + udate.st.wMonth - 1;
defaultChar = '?';
break;
case FMT_DATE_SHORT:
/* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
dwFmt = LOCALE_SSHORTDATE;
break;
case FMT_DATE_LONG:
/* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
dwFmt = LOCALE_SLONGDATE;
break;
case FMT_DATE_MEDIUM:
FIXME("Medium date treated as long date\n");
dwFmt = LOCALE_SLONGDATE;
break;
case FMT_DATE_DAY_WEEK:
szPrintFmt = szPercent_d;
if (pToken[1])
dwVal = udate.st.wDayOfWeek + 2 - pToken[1];
else
{
GetLocaleInfoW(lcid,LOCALE_RETURN_NUMBER|LOCALE_IFIRSTDAYOFWEEK,
(LPWSTR)&dwVal, sizeof(dwVal)/sizeof(WCHAR));
dwVal = udate.st.wDayOfWeek + 1 - dwVal;
}
pToken++;
break;
case FMT_DATE_WEEK_YEAR:
szPrintFmt = szPercent_d;
dwVal = udate.wDayOfYear / 7 + 1;
pToken += 2;
FIXME("Ignoring nFirstDay of %d, nFirstWeek of %d\n", pToken[0], pToken[1]);
break;
case FMT_DATE_MON:
szPrintFmt = szPercent_d;
dwVal = udate.st.wMonth;
break;
case FMT_DATE_MON_0:
szPrintFmt = szPercentZeroTwo_d;
dwVal = udate.st.wMonth;
break;
case FMT_DATE_MON_SHORT:
/* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
TRACE("short month\n");
localeValue = LOCALE_SABBREVMONTHNAME1 + udate.st.wMonth - 1;
defaultChar = '?';
break;
case FMT_DATE_MON_LONG:
/* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
TRACE("long month\n");
localeValue = LOCALE_SMONTHNAME1 + udate.st.wMonth - 1;
defaultChar = '?';
break;
case FMT_DATE_YEAR_DOY:
szPrintFmt = szPercent_d;
dwVal = udate.wDayOfYear;
break;
case FMT_DATE_YEAR_0:
szPrintFmt = szPercentZeroTwo_d;
dwVal = udate.st.wYear % 100;
break;
case FMT_DATE_YEAR_LONG:
szPrintFmt = szPercent_d;
dwVal = udate.st.wYear;
break;
case FMT_DATE_MIN:
szPrintFmt = szPercent_d;
dwVal = udate.st.wMinute;
break;
case FMT_DATE_MIN_0:
szPrintFmt = szPercentZeroTwo_d;
dwVal = udate.st.wMinute;
break;
case FMT_DATE_SEC:
szPrintFmt = szPercent_d;
dwVal = udate.st.wSecond;
break;
case FMT_DATE_SEC_0:
szPrintFmt = szPercentZeroTwo_d;
dwVal = udate.st.wSecond;
break;
case FMT_DATE_HOUR:
szPrintFmt = szPercent_d;
dwVal = udate.st.wHour;
break;
case FMT_DATE_HOUR_0:
szPrintFmt = szPercentZeroTwo_d;
dwVal = udate.st.wHour;
break;
case FMT_DATE_HOUR_12:
szPrintFmt = szPercent_d;
dwVal = udate.st.wHour ? udate.st.wHour > 12 ? udate.st.wHour - 12 : udate.st.wHour : 12;
break;
case FMT_DATE_HOUR_12_0:
szPrintFmt = szPercentZeroTwo_d;
dwVal = udate.st.wHour ? udate.st.wHour > 12 ? udate.st.wHour - 12 : udate.st.wHour : 12;
break;
case FMT_DATE_AMPM_SYS1:
case FMT_DATE_AMPM_SYS2:
localeValue = udate.st.wHour < 12 ? LOCALE_S1159 : LOCALE_S2359;
defaultChar = '?';
break;
case FMT_DATE_AMPM_UPPER:
*pBuff++ = udate.st.wHour < 12 ? 'A' : 'P';
*pBuff++ = 'M';
break;
case FMT_DATE_A_UPPER:
*pBuff++ = udate.st.wHour < 12 ? 'A' : 'P';
break;
case FMT_DATE_AMPM_LOWER:
*pBuff++ = udate.st.wHour < 12 ? 'a' : 'p';
*pBuff++ = 'm';
break;
case FMT_DATE_A_LOWER:
*pBuff++ = udate.st.wHour < 12 ? 'a' : 'p';
break;
default:
ERR("Unknown token 0x%02x!\n", *pToken);
hRes = E_INVALIDARG;
goto VARIANT_FormatDate_Exit;
break;
}
if (localeValue)
{
*pBuff = '\0';
if (GetLocaleInfoW(lcid, localeValue, pBuff,
sizeof(buff)/sizeof(WCHAR)-(pBuff-buff)))
{
TRACE("added %s\n", debugstr_w(pBuff));
while (*pBuff)
pBuff++;
}
else
{
TRACE("added %d %c\n", defaultChar, defaultChar);
*pBuff++ = defaultChar;
}
}
else if (dwFmt)
{
WCHAR fmt_buff[80];
if (!GetLocaleInfoW(lcid, dwFmt, fmt_buff, sizeof(fmt_buff)/sizeof(WCHAR)) ||
!GetDateFormatW(lcid, 0, &udate.st, fmt_buff, pBuff,
sizeof(buff)/sizeof(WCHAR)-(pBuff-buff)))
{
hRes = E_INVALIDARG;
goto VARIANT_FormatDate_Exit;
}
while (*pBuff)
pBuff++;
}
else if (szPrintFmt)
{
sprintfW(pBuff, szPrintFmt, dwVal);
while (*pBuff)
pBuff++;
}
pToken++;
}
VARIANT_FormatDate_Exit:
*pBuff = '\0';
TRACE("buff is %s\n", debugstr_w(buff));
if (SUCCEEDED(hRes))
{
*pbstrOut = SysAllocString(buff);
if (!*pbstrOut)
hRes = E_OUTOFMEMORY;
}
return hRes;
}
/* Format a variant using a string format */
static HRESULT VARIANT_FormatString(LPVARIANT pVarIn, LPOLESTR lpszFormat,
LPBYTE rgbTok, ULONG dwFlags,
BSTR *pbstrOut, LCID lcid)
{
static const WCHAR szEmpty[] = { '\0' };
WCHAR buff[256], *pBuff = buff;
WCHAR *pSrc;
FMT_HEADER *header = (FMT_HEADER*)rgbTok;
FMT_STRING_HEADER *strHeader;
const BYTE* pToken = NULL;
VARIANT vStr;
int blanks_first;
BOOL bUpper = FALSE;
HRESULT hRes = S_OK;
TRACE("(%p->(%s%s),%s,%p,0x%08lx,%p,0x%08lx)\n", pVarIn, debugstr_VT(pVarIn),
debugstr_VF(pVarIn), debugstr_w(lpszFormat), rgbTok, dwFlags, pbstrOut,
lcid);
V_VT(&vStr) = VT_EMPTY;
if (V_TYPE(pVarIn) == VT_EMPTY || V_TYPE(pVarIn) == VT_NULL)
{
strHeader = (FMT_STRING_HEADER*)(rgbTok + FmtGetNegative(header));
V_BSTR(&vStr) = (WCHAR*)szEmpty;
}
else
{
hRes = VariantChangeTypeEx(&vStr, pVarIn, LCID_US, VARIANT_NOUSEROVERRIDE, VT_BSTR);
if (FAILED(hRes))
return hRes;
if (V_BSTR(pVarIn)[0] == '\0')
strHeader = (FMT_STRING_HEADER*)(rgbTok + FmtGetNegative(header));
else
strHeader = (FMT_STRING_HEADER*)(rgbTok + FmtGetPositive(header));
}
pSrc = V_BSTR(&vStr);
if ((strHeader->flags & (FMT_FLAG_LT|FMT_FLAG_GT)) == FMT_FLAG_GT)
bUpper = TRUE;
blanks_first = strHeader->copy_chars - strlenW(pSrc);
pToken = (const BYTE*)strHeader + sizeof(FMT_DATE_HEADER);
while (*pToken != FMT_GEN_END)
{
int dwCount = 0;
if (pToken - rgbTok > header->size)
{
ERR("Ran off the end of the format!\n");
hRes = E_INVALIDARG;
goto VARIANT_FormatString_Exit;
}
switch (*pToken)
{
case FMT_GEN_COPY:
TRACE("copy %s\n", debugstr_wn(lpszFormat + pToken[1], pToken[2]));
memcpy(pBuff, lpszFormat + pToken[1], pToken[2] * sizeof(WCHAR));
pBuff += pToken[2];
pToken += 2;
break;
case FMT_STR_COPY_SPACE:
case FMT_STR_COPY_SKIP:
dwCount = pToken[1];
if (*pToken == FMT_STR_COPY_SPACE && blanks_first > 0)
{
TRACE("insert %d initial spaces\n", blanks_first);
while (dwCount > 0 && blanks_first > 0)
{
*pBuff++ = ' ';
dwCount--;
blanks_first--;
}
}
TRACE("copy %d chars%s\n", dwCount,
*pToken == FMT_STR_COPY_SPACE ? " with space" :"");
while (dwCount > 0 && *pSrc)
{
if (bUpper)
*pBuff++ = toupperW(*pSrc);
else
*pBuff++ = tolowerW(*pSrc);
dwCount--;
pSrc++;
}
if (*pToken == FMT_STR_COPY_SPACE && dwCount > 0)
{
TRACE("insert %d spaces\n", dwCount);
while (dwCount-- > 0)
*pBuff++ = ' ';
}
pToken++;
break;
default:
ERR("Unknown token 0x%02x!\n", *pToken);
hRes = E_INVALIDARG;
goto VARIANT_FormatString_Exit;
break;
}
pToken++;
}
VARIANT_FormatString_Exit:
/* Copy out any remaining chars */
while (*pSrc)
{
if (bUpper)
*pBuff++ = toupperW(*pSrc);
else
*pBuff++ = tolowerW(*pSrc);
pSrc++;
}
VariantClear(&vStr);
*pBuff = '\0';
TRACE("buff is %s\n", debugstr_w(buff));
if (SUCCEEDED(hRes))
{
*pbstrOut = SysAllocString(buff);
if (!*pbstrOut)
hRes = E_OUTOFMEMORY;
}
return hRes;
}
#define NUMBER_VTBITS (VTBIT_I1|VTBIT_UI1|VTBIT_I2|VTBIT_UI2| \
VTBIT_I4|VTBIT_UI4|VTBIT_I8|VTBIT_UI8| \
VTBIT_R4|VTBIT_R8|VTBIT_CY|VTBIT_DECIMAL| \
(1<<VT_BOOL)|(1<<VT_INT)|(1<<VT_UINT))
/**********************************************************************
* VarFormatFromTokens [OLEAUT32.139]
*/
HRESULT WINAPI VarFormatFromTokens(LPVARIANT pVarIn, LPOLESTR lpszFormat,
LPBYTE rgbTok, ULONG dwFlags,
BSTR *pbstrOut, LCID lcid)
{
FMT_SHORT_HEADER *header = (FMT_SHORT_HEADER *)rgbTok;
VARIANT vTmp;
HRESULT hres;
TRACE("(%p,%s,%p,%lx,%p,0x%08lx)\n", pVarIn, debugstr_w(lpszFormat),
rgbTok, dwFlags, pbstrOut, lcid);
if (!pbstrOut)
return E_INVALIDARG;
*pbstrOut = NULL;
if (!pVarIn || !rgbTok)
return E_INVALIDARG;
if (*rgbTok == FMT_TO_STRING || header->type == FMT_TYPE_GENERAL)
{
/* According to MSDN, general format acts somewhat like the 'Str'
* function in Visual Basic.
*/
VarFormatFromTokens_AsStr:
V_VT(&vTmp) = VT_EMPTY;
hres = VariantChangeTypeEx(&vTmp, pVarIn, lcid, dwFlags, VT_BSTR);
*pbstrOut = V_BSTR(&vTmp);
}
else
{
if (header->type == FMT_TYPE_NUMBER ||
(header->type == FMT_TYPE_UNKNOWN && ((1 << V_TYPE(pVarIn)) & NUMBER_VTBITS)))
{
hres = VARIANT_FormatNumber(pVarIn, lpszFormat, rgbTok, dwFlags, pbstrOut, lcid);
}
else if (header->type == FMT_TYPE_DATE ||
(header->type == FMT_TYPE_UNKNOWN && V_TYPE(pVarIn) == VT_DATE))
{
hres = VARIANT_FormatDate(pVarIn, lpszFormat, rgbTok, dwFlags, pbstrOut, lcid);
}
else if (header->type == FMT_TYPE_STRING || V_TYPE(pVarIn) == VT_BSTR)
{
hres = VARIANT_FormatString(pVarIn, lpszFormat, rgbTok, dwFlags, pbstrOut, lcid);
}
else
{
ERR("unrecognised format type 0x%02x\n", header->type);
return E_INVALIDARG;
}
/* If the coercion failed, still try to create output, unless the
* VAR_FORMAT_NOSUBSTITUTE flag is set.
*/
if ((hres == DISP_E_OVERFLOW || hres == DISP_E_TYPEMISMATCH) &&
!(dwFlags & VAR_FORMAT_NOSUBSTITUTE))
goto VarFormatFromTokens_AsStr;
}
return hres;
}
/**********************************************************************
* VarFormat [OLEAUT32.87]
*
* Format a variant from a format string.
*
* PARAMS
* pVarIn [I] Variant to format
* lpszFormat [I] Format string (see notes)
* nFirstDay [I] First day of the week, (See VarTokenizeFormatString() for details)
* nFirstWeek [I] First week of the year (See VarTokenizeFormatString() for details)
* dwFlags [I] Flags for the format (VAR_ flags from "oleauto.h")
* pbstrOut [O] Destination for formatted string.
*
* RETURNS
* Success: S_OK. pbstrOut contains the formatted value.
* Failure: E_INVALIDARG, if any parameter is invalid.
* E_OUTOFMEMORY, if enough memory cannot be allocated.
* DISP_E_TYPEMISMATCH, if the variant cannot be formatted.
*
* NOTES
* - See Variant-Formats for details concerning creating format strings.
* - This function uses LOCALE_USER_DEFAULT when calling VarTokenizeFormatString()
* and VarFormatFromTokens().
*/
HRESULT WINAPI VarFormat(LPVARIANT pVarIn, LPOLESTR lpszFormat,
int nFirstDay, int nFirstWeek, ULONG dwFlags,
BSTR *pbstrOut)
{
BYTE buff[256];
HRESULT hres;
TRACE("(%p->(%s%s),%s,%d,%d,0x%08lx,%p)\n", pVarIn, debugstr_VT(pVarIn),
debugstr_VF(pVarIn), debugstr_w(lpszFormat), nFirstDay, nFirstWeek,
dwFlags, pbstrOut);
if (!pbstrOut)
return E_INVALIDARG;
*pbstrOut = NULL;
hres = VarTokenizeFormatString(lpszFormat, buff, sizeof(buff), nFirstDay,
nFirstWeek, LOCALE_USER_DEFAULT, NULL);
if (SUCCEEDED(hres))
hres = VarFormatFromTokens(pVarIn, lpszFormat, buff, dwFlags,
pbstrOut, LOCALE_USER_DEFAULT);
TRACE("returning 0x%08lx, %s\n", hres, debugstr_w(*pbstrOut));
return hres;
}
/**********************************************************************
* VarFormatDateTime [OLEAUT32.97]
*
* Format a variant value as a date and/or time.
*
* PARAMS
* pVarIn [I] Variant to format
* nFormat [I] Format type (see notes)
* dwFlags [I] Flags for the format (VAR_ flags from "oleauto.h")
* pbstrOut [O] Destination for formatted string.
*
* RETURNS
* Success: S_OK. pbstrOut contains the formatted value.
* Failure: E_INVALIDARG, if any parameter is invalid.
* E_OUTOFMEMORY, if enough memory cannot be allocated.
* DISP_E_TYPEMISMATCH, if the variant cannot be formatted.
*
* NOTES
* This function uses LOCALE_USER_DEFAULT when determining the date format
* characters to use.
* Possible values for the nFormat parameter are:
*| Value Meaning
*| ----- -------
*| 0 General date format
*| 1 Long date format
*| 2 Short date format
*| 3 Long time format
*| 4 Short time format
*/
HRESULT WINAPI VarFormatDateTime(LPVARIANT pVarIn, INT nFormat, ULONG dwFlags, BSTR *pbstrOut)
{
static const WCHAR szEmpty[] = { '\0' };
const BYTE* lpFmt = NULL;
TRACE("(%p->(%s%s),%d,0x%08lx,%p)\n", pVarIn, debugstr_VT(pVarIn),
debugstr_VF(pVarIn), nFormat, dwFlags, pbstrOut);
if (!pVarIn || !pbstrOut || nFormat < 0 || nFormat > 4)
return E_INVALIDARG;
switch (nFormat)
{
case 0: lpFmt = fmtGeneralDate; break;
case 1: lpFmt = fmtLongDate; break;
case 2: lpFmt = fmtShortDate; break;
case 3: lpFmt = fmtLongTime; break;
case 4: lpFmt = fmtShortTime; break;
}
return VarFormatFromTokens(pVarIn, (LPWSTR)szEmpty, (BYTE*)lpFmt, dwFlags,
pbstrOut, LOCALE_USER_DEFAULT);
}
#define GETLOCALENUMBER(type,field) GetLocaleInfoW(LOCALE_USER_DEFAULT, \
type|LOCALE_RETURN_NUMBER, \
(LPWSTR)&numfmt.field, \
sizeof(numfmt.field)/sizeof(WCHAR))
/**********************************************************************
* VarFormatNumber [OLEAUT32.107]
*
* Format a variant value as a number.
*
* PARAMS
* pVarIn [I] Variant to format
* nDigits [I] Number of digits following the decimal point (-1 = user default)
* nLeading [I] Use a leading zero (-2 = user default, -1 = yes, 0 = no)
* nParens [I] Use brackets for values < 0 (-2 = user default, -1 = yes, 0 = no)
* nGrouping [I] Use grouping characters (-2 = user default, -1 = yes, 0 = no)
* dwFlags [I] Currently unused, set to zero
* pbstrOut [O] Destination for formatted string.
*
* RETURNS
* Success: S_OK. pbstrOut contains the formatted value.
* Failure: E_INVALIDARG, if any parameter is invalid.
* E_OUTOFMEMORY, if enough memory cannot be allocated.
* DISP_E_TYPEMISMATCH, if the variant cannot be formatted.
*
* NOTES
* This function uses LOCALE_USER_DEFAULT when determining the number format
* characters to use.
*/
HRESULT WINAPI VarFormatNumber(LPVARIANT pVarIn, INT nDigits, INT nLeading, INT nParens,
INT nGrouping, ULONG dwFlags, BSTR *pbstrOut)
{
HRESULT hRet;
VARIANT vStr;
TRACE("(%p->(%s%s),%d,%d,%d,%d,0x%08lx,%p)\n", pVarIn, debugstr_VT(pVarIn),
debugstr_VF(pVarIn), nDigits, nLeading, nParens, nGrouping, dwFlags, pbstrOut);
if (!pVarIn || !pbstrOut || nDigits > 9)
return E_INVALIDARG;
*pbstrOut = NULL;
V_VT(&vStr) = VT_EMPTY;
hRet = VariantCopyInd(&vStr, pVarIn);
if (SUCCEEDED(hRet))
hRet = VariantChangeTypeEx(&vStr, &vStr, LOCALE_USER_DEFAULT, 0, VT_BSTR);
if (SUCCEEDED(hRet))
{
WCHAR buff[256], decimal[8], thousands[8];
NUMBERFMTW numfmt;
/* Although MSDN makes it clear that the native versions of these functions
* are implemented using VarTokenizeFormatString()/VarFormatFromTokens(),
* using NLS gives us the same result.
*/
if (nDigits < 0)
GETLOCALENUMBER(LOCALE_IDIGITS, NumDigits);
else
numfmt.NumDigits = nDigits;
if (nLeading == -2)
GETLOCALENUMBER(LOCALE_ILZERO, LeadingZero);
else if (nLeading == -1)
numfmt.LeadingZero = 1;
else
numfmt.LeadingZero = 0;
if (nGrouping == -2)
{
WCHAR grouping[16];
grouping[2] = '\0';
GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, grouping,
sizeof(grouping)/sizeof(WCHAR));
numfmt.Grouping = grouping[2] == '2' ? 32 : grouping[0] - '0';
}
else if (nGrouping == -1)
numfmt.Grouping = 3; /* 3 = "n,nnn.nn" */
else
numfmt.Grouping = 0; /* 0 = No grouping */
if (nParens == -2)
GETLOCALENUMBER(LOCALE_INEGNUMBER, NegativeOrder);
else if (nParens == -1)
numfmt.NegativeOrder = 0; /* 0 = "(xxx)" */
else
numfmt.NegativeOrder = 1; /* 1 = "-xxx" */
numfmt.lpDecimalSep = decimal;
GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, decimal,
sizeof(decimal)/sizeof(WCHAR));
numfmt.lpThousandSep = thousands;
GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, thousands,
sizeof(thousands)/sizeof(WCHAR));
if (GetNumberFormatW(LOCALE_USER_DEFAULT, 0, V_BSTR(&vStr), &numfmt,
buff, sizeof(buff)/sizeof(WCHAR)))
{
*pbstrOut = SysAllocString(buff);
if (!*pbstrOut)
hRet = E_OUTOFMEMORY;
}
else
hRet = DISP_E_TYPEMISMATCH;
SysFreeString(V_BSTR(&vStr));
}
return hRet;
}
/**********************************************************************
* VarFormatPercent [OLEAUT32.117]
*
* Format a variant value as a percentage.
*
* PARAMS
* pVarIn [I] Variant to format
* nDigits [I] Number of digits following the decimal point (-1 = user default)
* nLeading [I] Use a leading zero (-2 = user default, -1 = yes, 0 = no)
* nParens [I] Use brackets for values < 0 (-2 = user default, -1 = yes, 0 = no)
* nGrouping [I] Use grouping characters (-2 = user default, -1 = yes, 0 = no)
* dwFlags [I] Currently unused, set to zero
* pbstrOut [O] Destination for formatted string.
*
* RETURNS
* Success: S_OK. pbstrOut contains the formatted value.
* Failure: E_INVALIDARG, if any parameter is invalid.
* E_OUTOFMEMORY, if enough memory cannot be allocated.
* DISP_E_OVERFLOW, if overflow occurs during the conversion.
* DISP_E_TYPEMISMATCH, if the variant cannot be formatted.
*
* NOTES
* This function uses LOCALE_USER_DEFAULT when determining the number format
* characters to use.
*/
HRESULT WINAPI VarFormatPercent(LPVARIANT pVarIn, INT nDigits, INT nLeading, INT nParens,
INT nGrouping, ULONG dwFlags, BSTR *pbstrOut)
{
static const WCHAR szPercent[] = { '%','\0' };
static const WCHAR szPercentBracket[] = { '%',')','\0' };
WCHAR buff[256];
HRESULT hRet;
VARIANT vDbl;
TRACE("(%p->(%s%s),%d,%d,%d,%d,0x%08lx,%p)\n", pVarIn, debugstr_VT(pVarIn),
debugstr_VF(pVarIn), nDigits, nLeading, nParens, nGrouping,
dwFlags, pbstrOut);
if (!pVarIn || !pbstrOut || nDigits > 9)
return E_INVALIDARG;
*pbstrOut = NULL;
V_VT(&vDbl) = VT_EMPTY;
hRet = VariantCopyInd(&vDbl, pVarIn);
if (SUCCEEDED(hRet))
{
hRet = VariantChangeTypeEx(&vDbl, &vDbl, LOCALE_USER_DEFAULT, 0, VT_R8);
if (SUCCEEDED(hRet))
{
if (V_R8(&vDbl) > (R8_MAX / 100.0))
return DISP_E_OVERFLOW;
V_R8(&vDbl) *= 100.0;
hRet = VarFormatNumber(&vDbl, nDigits, nLeading, nParens,
nGrouping, dwFlags, pbstrOut);
if (SUCCEEDED(hRet))
{
DWORD dwLen = strlenW(*pbstrOut);
BOOL bBracket = (*pbstrOut)[dwLen] == ')' ? TRUE : FALSE;
dwLen -= bBracket;
memcpy(buff, *pbstrOut, dwLen * sizeof(WCHAR));
strcpyW(buff + dwLen, bBracket ? szPercentBracket : szPercent);
SysFreeString(*pbstrOut);
*pbstrOut = SysAllocString(buff);
if (!*pbstrOut)
hRet = E_OUTOFMEMORY;
}
}
}
return hRet;
}
/**********************************************************************
* VarFormatCurrency [OLEAUT32.127]
*
* Format a variant value as a currency.
*
* PARAMS
* pVarIn [I] Variant to format
* nDigits [I] Number of digits following the decimal point (-1 = user default)
* nLeading [I] Use a leading zero (-2 = user default, -1 = yes, 0 = no)
* nParens [I] Use brackets for values < 0 (-2 = user default, -1 = yes, 0 = no)
* nGrouping [I] Use grouping characters (-2 = user default, -1 = yes, 0 = no)
* dwFlags [I] Currently unused, set to zero
* pbstrOut [O] Destination for formatted string.
*
* RETURNS
* Success: S_OK. pbstrOut contains the formatted value.
* Failure: E_INVALIDARG, if any parameter is invalid.
* E_OUTOFMEMORY, if enough memory cannot be allocated.
* DISP_E_TYPEMISMATCH, if the variant cannot be formatted.
*
* NOTES
* This function uses LOCALE_USER_DEFAULT when determining the currency format
* characters to use.
*/
HRESULT WINAPI VarFormatCurrency(LPVARIANT pVarIn, INT nDigits, INT nLeading,
INT nParens, INT nGrouping, ULONG dwFlags,
BSTR *pbstrOut)
{
HRESULT hRet;
VARIANT vStr;
TRACE("(%p->(%s%s),%d,%d,%d,%d,0x%08lx,%p)\n", pVarIn, debugstr_VT(pVarIn),
debugstr_VF(pVarIn), nDigits, nLeading, nParens, nGrouping, dwFlags, pbstrOut);
if (!pVarIn || !pbstrOut || nDigits > 9)
return E_INVALIDARG;
*pbstrOut = NULL;
V_VT(&vStr) = VT_EMPTY;
hRet = VariantCopyInd(&vStr, pVarIn);
if (SUCCEEDED(hRet))
hRet = VariantChangeTypeEx(&vStr, &vStr, LOCALE_USER_DEFAULT, 0, VT_BSTR);
if (SUCCEEDED(hRet))
{
WCHAR buff[256], decimal[8], thousands[8], currency[8];
CURRENCYFMTW numfmt;
if (nDigits < 0)
GETLOCALENUMBER(LOCALE_IDIGITS, NumDigits);
else
numfmt.NumDigits = nDigits;
if (nLeading == -2)
GETLOCALENUMBER(LOCALE_ILZERO, LeadingZero);
else if (nLeading == -1)
numfmt.LeadingZero = 1;
else
numfmt.LeadingZero = 0;
if (nGrouping == -2)
{
WCHAR nGrouping[16];
nGrouping[2] = '\0';
GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, nGrouping,
sizeof(nGrouping)/sizeof(WCHAR));
numfmt.Grouping = nGrouping[2] == '2' ? 32 : nGrouping[0] - '0';
}
else if (nGrouping == -1)
numfmt.Grouping = 3; /* 3 = "n,nnn.nn" */
else
numfmt.Grouping = 0; /* 0 = No grouping */
if (nParens == -2)
GETLOCALENUMBER(LOCALE_INEGCURR, NegativeOrder);
else if (nParens == -1)
numfmt.NegativeOrder = 0; /* 0 = "(xxx)" */
else
numfmt.NegativeOrder = 1; /* 1 = "-xxx" */
GETLOCALENUMBER(LOCALE_ICURRENCY, PositiveOrder);
numfmt.lpDecimalSep = decimal;
GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, decimal,
sizeof(decimal)/sizeof(WCHAR));
numfmt.lpThousandSep = thousands;
GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, thousands,
sizeof(thousands)/sizeof(WCHAR));
numfmt.lpCurrencySymbol = currency;
GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, currency,
sizeof(currency)/sizeof(WCHAR));
/* use NLS as per VarFormatNumber() */
if (GetCurrencyFormatW(LOCALE_USER_DEFAULT, 0, V_BSTR(&vStr), &numfmt,
buff, sizeof(buff)/sizeof(WCHAR)))
{
*pbstrOut = SysAllocString(buff);
if (!*pbstrOut)
hRet = E_OUTOFMEMORY;
}
else
hRet = DISP_E_TYPEMISMATCH;
SysFreeString(V_BSTR(&vStr));
}
return hRet;
}
......@@ -66,13 +66,40 @@
WINE_DEFAULT_DEBUG_CHANNEL(ole);
#define SYSDUPSTRING(str) SysAllocStringByteLen((LPCSTR)(str), SysStringByteLen(str))
const char* wine_vtypes[VT_CLSID] =
{
"VT_EMPTY","VT_NULL","VT_I2","VT_I4","VT_R4","VT_R8","VT_CY","VT_DATE",
"VT_BSTR","VT_DISPATCH","VT_ERROR","VT_BOOL","VT_VARIANT","VT_UNKNOWN",
"VT_DECIMAL","15","VT_I1","VT_UI1","VT_UI2","VT_UI4","VT_I8","VT_UI8",
"VT_INT","VT_UINT","VT_VOID","VT_HRESULT","VT_PTR","VT_SAFEARRAY",
"VT_CARRAY","VT_USERDEFINED","VT_LPSTR","VT_LPWSTR""32","33","34","35",
"VT_RECORD","VT_INT_PTR","VT_UINT_PTR","39","40","41","42","43","44","45",
"46","47","48","49","50","51","52","53","54","55","56","57","58","59","60",
"61","62","63","VT_FILETIME","VT_BLOB","VT_STREAM","VT_STORAGE",
"VT_STREAMED_OBJECT","VT_STORED_OBJECT","VT_BLOB_OBJECT","VT_CF","VT_CLSID"
};
/* Flags set in V_VT, other than the actual type value */
#define VT_EXTRA_TYPE (VT_VECTOR|VT_ARRAY|VT_BYREF|VT_RESERVED)
const char* wine_vflags[16] =
{
"",
"|VT_VECTOR",
"|VT_ARRAY",
"|VT_VECTOR|VT_ARRAY",
"|VT_BYREF",
"|VT_VECTOR|VT_ARRAY",
"|VT_ARRAY|VT_BYREF",
"|VT_VECTOR|VT_ARRAY|VT_BYREF",
"|VT_HARDTYPE",
"|VT_VECTOR|VT_HARDTYPE",
"|VT_ARRAY|VT_HARDTYPE",
"|VT_VECTOR|VT_ARRAY|VT_HARDTYPE",
"|VT_BYREF|VT_HARDTYPE",
"|VT_VECTOR|VT_ARRAY|VT_HARDTYPE",
"|VT_ARRAY|VT_BYREF|VT_HARDTYPE",
"|VT_VECTOR|VT_ARRAY|VT_BYREF|VT_HARDTYPE",
};
/* Get the extra flags from a variant pointer */
#define V_EXTRA_TYPE(v) (V_VT(v) & VT_EXTRA_TYPE)
#define SYSDUPSTRING(str) SysAllocStringByteLen((LPCSTR)(str), SysStringByteLen(str))
/* the largest valid type
*/
......@@ -101,100 +128,6 @@ static char pBuffer[BUFFER_MAX];
*/
static const double DAYS_IN_ONE_YEAR = 365.0;
/*
* Token definitions for Varient Formatting
* Worked out by experimentation on a w2k machine. Doesnt appear to be
* documented anywhere obviously so keeping definitions internally
*
*/
/* Pre defined tokens */
#define TOK_COPY 0x00
#define TOK_END 0x02
#define LARGEST_TOKENID 6
/* Mapping of token name to id put into the tokenized form
Note testing on W2K shows aaaa and oooo are not parsed??!! */
#define TOK_COLON 0x03
#define TOK_SLASH 0x04
#define TOK_c 0x05
#define TOK_d 0x08
#define TOK_dd 0x09
#define TOK_ddd 0x0a
#define TOK_dddd 0x0b
#define TOK_ddddd 0x0c
#define TOK_dddddd 0x0d
#define TOK_w 0x0f
#define TOK_ww 0x10
#define TOK_m 0x11
#define TOK_mm 0x12
#define TOK_mmm 0x13
#define TOK_mmmm 0x14
#define TOK_q 0x06
#define TOK_y 0x15
#define TOK_yy 0x16
#define TOK_yyyy 0x18
#define TOK_h 0x1e
#define TOK_Hh 0x1f
#define TOK_N 0x1a
#define TOK_Nn 0x1b
#define TOK_S 0x1c
#define TOK_Ss 0x1d
#define TOK_ttttt 0x07
#define TOK_AMsPM 0x2f
#define TOK_amspm 0x32
#define TOK_AsP 0x30
#define TOK_asp 0x33
#define TOK_AMPM 0x2e
typedef struct tagFORMATTOKEN {
const char *str;
BYTE tokenSize;
BYTE tokenId;
int varTypeRequired;
} FORMATTOKEN;
typedef struct tagFORMATHDR {
BYTE len;
BYTE hex3;
BYTE hex6;
BYTE reserved[8];
} FORMATHDR;
FORMATTOKEN formatTokens[] = { /* FIXME: Only date formats so far */
{":" , 1, TOK_COLON , 0},
{"/" , 1, TOK_SLASH , 0},
{"c" , 1, TOK_c , VT_DATE},
{"dddddd", 6, TOK_dddddd , VT_DATE},
{"ddddd" , 5, TOK_ddddd , VT_DATE},
{"dddd" , 4, TOK_dddd , VT_DATE},
{"ddd" , 3, TOK_ddd , VT_DATE},
{"dd" , 2, TOK_dd , VT_DATE},
{"d" , 1, TOK_d , VT_DATE},
{"ww" , 2, TOK_ww , VT_DATE},
{"w" , 1, TOK_w , VT_DATE},
{"mmmm" , 4, TOK_mmmm , VT_DATE},
{"mmm" , 3, TOK_mmm , VT_DATE},
{"mm" , 2, TOK_mm , VT_DATE},
{"m" , 1, TOK_m , VT_DATE},
{"q" , 1, TOK_q , VT_DATE},
{"yyyy" , 4, TOK_yyyy , VT_DATE},
{"yy" , 2, TOK_yy , VT_DATE},
{"y" , 1, TOK_y , VT_DATE},
{"h" , 1, TOK_h , VT_DATE},
{"Hh" , 2, TOK_Hh , VT_DATE},
{"Nn" , 2, TOK_Nn , VT_DATE},
{"N" , 1, TOK_N , VT_DATE},
{"S" , 1, TOK_S , VT_DATE},
{"Ss" , 2, TOK_Ss , VT_DATE},
{"ttttt" , 5, TOK_ttttt , VT_DATE},
{"AM/PM" , 5, TOK_AMsPM , VT_DATE},
{"am/pm" , 5, TOK_amspm , VT_DATE},
{"A/P" , 3, TOK_AsP , VT_DATE},
{"a/p" , 3, TOK_asp , VT_DATE},
{"AMPM" , 4, TOK_AMPM , VT_DATE},
{0x00 , 0, 0 , VT_NULL}
};
/******************************************************************************
* DateTimeStringToTm [INTERNAL]
*
......@@ -1360,7 +1293,12 @@ static HRESULT Coerce( VARIANTARG* pd, LCID lcid, ULONG dwFlags, VARIANTARG* ps,
res = VarBstrFromDate( V_UNION(ps,date), lcid, 0, &V_UNION(pd,bstrVal) );
break;
case( VT_BOOL ):
res = VarBstrFromBool( V_UNION(ps,boolVal), lcid, 0, &V_UNION(pd,bstrVal) );
if (dwFlags & VARIANT_ALPHABOOL)
res = VarBstrFromBool(V_BOOL(ps), lcid, 0, &V_BSTR(pd));
else if (dwFlags & VARIANT_LOCALBOOL)
res = VarBstrFromBool(V_BOOL(ps), lcid, VAR_LOCALBOOL, &V_BSTR(pd));
else
res = VarBstrFromI2(V_BOOL(ps), lcid, dwFlags, &V_BSTR(pd));
break;
case( VT_BSTR ):
res = VariantCopy( pd, ps );
......@@ -4545,7 +4483,7 @@ HRESULT WINAPI VarDateFromUdate(UDATE *pUdateIn, ULONG dwFlags, DATE *pDateOut)
* PARAMS
* datein [I] Variant VT_DATE format date
* dwFlags [I] Flags controlling the conversion (VAR_ flags from "oleauto.h")
* lpUdate [O] Destination for unpacked format date and time
* lpUdate [O] Destination for unpacked format date and time
*
* RETURNS
* Success: S_OK. *lpUdate contains the converted value.
......@@ -5390,24 +5328,6 @@ HRESULT WINAPI VarNumFromParseNum(NUMPARSE *pNumprs, BYTE *rgbDig,
}
/**********************************************************************
* VarFormatDateTime [OLEAUT32.97]
*/
HRESULT WINAPI VarFormatDateTime(LPVARIANT var, INT format, ULONG dwFlags, BSTR *out)
{
FIXME("%p %d %lx %p\n", var, format, dwFlags, out);
return E_NOTIMPL;
}
/**********************************************************************
* VarFormatCurrency [OLEAUT32.127]
*/
HRESULT WINAPI VarFormatCurrency(LPVARIANT var, INT digits, INT lead, INT paren, INT group, ULONG dwFlags, BSTR *out)
{
FIXME("%p %d %d %d %d %lx %p\n", var, digits, lead, paren, group, dwFlags, out);
return E_NOTIMPL;
}
/**********************************************************************
* VarBstrCmp [OLEAUT32.314]
*
* flags can be:
......@@ -5562,7 +5482,7 @@ HRESULT WINAPI VarCmp(LPVARIANT left, LPVARIANT right, LCID lcid, DWORD flags)
if (FAILED(rc)) return rc;
rc = VariantChangeType(&rv,right,0,VT_R8);
if (FAILED(rc)) return rc;
if (V_R8(&lv) == V_R8(&rv)) return VARCMP_EQ;
if (V_R8(&lv) < V_R8(&rv)) return VARCMP_LT;
if (V_R8(&lv) > V_R8(&rv)) return VARCMP_GT;
......@@ -5573,7 +5493,7 @@ HRESULT WINAPI VarCmp(LPVARIANT left, LPVARIANT right, LCID lcid, DWORD flags)
if (FAILED(rc)) return rc;
rc = VariantChangeType(&rv,right,0,VT_R4);
if (FAILED(rc)) return rc;
if (V_R4(&lv) == V_R4(&rv)) return VARCMP_EQ;
if (V_R4(&lv) < V_R4(&rv)) return VARCMP_LT;
if (V_R4(&lv) > V_R4(&rv)) return VARCMP_GT;
......@@ -6168,437 +6088,6 @@ HRESULT WINAPI VarNot(LPVARIANT in, LPVARIANT result)
}
/**********************************************************************
* VarTokenizeFormatString [OLEAUT32.140]
*
* From investigation on W2K, a list is built up which is:
*
* <0x00> AA BB - Copy from AA for BB chars (Note 1 byte with wrap!)
* <token> - Insert appropriate token
*
*/
HRESULT WINAPI VarTokenizeFormatString(LPOLESTR format, LPBYTE rgbTok,
int cbTok, int iFirstDay, int iFirstWeek,
LCID lcid, int *pcbActual) {
FORMATHDR *hdr;
int realLen, formatLeft;
BYTE *pData;
LPSTR pFormatA, pStart;
int checkStr;
BOOL insertCopy = FALSE;
LPSTR copyFrom = NULL;
TRACE("'%s', %p %d %d %d only date support\n", debugstr_w(format), rgbTok, cbTok,
iFirstDay, iFirstWeek);
/* Big enough for header? */
if (cbTok < sizeof(FORMATHDR)) {
return TYPE_E_BUFFERTOOSMALL;
}
/* Insert header */
hdr = (FORMATHDR *) rgbTok;
memset(hdr, 0x00, sizeof(FORMATHDR));
hdr->hex3 = 0x03; /* No idea what these are */
hdr->hex6 = 0x06;
/* Start parsing string */
realLen = sizeof(FORMATHDR);
pData = rgbTok + realLen;
pFormatA = HEAP_strdupWtoA( GetProcessHeap(), 0, format );
pStart = pFormatA;
formatLeft = strlen(pFormatA);
/* Work through the format */
while (*pFormatA != 0x00) {
checkStr = 0;
while (checkStr>=0 && (formatTokens[checkStr].tokenSize != 0x00)) {
if (formatLeft >= formatTokens[checkStr].tokenSize &&
strncmp(formatTokens[checkStr].str, pFormatA,
formatTokens[checkStr].tokenSize) == 0) {
TRACE("match on '%s'\n", formatTokens[checkStr].str);
/* Found Match! */
/* If we have skipped chars, insert the copy */
if (insertCopy == TRUE) {
if ((realLen + 3) > cbTok) {
HeapFree( GetProcessHeap(), 0, pFormatA );
return TYPE_E_BUFFERTOOSMALL;
}
insertCopy = FALSE;
*pData = TOK_COPY;
pData++;
*pData = (BYTE)(copyFrom - pStart);
pData++;
*pData = (BYTE)(pFormatA - copyFrom);
pData++;
realLen = realLen + 3;
}
/* Now insert the token itself */
if ((realLen + 1) > cbTok) {
HeapFree( GetProcessHeap(), 0, pFormatA );
return TYPE_E_BUFFERTOOSMALL;
}
*pData = formatTokens[checkStr].tokenId;
pData = pData + 1;
realLen = realLen + 1;
pFormatA = pFormatA + formatTokens[checkStr].tokenSize;
formatLeft = formatLeft - formatTokens[checkStr].tokenSize;
checkStr = -1; /* Flag as found and break out of while loop */
} else {
checkStr++;
}
}
/* Did we ever match a token? */
if (checkStr != -1 && insertCopy == FALSE) {
TRACE("No match - need to insert copy from %p [%p]\n", pFormatA, pStart);
insertCopy = TRUE;
copyFrom = pFormatA;
} else if (checkStr != -1) {
pFormatA = pFormatA + 1;
}
}
/* Finally, if we have skipped chars, insert the copy */
if (insertCopy == TRUE) {
TRACE("Chars left over, so still copy %p,%p,%p\n", copyFrom, pStart, pFormatA);
if ((realLen + 3) > cbTok) {
HeapFree( GetProcessHeap(), 0, pFormatA );
return TYPE_E_BUFFERTOOSMALL;
}
insertCopy = FALSE;
*pData = TOK_COPY;
pData++;
*pData = (BYTE)(copyFrom - pStart);
pData++;
*pData = (BYTE)(pFormatA - copyFrom);
pData++;
realLen = realLen + 3;
}
/* Finally insert the terminator */
if ((realLen + 1) > cbTok) {
HeapFree( GetProcessHeap(), 0, pFormatA );
return TYPE_E_BUFFERTOOSMALL;
}
*pData++ = TOK_END;
realLen = realLen + 1;
/* Finally fill in the length */
hdr->len = realLen;
*pcbActual = realLen;
#if 0
{ int i,j;
for (i=0; i<realLen; i=i+0x10) {
printf(" %4.4x : ", i);
for (j=0; j<0x10 && (i+j < realLen); j++) {
printf("%2.2x ", rgbTok[i+j]);
}
printf("\n");
}
}
#endif
HeapFree( GetProcessHeap(), 0, pFormatA );
return S_OK;
}
/**********************************************************************
* VarFormatFromTokens [OLEAUT32.139]
* FIXME: No account of flags or iFirstDay etc
*/
HRESULT WINAPI VarFormatFromTokens(LPVARIANT varIn, LPOLESTR format,
LPBYTE pbTokCur, ULONG dwFlags, BSTR *pbstrOut,
LCID lcid) {
FORMATHDR *hdr = (FORMATHDR *)pbTokCur;
BYTE *pData = pbTokCur + sizeof (FORMATHDR);
LPSTR pFormatA = HEAP_strdupWtoA( GetProcessHeap(), 0, format );
char output[BUFFER_MAX];
char *pNextPos;
int size, whichToken;
VARIANTARG Variant;
struct tm TM;
TRACE("'%s', %p %lx %p only date support\n", pFormatA, pbTokCur, dwFlags, pbstrOut);
TRACE("varIn:\n");
dump_Variant(varIn);
memset(output, 0x00, BUFFER_MAX);
pNextPos = output;
while (*pData != TOK_END && ((pData - pbTokCur) <= (hdr->len))) {
TRACE("Output looks like : '%s'\n", output);
/* Convert varient to appropriate data type */
whichToken = 0;
while ((formatTokens[whichToken].tokenSize != 0x00) &&
(formatTokens[whichToken].tokenId != *pData)) {
whichToken++;
}
/* Use Variant local from here downwards as always correct type */
if (formatTokens[whichToken].tokenSize > 0 &&
formatTokens[whichToken].varTypeRequired != 0) {
VariantInit( &Variant );
if (Coerce( &Variant, lcid, dwFlags, varIn,
formatTokens[whichToken].varTypeRequired ) != S_OK) {
HeapFree( GetProcessHeap(), 0, pFormatA );
return DISP_E_TYPEMISMATCH;
} else if (formatTokens[whichToken].varTypeRequired == VT_DATE) {
if( DateToTm( V_UNION(&Variant,date), dwFlags, &TM ) == FALSE ) {
HeapFree( GetProcessHeap(), 0, pFormatA );
return E_INVALIDARG;
}
}
}
TRACE("Looking for match on token '%x'\n", *pData);
switch (*pData) {
case TOK_COPY:
TRACE("Copy from %d for %d bytes\n", *(pData+1), *(pData+2));
memcpy(pNextPos, &pFormatA[*(pData+1)], *(pData+2));
pNextPos = pNextPos + *(pData+2);
pData = pData + 3;
break;
case TOK_COLON :
/* Get locale information - Time Separator */
size = GetLocaleInfoA(lcid, LOCALE_STIME, NULL, 0);
GetLocaleInfoA(lcid, LOCALE_STIME, pNextPos, size);
TRACE("TOK_COLON Time separator is '%s'\n", pNextPos);
pNextPos = pNextPos + size;
pData = pData + 1;
break;
case TOK_SLASH :
/* Get locale information - Date Separator */
size = GetLocaleInfoA(lcid, LOCALE_SDATE, NULL, 0);
GetLocaleInfoA(lcid, LOCALE_SDATE, pNextPos, size);
TRACE("TOK_COLON Time separator is '%s'\n", pNextPos);
pNextPos = pNextPos + size;
pData = pData + 1;
break;
case TOK_d :
sprintf(pNextPos, "%d", TM.tm_mday);
pNextPos = pNextPos + strlen(pNextPos);
pData = pData + 1;
break;
case TOK_dd :
sprintf(pNextPos, "%2.2d", TM.tm_mday);
pNextPos = pNextPos + strlen(pNextPos);
pData = pData + 1;
break;
case TOK_w :
sprintf(pNextPos, "%d", TM.tm_wday+1);
pNextPos = pNextPos + strlen(pNextPos);
pData = pData + 1;
break;
case TOK_m :
sprintf(pNextPos, "%d", TM.tm_mon+1);
pNextPos = pNextPos + strlen(pNextPos);
pData = pData + 1;
break;
case TOK_mm :
sprintf(pNextPos, "%2.2d", TM.tm_mon+1);
pNextPos = pNextPos + strlen(pNextPos);
pData = pData + 1;
break;
case TOK_q :
sprintf(pNextPos, "%d", ((TM.tm_mon+1)/4)+1);
pNextPos = pNextPos + strlen(pNextPos);
pData = pData + 1;
break;
case TOK_y :
sprintf(pNextPos, "%2.2d", TM.tm_yday+1);
pNextPos = pNextPos + strlen(pNextPos);
pData = pData + 1;
break;
case TOK_yy :
sprintf(pNextPos, "%2.2d", TM.tm_year);
pNextPos = pNextPos + strlen(pNextPos);
pData = pData + 1;
break;
case TOK_yyyy :
sprintf(pNextPos, "%4.4d", TM.tm_year);
pNextPos = pNextPos + strlen(pNextPos);
pData = pData + 1;
break;
case TOK_h :
sprintf(pNextPos, "%d", TM.tm_hour);
pNextPos = pNextPos + strlen(pNextPos);
pData = pData + 1;
break;
case TOK_Hh :
sprintf(pNextPos, "%2.2d", TM.tm_hour);
pNextPos = pNextPos + strlen(pNextPos);
pData = pData + 1;
break;
case TOK_N :
sprintf(pNextPos, "%d", TM.tm_min);
pNextPos = pNextPos + strlen(pNextPos);
pData = pData + 1;
break;
case TOK_Nn :
sprintf(pNextPos, "%2.2d", TM.tm_min);
pNextPos = pNextPos + strlen(pNextPos);
pData = pData + 1;
break;
case TOK_S :
sprintf(pNextPos, "%d", TM.tm_sec);
pNextPos = pNextPos + strlen(pNextPos);
pData = pData + 1;
break;
case TOK_Ss :
sprintf(pNextPos, "%2.2d", TM.tm_sec);
pNextPos = pNextPos + strlen(pNextPos);
pData = pData + 1;
break;
/* FIXME: To Do! */
case TOK_ttttt :
case TOK_AMsPM :
case TOK_amspm :
case TOK_AsP :
case TOK_asp :
case TOK_AMPM :
case TOK_c :
case TOK_ddd :
case TOK_dddd :
case TOK_ddddd :
case TOK_dddddd :
case TOK_ww :
case TOK_mmm :
case TOK_mmmm :
default:
FIXME("Unhandled token for VarFormat %d\n", *pData);
HeapFree( GetProcessHeap(), 0, pFormatA );
return E_INVALIDARG;
}
}
*pbstrOut = StringDupAtoBstr( output );
HeapFree( GetProcessHeap(), 0, pFormatA );
return S_OK;
}
/**********************************************************************
* VarFormat [OLEAUT32.87]
*
*/
HRESULT WINAPI VarFormat(LPVARIANT varIn, LPOLESTR format,
int firstDay, int firstWeek, ULONG dwFlags,
BSTR *pbstrOut) {
LPSTR pNewString = NULL;
HRESULT rc = S_OK;
TRACE("mostly stub! format='%s' day=%d, wk=%d, flags=%ld\n",
debugstr_w(format), firstDay, firstWeek, dwFlags);
TRACE("varIn:\n");
dump_Variant(varIn);
/* Note: Must Handle references type Variants (contain ptrs
to values rather than values */
/* Get format string */
pNewString = HEAP_strdupWtoA( GetProcessHeap(), 0, format );
/* FIXME: Handle some simple pre-definted format strings : */
if (((V_VT(varIn)&VT_TYPEMASK) == VT_CY) && (lstrcmpiA(pNewString, "Currency") == 0)) {
/* Can't use VarBstrFromCy as it does not put currency sign on nor decimal places */
double curVal;
/* Handle references type Variants (contain ptrs to values rather than values */
if (V_VT(varIn)&VT_BYREF) {
rc = VarR8FromCy(*(CY *)V_UNION(varIn,byref), &curVal);
} else {
rc = VarR8FromCy(V_UNION(varIn,cyVal), &curVal);
}
if (rc == S_OK) {
char tmpStr[BUFFER_MAX];
sprintf(tmpStr, "%f", curVal);
if (GetCurrencyFormatA(GetUserDefaultLCID(), dwFlags, tmpStr, NULL, pBuffer, BUFFER_MAX) == 0) {
return E_FAIL;
} else {
*pbstrOut = StringDupAtoBstr( pBuffer );
}
}
} else if ((V_VT(varIn)&VT_TYPEMASK) == VT_DATE) {
/* Attempt to do proper formatting! */
int firstToken = -1;
rc = VarTokenizeFormatString(format, pBuffer, sizeof(pBuffer), firstDay,
firstWeek, GetUserDefaultLCID(), &firstToken);
if (rc==S_OK) {
rc = VarFormatFromTokens(varIn, format, pBuffer, dwFlags, pbstrOut, GetUserDefaultLCID());
}
} else if ((V_VT(varIn)&VT_TYPEMASK) == VT_R8) {
if (V_VT(varIn)&VT_BYREF) {
sprintf(pBuffer, "%f", *V_UNION(varIn,pdblVal));
} else {
sprintf(pBuffer, "%f", V_UNION(varIn,dblVal));
}
*pbstrOut = StringDupAtoBstr( pBuffer );
} else if ((V_VT(varIn)&VT_TYPEMASK) == VT_I2) {
if (V_VT(varIn)&VT_BYREF) {
sprintf(pBuffer, "%d", *V_UNION(varIn,piVal));
} else {
sprintf(pBuffer, "%d", V_UNION(varIn,iVal));
}
*pbstrOut = StringDupAtoBstr( pBuffer );
} else if ((V_VT(varIn)&VT_TYPEMASK) == VT_BSTR) {
if (V_VT(varIn)&VT_BYREF)
*pbstrOut = SysAllocString( *V_UNION(varIn,pbstrVal) );
else
*pbstrOut = SysAllocString( V_UNION(varIn,bstrVal) );
} else {
FIXME("VarFormat: Unsupported format %d!\n", V_VT(varIn)&VT_TYPEMASK);
*pbstrOut = StringDupAtoBstr( "??" );
}
/* Free allocated storage */
HeapFree( GetProcessHeap(), 0, pNewString );
TRACE("result: '%s'\n", debugstr_w(*pbstrOut));
return rc;
}
/**********************************************************************
* VarCyMulI4 [OLEAUT32.304]
* Multiply currency value by integer
*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment