- more Extensions work (begin to detect ATI extensions)
[wine/wine-kai.git] / dlls / oleaut32 / varformat.c
blob2f19ca87a1b5405e02ad20c13a2caf8cc80af69e
1 /*
2 * Variant formatting functions
4 * Copyright 2003 Jon Griffiths
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * NOTES
21 * Since the formatting functions aren't properly documented, I used the
22 * Visual Basic documentation as a guide to implementing these functions. This
23 * means that some named or user-defined formats may work slightly differently.
24 * Please submit a test case if you find a difference.
27 #include "config.h"
29 #include <string.h>
30 #include <stdlib.h>
31 #include <stdarg.h>
32 #include <stdio.h>
34 #define NONAMELESSUNION
35 #define NONAMELESSSTRUCT
36 #include "windef.h"
37 #include "winbase.h"
38 #include "oleauto.h"
39 #include "wine/debug.h"
40 #include "wine/unicode.h"
41 #include "winerror.h"
42 #include "variant.h"
44 WINE_DEFAULT_DEBUG_CHANNEL(ole);
46 /* Make sure internal conversions to strings use the '.','+'/'-' and ','
47 * format chars from the US locale. This enables us to parse the created
48 * strings to determine the number of decimal places, exponent, etc.
50 #define LCID_US MAKELCID(MAKELANGID(LANG_ENGLISH,SUBLANG_ENGLISH_US),SORT_DEFAULT)
52 static const WCHAR szPercent_d[] = { '%','d','\0' };
53 static const WCHAR szPercentZeroTwo_d[] = { '%','0','2','d','\0' };
54 static const WCHAR szPercentZeroFour_d[] = { '%','0','4','d','\0' };
55 static const WCHAR szPercentZeroStar_d[] = { '%','0','*','d','\0' };
57 #if 0
58 #define dump_tokens(rgb) do { \
59 int i_; TRACE("Tokens->{ \n"); \
60 for (i_ = 0; i_ < rgb[0]; i_++) \
61 TRACE("%s0x%02x", i_?",":"",rgb[i_]); \
62 TRACE(" }\n"); \
63 } while(0)
64 #endif
66 /******************************************************************************
67 * Variant-Formats {OLEAUT32}
69 * NOTES
70 * When formatting a variant a variety of format strings may be used to generate
71 * different kinds of formatted output. A format string consists of either a named
72 * format, or a user-defined format.
74 * The following named formats are defined:
75 *| Name Description
76 *| ---- -----------
77 *| General Date Display Date, and time for non-integer values
78 *| Short Date Short date format as defined by locale settings
79 *| Medium Date Medium date format as defined by locale settings
80 *| Long Date Long date format as defined by locale settings
81 *| Short Time Short Time format as defined by locale settings
82 *| Medium Time Medium time format as defined by locale settings
83 *| Long Time Long time format as defined by locale settings
84 *| True/False Localised text of "True" or "False"
85 *| Yes/No Localised text of "Yes" or "No"
86 *| On/Off Localised text of "On" or "Off"
87 *| General Number No thousands separator. No decimal points for integers
88 *| Currency General currency format using localised characters
89 *| Fixed At least one whole and two fractional digits
90 *| Standard Same as 'Fixed', but including decimal separators
91 *| Percent Multiply by 100 and display a trailing '%' character
92 *| Scientific Display with exponent
94 * User-defined formats consist of a combination of tokens and literal
95 * characters. Literal characters are copied unmodified to the formatted
96 * output at the position they occupy in the format string. Any character
97 * that is not recognised as a token is treated as a literal. A literal can
98 * also be specified by preceeding it with a backslash character
99 * (e.g. "\L\i\t\e\r\a\l") or enclosing it in double quotes.
101 * A user-defined format can have up to 4 sections, depending on the type of
102 * format. The following table lists sections and their meaning:
103 *| Format Type Sections Meaning
104 *| ----------- -------- -------
105 *| Number 1 Use the same format for all numbers
106 *| Number 2 Use format 1 for positive and 2 for negative numbers
107 *| Number 3 Use format 1 for positive, 2 for zero, and 3
108 *| for negative numbers.
109 *| Number 4 Use format 1 for positive, 2 for zero, 3 for
110 *| negative, and 4 for null numbers.
111 *| String 1 Use the same format for all strings
112 *| String 2 Use format 2 for null and empty strings, otherwise
113 *| use format 1.
114 *| Date 1 Use the same format for all dates
116 * The formatting tokens fall into several categories depending on the type
117 * of formatted output. For more information on each type, see
118 * VarFormat-Dates(), VarFormat-Strings() and VarFormat-Numbers().
120 * SEE ALSO
121 * VarTokenizeFormatString(), VarFormatFromTokens(), VarFormat(),
122 * VarFormatDateTime(), VarFormatNumber(), VarFormatCurrency().
125 /******************************************************************************
126 * VarFormat-Strings {OLEAUT32}
128 * NOTES
129 * When formatting a variant as a string, it is first converted to a VT_BSTR.
130 * The user-format string defines which characters are copied into which
131 * positions in the output string. Literals may be inserted in the format
132 * string. When creating the formatted string, excess characters in the string
133 * (those not consumed by a token) are appended to the end of the output. If
134 * there are more tokens than characters in the string to format, spaces will
135 * be inserted at the start of the string if the '@' token was used.
137 * By default strings are converted to lowercase, or uppercase if the '>' token
138 * is encountered. This applies to the whole string: it is not possible to
139 * generate a mixed-case output string.
141 * In user-defined string formats, the following tokens are recognised:
142 *| Token Description
143 *| ----- -----------
144 *| '@' Copy a char from the source, or a space if no chars are left.
145 *| '&' Copy a char from the source, or write nothing if no chars are left.
146 *| '<' Output the whole string as lower-case (the default).
147 *| '>' Output the whole string as upper-case.
148 *| '!' MSDN indicates that this character should cause right-to-left
149 *| copying, however tests show that it is tokenised but not processed.
153 * Common format definitions
156 /* Fomat types */
157 #define FMT_TYPE_UNKNOWN 0x0
158 #define FMT_TYPE_GENERAL 0x1
159 #define FMT_TYPE_NUMBER 0x2
160 #define FMT_TYPE_DATE 0x3
161 #define FMT_TYPE_STRING 0x4
163 #define FMT_TO_STRING 0x0 /* If header->size == this, act like VB's Str() fn */
165 typedef struct tagFMT_SHORT_HEADER
167 BYTE size; /* Size of tokenised block (including header), or FMT_TO_STRING */
168 BYTE type; /* Allowable types (FMT_TYPE_*) */
169 BYTE offset[1]; /* Offset of the first (and only) format section */
170 } FMT_SHORT_HEADER;
172 typedef struct tagFMT_HEADER
174 BYTE size; /* Total size of the whole tokenised block (including header) */
175 BYTE type; /* Allowable types (FMT_TYPE_*) */
176 BYTE starts[4]; /* Offset of each of the 4 format sections, or 0 if none */
177 } FMT_HEADER;
179 #define FmtGetPositive(x) (x->starts[0])
180 #define FmtGetNegative(x) (x->starts[1] ? x->starts[1] : x->starts[0])
181 #define FmtGetZero(x) (x->starts[2] ? x->starts[2] : x->starts[0])
182 #define FmtGetNull(x) (x->starts[3] ? x->starts[3] : x->starts[0])
185 * String formats
188 #define FMT_FLAG_LT 0x1 /* Has '<' (lower case) */
189 #define FMT_FLAG_GT 0x2 /* Has '>' (upper case) */
190 #define FMT_FLAG_RTL 0x4 /* Has '!' (Copy right to left) */
192 typedef struct tagFMT_STRING_HEADER
194 BYTE flags; /* LT, GT, RTL */
195 BYTE unknown1;
196 BYTE unknown2;
197 BYTE copy_chars; /* Number of chars to be copied */
198 BYTE unknown3;
199 } FMT_STRING_HEADER;
202 * Number formats
205 #define FMT_FLAG_PERCENT 0x1 /* Has '%' (Percentage) */
206 #define FMT_FLAG_EXPONENT 0x2 /* Has 'e' (Exponent/Scientific notation) */
207 #define FMT_FLAG_THOUSANDS 0x4 /* Has ',' (Standard use of the thousands separator) */
208 #define FMT_FLAG_BOOL 0x20 /* Boolean format */
210 typedef struct tagFMT_NUMBER_HEADER
212 BYTE flags; /* PERCENT, EXPONENT, THOUSANDS, BOOL */
213 BYTE multiplier; /* Multiplier, 100 for percentages */
214 BYTE divisor; /* Divisor, 1000 if '%%' was used */
215 BYTE whole; /* Number of digits before the decimal point */
216 BYTE fractional; /* Number of digits after the decimal point */
217 } FMT_NUMBER_HEADER;
220 * Date Formats
222 typedef struct tagFMT_DATE_HEADER
224 BYTE flags;
225 BYTE unknown1;
226 BYTE unknown2;
227 BYTE unknown3;
228 BYTE unknown4;
229 } FMT_DATE_HEADER;
232 * Format token values
234 #define FMT_GEN_COPY 0x00 /* \n, "lit" => 0,pos,len: Copy len chars from input+pos */
235 #define FMT_GEN_INLINE 0x01 /* => 1,len,[chars]: Copy len chars from token stream */
236 #define FMT_GEN_END 0x02 /* \0,; => 2: End of the tokenised format */
237 #define FMT_DATE_TIME_SEP 0x03 /* Time separator char */
238 #define FMT_DATE_DATE_SEP 0x04 /* Date separator char */
239 #define FMT_DATE_GENERAL 0x05 /* General format date */
240 #define FMT_DATE_QUARTER 0x06 /* Quarter of the year from 1-4 */
241 #define FMT_DATE_TIME_SYS 0x07 /* System long time format */
242 #define FMT_DATE_DAY 0x08 /* Day with no leading 0 */
243 #define FMT_DATE_DAY_0 0x09 /* Day with leading 0 */
244 #define FMT_DATE_DAY_SHORT 0x0A /* Short day name */
245 #define FMT_DATE_DAY_LONG 0x0B /* Long day name */
246 #define FMT_DATE_SHORT 0x0C /* Short date format */
247 #define FMT_DATE_LONG 0x0D /* Long date format */
248 #define FMT_DATE_MEDIUM 0x0E /* Medium date format */
249 #define FMT_DATE_DAY_WEEK 0x0F /* First day of the week */
250 #define FMT_DATE_WEEK_YEAR 0x10 /* First week of the year */
251 #define FMT_DATE_MON 0x11 /* Month with no leading 0 */
252 #define FMT_DATE_MON_0 0x12 /* Month with leading 0 */
253 #define FMT_DATE_MON_SHORT 0x13 /* Short month name */
254 #define FMT_DATE_MON_LONG 0x14 /* Long month name */
255 #define FMT_DATE_YEAR_DOY 0x15 /* Day of the year with no leading 0 */
256 #define FMT_DATE_YEAR_0 0x16 /* 2 digit year with leading 0 */
257 /* NOTE: token 0x17 is not defined, 'yyy' is not valid */
258 #define FMT_DATE_YEAR_LONG 0x18 /* 4 digit year */
259 #define FMT_DATE_MIN 0x1A /* Minutes with no leading 0 */
260 #define FMT_DATE_MIN_0 0x1B /* Minutes with leading 0 */
261 #define FMT_DATE_SEC 0x1C /* Seconds with no leading 0 */
262 #define FMT_DATE_SEC_0 0x1D /* Seconds with leading 0 */
263 #define FMT_DATE_HOUR 0x1E /* Hours with no leading 0 */
264 #define FMT_DATE_HOUR_0 0x1F /* Hours with leading 0 */
265 #define FMT_DATE_HOUR_12 0x20 /* Hours with no leading 0, 12 hour clock */
266 #define FMT_DATE_HOUR_12_0 0x21 /* Hours with leading 0, 12 hour clock */
267 #define FMT_DATE_TIME_UNK2 0x23
268 /* FIXME: probably missing some here */
269 #define FMT_DATE_AMPM_SYS1 0x2E /* AM/PM as defined by system settings */
270 #define FMT_DATE_AMPM_UPPER 0x2F /* Upper-case AM or PM */
271 #define FMT_DATE_A_UPPER 0x30 /* Upper-case A or P */
272 #define FMT_DATE_AMPM_SYS2 0x31 /* AM/PM as defined by system settings */
273 #define FMT_DATE_AMPM_LOWER 0x32 /* Lower-case AM or PM */
274 #define FMT_DATE_A_LOWER 0x33 /* Lower-case A or P */
275 #define FMT_NUM_COPY_ZERO 0x34 /* Copy 1 digit or 0 if no digit */
276 #define FMT_NUM_COPY_SKIP 0x35 /* Copy 1 digit or skip if no digit */
277 #define FMT_NUM_DECIMAL 0x36 /* Decimal separator */
278 #define FMT_NUM_EXP_POS_U 0x37 /* Scientific notation, uppercase, + sign */
279 #define FMT_NUM_EXP_NEG_U 0x38 /* Scientific notation, lowercase, - sign */
280 #define FMT_NUM_EXP_POS_L 0x39 /* Scientific notation, uppercase, + sign */
281 #define FMT_NUM_EXP_NEG_L 0x3A /* Scientific notation, lowercase, - sign */
282 #define FMT_NUM_CURRENCY 0x3B /* Currency symbol */
283 #define FMT_NUM_TRUE_FALSE 0x3D /* Convert to "True" or "False" */
284 #define FMT_NUM_YES_NO 0x3E /* Convert to "Yes" or "No" */
285 #define FMT_NUM_ON_OFF 0x3F /* Convert to "On" or "Off" */
286 #define FMT_STR_COPY_SPACE 0x40 /* Copy len chars with space if no char */
287 #define FMT_STR_COPY_SKIP 0x41 /* Copy len chars or skip if no char */
288 /* Wine additions */
289 #define FMT_WINE_HOURS_12 0x81 /* Hours using 12 hour clockhourCopy len chars or skip if no char */
291 /* Named Formats and their tokenised values */
292 static const WCHAR szGeneralDate[] = { 'G','e','n','e','r','a','l',' ','D','a','t','e','\0' };
293 static const BYTE fmtGeneralDate[0x0a] =
295 0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
296 0x0,0x0,0x0,0x0,0x0,
297 FMT_DATE_GENERAL,FMT_GEN_END
300 static const WCHAR szShortDate[] = { 'S','h','o','r','t',' ','D','a','t','e','\0' };
301 static const BYTE fmtShortDate[0x0a] =
303 0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
304 0x0,0x0,0x0,0x0,0x0,
305 FMT_DATE_SHORT,FMT_GEN_END
308 static const WCHAR szMediumDate[] = { 'M','e','d','i','u','m',' ','D','a','t','e','\0' };
309 static const BYTE fmtMediumDate[0x0a] =
311 0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
312 0x0,0x0,0x0,0x0,0x0,
313 FMT_DATE_MEDIUM,FMT_GEN_END
316 static const WCHAR szLongDate[] = { 'L','o','n','g',' ','D','a','t','e','\0' };
317 static const BYTE fmtLongDate[0x0a] =
319 0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
320 0x0,0x0,0x0,0x0,0x0,
321 FMT_DATE_LONG,FMT_GEN_END
324 static const WCHAR szShortTime[] = { 'S','h','o','r','t',' ','T','i','m','e','\0' };
325 static const BYTE fmtShortTime[0x0c] =
327 0x0c,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
328 0x0,0x0,0x0,0x0,0x0,
329 FMT_DATE_TIME_UNK2,FMT_DATE_TIME_SEP,FMT_DATE_MIN_0,FMT_GEN_END
332 static const WCHAR szMediumTime[] = { 'M','e','d','i','u','m',' ','T','i','m','e','\0' };
333 static const BYTE fmtMediumTime[0x11] =
335 0x11,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
336 0x0,0x0,0x0,0x0,0x0,
337 FMT_DATE_HOUR_12_0,FMT_DATE_TIME_SEP,FMT_DATE_MIN_0,
338 FMT_GEN_INLINE,0x01,' ','\0',FMT_DATE_AMPM_SYS1,FMT_GEN_END
341 static const WCHAR szLongTime[] = { 'L','o','n','g',' ','T','i','m','e','\0' };
342 static const BYTE fmtLongTime[0x0d] =
344 0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER),
345 0x0,0x0,0x0,0x0,0x0,
346 FMT_DATE_TIME_SYS,FMT_GEN_END
349 static const WCHAR szTrueFalse[] = { 'T','r','u','e','/','F','a','l','s','e','\0' };
350 static const BYTE fmtTrueFalse[0x0d] =
352 0x0d,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
353 FMT_FLAG_BOOL,0x0,0x0,0x0,0x0,
354 FMT_NUM_TRUE_FALSE,FMT_GEN_END
357 static const WCHAR szYesNo[] = { 'Y','e','s','/','N','o','\0' };
358 static const BYTE fmtYesNo[0x0d] =
360 0x0d,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
361 FMT_FLAG_BOOL,0x0,0x0,0x0,0x0,
362 FMT_NUM_YES_NO,FMT_GEN_END
365 static const WCHAR szOnOff[] = { 'O','n','/','O','f','f','\0' };
366 static const BYTE fmtOnOff[0x0d] =
368 0x0d,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
369 FMT_FLAG_BOOL,0x0,0x0,0x0,0x0,
370 FMT_NUM_ON_OFF,FMT_GEN_END
373 static const WCHAR szGeneralNumber[] = { 'G','e','n','e','r','a','l',' ','N','u','m','b','e','r','\0' };
374 static const BYTE fmtGeneralNumber[sizeof(FMT_HEADER)] =
376 sizeof(FMT_HEADER),FMT_TYPE_GENERAL,sizeof(FMT_HEADER),0x0,0x0,0x0
379 static const WCHAR szCurrency[] = { 'C','u','r','r','e','n','c','y','\0' };
380 static const BYTE fmtCurrency[0x26] =
382 0x26,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x12,0x0,0x0,
383 /* Positive numbers */
384 FMT_FLAG_THOUSANDS,0xcc,0x0,0x1,0x2,
385 FMT_NUM_CURRENCY,FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,
386 FMT_GEN_END,
387 /* Negative numbers */
388 FMT_FLAG_THOUSANDS,0xcc,0x0,0x1,0x2,
389 FMT_GEN_INLINE,0x1,'(','\0',FMT_NUM_CURRENCY,FMT_NUM_COPY_ZERO,0x1,
390 FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,FMT_GEN_INLINE,0x1,')','\0',
391 FMT_GEN_END
394 static const WCHAR szFixed[] = { 'F','i','x','e','d','\0' };
395 static const BYTE fmtFixed[0x11] =
397 0x11,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
398 0x0,0x0,0x0,0x1,0x2,
399 FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,FMT_GEN_END
402 static const WCHAR szStandard[] = { 'S','t','a','n','d','a','r','d','\0' };
403 static const BYTE fmtStandard[0x11] =
405 0x11,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
406 FMT_FLAG_THOUSANDS,0x0,0x0,0x1,0x2,
407 FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,FMT_GEN_END
410 static const WCHAR szPercent[] = { 'P','e','r','c','e','n','t','\0' };
411 static const BYTE fmtPercent[0x15] =
413 0x15,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
414 FMT_FLAG_PERCENT,0x1,0x0,0x1,0x2,
415 FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,
416 FMT_GEN_INLINE,0x1,'%','\0',FMT_GEN_END
419 static const WCHAR szScientific[] = { 'S','c','i','e','n','t','i','f','i','c','\0' };
420 static const BYTE fmtScientific[0x13] =
422 0x13,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0,
423 FMT_FLAG_EXPONENT,0x0,0x0,0x1,0x2,
424 FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,FMT_NUM_EXP_POS_U,0x2,FMT_GEN_END
427 typedef struct tagNAMED_FORMAT
429 LPCWSTR name;
430 const BYTE* format;
431 } NAMED_FORMAT;
433 /* Format name to tokenised format. Must be kept sorted by name */
434 static const NAMED_FORMAT VARIANT_NamedFormats[] =
436 { szCurrency, fmtCurrency },
437 { szFixed, fmtFixed },
438 { szGeneralDate, fmtGeneralDate },
439 { szGeneralNumber, fmtGeneralNumber },
440 { szLongDate, fmtLongDate },
441 { szLongTime, fmtLongTime },
442 { szMediumDate, fmtMediumDate },
443 { szMediumTime, fmtMediumTime },
444 { szOnOff, fmtOnOff },
445 { szPercent, fmtPercent },
446 { szScientific, fmtScientific },
447 { szShortDate, fmtShortDate },
448 { szShortTime, fmtShortTime },
449 { szStandard, fmtStandard },
450 { szTrueFalse, fmtTrueFalse },
451 { szYesNo, fmtYesNo }
453 typedef const NAMED_FORMAT *LPCNAMED_FORMAT;
455 static int FormatCompareFn(const void *l, const void *r)
457 return strcmpiW(((LPCNAMED_FORMAT)l)->name, ((LPCNAMED_FORMAT)r)->name);
460 static inline const BYTE *VARIANT_GetNamedFormat(LPCWSTR lpszFormat)
462 NAMED_FORMAT key;
463 LPCNAMED_FORMAT fmt;
465 key.name = lpszFormat;
466 fmt = (LPCNAMED_FORMAT)bsearch(&key, VARIANT_NamedFormats,
467 sizeof(VARIANT_NamedFormats)/sizeof(NAMED_FORMAT),
468 sizeof(NAMED_FORMAT), FormatCompareFn);
469 return fmt ? fmt->format : NULL;
472 /* Return an error if the token for the value will not fit in the destination */
473 #define NEED_SPACE(x) if (cbTok < (int)(x)) return TYPE_E_BUFFERTOOSMALL; cbTok -= (x)
475 /* Non-zero if the format is unknown or a given type */
476 #define COULD_BE(typ) ((!fmt_number && header->type==FMT_TYPE_UNKNOWN)||header->type==typ)
478 /* State during tokenising */
479 #define FMT_STATE_OPEN_COPY 0x1 /* Last token written was a copy */
480 #define FMT_STATE_WROTE_DECIMAL 0x2 /* Already wrote a decimal separator */
481 #define FMT_STATE_SEEN_HOURS 0x4 /* See the hh specifier */
482 #define FMT_STATE_WROTE_MINUTES 0x8 /* Wrote minutes */
484 /**********************************************************************
485 * VarTokenizeFormatString [OLEAUT32.140]
487 * Convert a format string into tokenised form.
489 * PARAMS
490 * lpszFormat [I] Format string to tokenise
491 * rgbTok [O] Destination for tokenised format
492 * cbTok [I] Size of rgbTok in bytes
493 * nFirstDay [I] First day of the week (1-7, or 0 for current system default)
494 * nFirstWeek [I] How to treat the first week (see notes)
495 * lcid [I] Locale Id of the format string
496 * pcbActual [O] If non-NULL, filled with the first token generated
498 * RETURNS
499 * Success: S_OK. rgbTok contains the tokenised format.
500 * Failure: E_INVALIDARG, if any argument is invalid.
501 * TYPE_E_BUFFERTOOSMALL, if rgbTok is not large enough.
503 * NOTES
504 * Valid values for the nFirstWeek parameter are:
505 *| Value Meaning
506 *| ----- -------
507 *| 0 Use the current system default
508 *| 1 The first week is that containing Jan 1
509 *| 2 Four or more days of the first week are in the current year
510 *| 3 The first week is 7 days long
511 * See Variant-Formats(), VarFormatFromTokens().
513 HRESULT WINAPI VarTokenizeFormatString(LPOLESTR lpszFormat, LPBYTE rgbTok,
514 int cbTok, int nFirstDay, int nFirstWeek,
515 LCID lcid, int *pcbActual)
517 /* Note: none of these strings should be NUL terminated */
518 static const WCHAR szTTTTT[] = { 't','t','t','t','t' };
519 static const WCHAR szAMPM[] = { 'A','M','P','M' };
520 static const WCHAR szampm[] = { 'a','m','p','m' };
521 static const WCHAR szAMSlashPM[] = { 'A','M','/','P','M' };
522 static const WCHAR szamSlashpm[] = { 'a','m','/','p','m' };
523 const BYTE *namedFmt;
524 FMT_HEADER *header = (FMT_HEADER*)rgbTok;
525 FMT_STRING_HEADER *str_header = (FMT_STRING_HEADER*)(rgbTok + sizeof(FMT_HEADER));
526 FMT_NUMBER_HEADER *num_header = (FMT_NUMBER_HEADER*)str_header;
527 FMT_DATE_HEADER *date_header = (FMT_DATE_HEADER*)str_header;
528 BYTE* pOut = rgbTok + sizeof(FMT_HEADER) + sizeof(FMT_STRING_HEADER);
529 BYTE* pLastHours = NULL;
530 BYTE fmt_number = 0;
531 DWORD fmt_state = 0;
532 LPCWSTR pFormat = lpszFormat;
534 TRACE("(%s,%p,%d,%d,%d,0x%08lx,%p)\n", debugstr_w(lpszFormat), rgbTok, cbTok,
535 nFirstDay, nFirstWeek, lcid, pcbActual);
537 if (!rgbTok ||
538 nFirstDay < 0 || nFirstDay > 7 || nFirstWeek < 0 || nFirstWeek > 3)
539 return E_INVALIDARG;
541 if (!lpszFormat || !*lpszFormat)
543 /* An empty string means 'general format' */
544 NEED_SPACE(sizeof(BYTE));
545 *rgbTok = FMT_TO_STRING;
546 if (pcbActual)
547 *pcbActual = FMT_TO_STRING;
548 return S_OK;
551 if (cbTok > 255)
552 cbTok = 255; /* Ensure we error instead of wrapping */
554 /* Named formats */
555 namedFmt = VARIANT_GetNamedFormat(lpszFormat);
556 if (namedFmt)
558 NEED_SPACE(namedFmt[0]);
559 memcpy(rgbTok, namedFmt, namedFmt[0]);
560 TRACE("Using pre-tokenised named format %s\n", debugstr_w(lpszFormat));
561 /* FIXME: pcbActual */
562 return S_OK;
565 /* Insert header */
566 NEED_SPACE(sizeof(FMT_HEADER) + sizeof(FMT_STRING_HEADER));
567 memset(header, 0, sizeof(FMT_HEADER));
568 memset(str_header, 0, sizeof(FMT_STRING_HEADER));
570 header->starts[fmt_number] = sizeof(FMT_HEADER);
572 while (*pFormat)
574 /* --------------
575 * General tokens
576 * --------------
578 if (*pFormat == ';')
580 while (*pFormat == ';')
582 TRACE(";\n");
583 if (++fmt_number > 3)
584 return E_INVALIDARG; /* too many formats */
585 pFormat++;
587 if (*pFormat)
589 TRACE("New header\n");
590 NEED_SPACE(sizeof(BYTE) + sizeof(FMT_STRING_HEADER));
591 *pOut++ = FMT_GEN_END;
593 header->starts[fmt_number] = pOut - rgbTok;
594 str_header = (FMT_STRING_HEADER*)pOut;
595 num_header = (FMT_NUMBER_HEADER*)pOut;
596 date_header = (FMT_DATE_HEADER*)pOut;
597 memset(str_header, 0, sizeof(FMT_STRING_HEADER));
598 pOut += sizeof(FMT_STRING_HEADER);
599 fmt_state = 0;
600 pLastHours = NULL;
603 else if (*pFormat == '\\')
605 /* Escaped character */
606 if (pFormat[1])
608 NEED_SPACE(3 * sizeof(BYTE));
609 pFormat++;
610 *pOut++ = FMT_GEN_COPY;
611 *pOut++ = pFormat - lpszFormat;
612 *pOut++ = 0x1;
613 fmt_state |= FMT_STATE_OPEN_COPY;
614 TRACE("'\\'\n");
616 else
617 fmt_state &= ~FMT_STATE_OPEN_COPY;
618 pFormat++;
620 else if (*pFormat == '"')
622 /* Escaped string
623 * Note: Native encodes "" as a copy of length zero. That's just dumb, so
624 * here we avoid encoding anything in this case.
626 if (!pFormat[1])
627 pFormat++;
628 else if (pFormat[1] == '"')
630 pFormat += 2;
632 else
634 LPCWSTR start = ++pFormat;
635 while (*pFormat && *pFormat != '"')
636 pFormat++;
637 NEED_SPACE(3 * sizeof(BYTE));
638 *pOut++ = FMT_GEN_COPY;
639 *pOut++ = start - lpszFormat;
640 *pOut++ = pFormat - start;
641 if (*pFormat == '"')
642 pFormat++;
643 TRACE("Quoted string pos %d, len %d\n", pOut[-2], pOut[-1]);
645 fmt_state &= ~FMT_STATE_OPEN_COPY;
647 /* -------------
648 * Number tokens
649 * -------------
651 else if (*pFormat == '0' && COULD_BE(FMT_TYPE_NUMBER))
653 /* Number formats: Digit from number or '0' if no digits
654 * Other formats: Literal
655 * Types the format if found
657 header->type = FMT_TYPE_NUMBER;
658 NEED_SPACE(2 * sizeof(BYTE));
659 *pOut++ = FMT_NUM_COPY_ZERO;
660 *pOut = 0x0;
661 while (*pFormat == '0')
663 *pOut = *pOut + 1;
664 pFormat++;
666 if (fmt_state & FMT_STATE_WROTE_DECIMAL)
667 num_header->fractional += *pOut;
668 else
669 num_header->whole += *pOut;
670 TRACE("%d 0's\n", *pOut);
671 pOut++;
672 fmt_state &= ~FMT_STATE_OPEN_COPY;
674 else if (*pFormat == '#' && COULD_BE(FMT_TYPE_NUMBER))
676 /* Number formats: Digit from number or blank if no digits
677 * Other formats: Literal
678 * Types the format if found
680 header->type = FMT_TYPE_NUMBER;
681 NEED_SPACE(2 * sizeof(BYTE));
682 *pOut++ = FMT_NUM_COPY_SKIP;
683 *pOut = 0x0;
684 while (*pFormat == '#')
686 *pOut = *pOut + 1;
687 pFormat++;
689 if (fmt_state & FMT_STATE_WROTE_DECIMAL)
690 num_header->fractional += *pOut;
691 else
692 num_header->whole += *pOut;
693 TRACE("%d #'s\n", *pOut);
694 pOut++;
695 fmt_state &= ~FMT_STATE_OPEN_COPY;
697 else if (*pFormat == '.' && COULD_BE(FMT_TYPE_NUMBER) &&
698 !(fmt_state & FMT_STATE_WROTE_DECIMAL))
700 /* Number formats: Decimal separator when 1st seen, literal thereafter
701 * Other formats: Literal
702 * Types the format if found
704 header->type = FMT_TYPE_NUMBER;
705 NEED_SPACE(sizeof(BYTE));
706 *pOut++ = FMT_NUM_DECIMAL;
707 fmt_state |= FMT_STATE_WROTE_DECIMAL;
708 fmt_state &= ~FMT_STATE_OPEN_COPY;
709 pFormat++;
710 TRACE("decimal sep\n");
712 /* FIXME: E+ E- e+ e- => Exponent */
713 /* FIXME: %% => Divide by 1000 */
714 else if (*pFormat == ',' && header->type == FMT_TYPE_NUMBER)
716 /* Number formats: Use the thousands separator
717 * Other formats: Literal
719 num_header->flags |= FMT_FLAG_THOUSANDS;
720 pFormat++;
721 fmt_state &= ~FMT_STATE_OPEN_COPY;
722 TRACE("thousands sep\n");
724 /* -----------
725 * Date tokens
726 * -----------
728 else if (*pFormat == '/' && COULD_BE(FMT_TYPE_DATE))
730 /* Date formats: Date separator
731 * Other formats: Literal
732 * Types the format if found
734 header->type = FMT_TYPE_DATE;
735 NEED_SPACE(sizeof(BYTE));
736 *pOut++ = FMT_DATE_DATE_SEP;
737 pFormat++;
738 fmt_state &= ~FMT_STATE_OPEN_COPY;
739 TRACE("date sep\n");
741 else if (*pFormat == ':' && COULD_BE(FMT_TYPE_DATE))
743 /* Date formats: Time separator
744 * Other formats: Literal
745 * Types the format if found
747 header->type = FMT_TYPE_DATE;
748 NEED_SPACE(sizeof(BYTE));
749 *pOut++ = FMT_DATE_TIME_SEP;
750 pFormat++;
751 fmt_state &= ~FMT_STATE_OPEN_COPY;
752 TRACE("time sep\n");
754 else if ((*pFormat == 'a' || *pFormat == 'A') &&
755 !strncmpiW(pFormat, szAMPM, sizeof(szAMPM)/sizeof(WCHAR)))
757 /* Date formats: System AM/PM designation
758 * Other formats: Literal
759 * Types the format if found
761 header->type = FMT_TYPE_DATE;
762 NEED_SPACE(sizeof(BYTE));
763 pFormat += sizeof(szAMPM)/sizeof(WCHAR);
764 if (!strncmpW(pFormat, szampm, sizeof(szampm)/sizeof(WCHAR)))
765 *pOut++ = FMT_DATE_AMPM_SYS2;
766 else
767 *pOut++ = FMT_DATE_AMPM_SYS1;
768 if (pLastHours)
769 *pLastHours = *pLastHours + 2;
770 TRACE("ampm\n");
772 else if (*pFormat == 'a' && pFormat[1] == '/' &&
773 (pFormat[2] == 'p' || pFormat[2] == 'P'))
775 /* Date formats: lowercase a or p designation
776 * Other formats: Literal
777 * Types the format if found
779 header->type = FMT_TYPE_DATE;
780 NEED_SPACE(sizeof(BYTE));
781 pFormat += 3;
782 *pOut++ = FMT_DATE_A_LOWER;
783 if (pLastHours)
784 *pLastHours = *pLastHours + 2;
785 TRACE("a/p\n");
787 else if (*pFormat == 'A' && pFormat[1] == '/' &&
788 (pFormat[2] == 'p' || pFormat[2] == 'P'))
790 /* Date formats: Uppercase a or p designation
791 * Other formats: Literal
792 * Types the format if found
794 header->type = FMT_TYPE_DATE;
795 NEED_SPACE(sizeof(BYTE));
796 pFormat += 3;
797 *pOut++ = FMT_DATE_A_UPPER;
798 if (pLastHours)
799 *pLastHours = *pLastHours + 2;
800 TRACE("A/P\n");
802 else if (*pFormat == 'a' &&
803 !strncmpW(pFormat, szamSlashpm, sizeof(szamSlashpm)/sizeof(WCHAR)))
805 /* Date formats: lowercase AM or PM designation
806 * Other formats: Literal
807 * Types the format if found
809 header->type = FMT_TYPE_DATE;
810 NEED_SPACE(sizeof(BYTE));
811 pFormat += sizeof(szamSlashpm)/sizeof(WCHAR);
812 *pOut++ = FMT_DATE_AMPM_LOWER;
813 if (pLastHours)
814 *pLastHours = *pLastHours + 2;
815 TRACE("AM/PM\n");
817 else if (*pFormat == 'A' &&
818 !strncmpW(pFormat, szAMSlashPM, sizeof(szAMSlashPM)/sizeof(WCHAR)))
820 /* Date formats: Uppercase AM or PM designation
821 * Other formats: Literal
822 * Types the format if found
824 header->type = FMT_TYPE_DATE;
825 NEED_SPACE(sizeof(BYTE));
826 pFormat += sizeof(szAMSlashPM)/sizeof(WCHAR);
827 *pOut++ = FMT_DATE_AMPM_UPPER;
828 TRACE("AM/PM\n");
830 else if (*pFormat == 'c' || *pFormat == 'C')
832 /* Date formats: General date format
833 * Other formats: Literal
834 * Types the format if found
836 header->type = FMT_TYPE_DATE;
837 NEED_SPACE(sizeof(BYTE));
838 pFormat += sizeof(szAMSlashPM)/sizeof(WCHAR);
839 *pOut++ = FMT_DATE_GENERAL;
840 TRACE("gen date\n");
842 else if ((*pFormat == 'd' || *pFormat == 'D') && COULD_BE(FMT_TYPE_DATE))
844 /* Date formats: Day specifier
845 * Other formats: Literal
846 * Types the format if found
848 int count = -1;
849 header->type = FMT_TYPE_DATE;
850 while ((*pFormat == 'd' || *pFormat == 'D') && count < 6)
852 pFormat++;
853 count++;
855 NEED_SPACE(sizeof(BYTE));
856 *pOut++ = FMT_DATE_DAY + count;
857 fmt_state &= ~FMT_STATE_OPEN_COPY;
858 /* When we find the days token, reset the seen hours state so that
859 * 'mm' is again written as month when encountered.
861 fmt_state &= ~FMT_STATE_SEEN_HOURS;
862 TRACE("%d d's\n", count + 1);
864 else if ((*pFormat == 'h' || *pFormat == 'H') && COULD_BE(FMT_TYPE_DATE))
866 /* Date formats: Hour specifier
867 * Other formats: Literal
868 * Types the format if found
870 header->type = FMT_TYPE_DATE;
871 NEED_SPACE(sizeof(BYTE));
872 pFormat++;
873 /* Record the position of the hours specifier - if we encounter
874 * an am/pm specifier we will change the hours from 24 to 12.
876 pLastHours = pOut;
877 if (*pFormat == 'h' || *pFormat == 'H')
879 pFormat++;
880 *pOut++ = FMT_DATE_HOUR_0;
881 TRACE("hh\n");
883 else
885 *pOut++ = FMT_DATE_HOUR;
886 TRACE("h\n");
888 fmt_state &= ~FMT_STATE_OPEN_COPY;
889 /* Note that now we have seen an hours token, the next occurence of
890 * 'mm' indicates minutes, not months.
892 fmt_state |= FMT_STATE_SEEN_HOURS;
894 else if ((*pFormat == 'm' || *pFormat == 'M') && COULD_BE(FMT_TYPE_DATE))
896 /* Date formats: Month specifier (or Minute specifier, after hour specifier)
897 * Other formats: Literal
898 * Types the format if found
900 int count = -1;
901 header->type = FMT_TYPE_DATE;
902 while ((*pFormat == 'm' || *pFormat == 'M') && count < 4)
904 pFormat++;
905 count++;
907 NEED_SPACE(sizeof(BYTE));
908 if (count <= 1 && fmt_state & FMT_STATE_SEEN_HOURS &&
909 !(fmt_state & FMT_STATE_WROTE_MINUTES))
911 /* We have seen an hours specifier and not yet written a minutes
912 * specifier. Write this as minutes and thereafter as months.
914 *pOut++ = count == 1 ? FMT_DATE_MIN_0 : FMT_DATE_MIN;
915 fmt_state |= FMT_STATE_WROTE_MINUTES; /* Hereafter write months */
917 else
918 *pOut++ = FMT_DATE_MON + count; /* Months */
919 fmt_state &= ~FMT_STATE_OPEN_COPY;
920 TRACE("%d m's\n", count + 1);
922 else if ((*pFormat == 'n' || *pFormat == 'N') && COULD_BE(FMT_TYPE_DATE))
924 /* Date formats: Minute specifier
925 * Other formats: Literal
926 * Types the format if found
928 header->type = FMT_TYPE_DATE;
929 NEED_SPACE(sizeof(BYTE));
930 pFormat++;
931 if (*pFormat == 'n' || *pFormat == 'N')
933 pFormat++;
934 *pOut++ = FMT_DATE_MIN_0;
935 TRACE("nn\n");
937 else
939 *pOut++ = FMT_DATE_MIN;
940 TRACE("n\n");
942 fmt_state &= ~FMT_STATE_OPEN_COPY;
944 else if ((*pFormat == 'q' || *pFormat == 'q') && COULD_BE(FMT_TYPE_DATE))
946 /* Date formats: Quarter specifier
947 * Other formats: Literal
948 * Types the format if found
950 header->type = FMT_TYPE_DATE;
951 NEED_SPACE(sizeof(BYTE));
952 *pOut++ = FMT_DATE_QUARTER;
953 pFormat++;
954 fmt_state &= ~FMT_STATE_OPEN_COPY;
955 TRACE("quarter\n");
957 else if ((*pFormat == 's' || *pFormat == 'S') && COULD_BE(FMT_TYPE_DATE))
959 /* Date formats: Second specifier
960 * Other formats: Literal
961 * Types the format if found
963 header->type = FMT_TYPE_DATE;
964 NEED_SPACE(sizeof(BYTE));
965 pFormat++;
966 if (*pFormat == 's' || *pFormat == 'S')
968 pFormat++;
969 *pOut++ = FMT_DATE_SEC_0;
970 TRACE("ss\n");
972 else
974 *pOut++ = FMT_DATE_SEC;
975 TRACE("s\n");
977 fmt_state &= ~FMT_STATE_OPEN_COPY;
979 else if ((*pFormat == 't' || *pFormat == 'T') &&
980 !strncmpiW(pFormat, szTTTTT, sizeof(szTTTTT)/sizeof(WCHAR)))
982 /* Date formats: System time specifier
983 * Other formats: Literal
984 * Types the format if found
986 header->type = FMT_TYPE_DATE;
987 pFormat += sizeof(szTTTTT)/sizeof(WCHAR);
988 NEED_SPACE(sizeof(BYTE));
989 *pOut++ = FMT_DATE_TIME_SYS;
990 fmt_state &= ~FMT_STATE_OPEN_COPY;
992 else if ((*pFormat == 'w' || *pFormat == 'W') && COULD_BE(FMT_TYPE_DATE))
994 /* Date formats: Week of the year/Day of the week
995 * Other formats: Literal
996 * Types the format if found
998 header->type = FMT_TYPE_DATE;
999 pFormat++;
1000 if (*pFormat == 'w' || *pFormat == 'W')
1002 NEED_SPACE(3 * sizeof(BYTE));
1003 pFormat++;
1004 *pOut++ = FMT_DATE_WEEK_YEAR;
1005 *pOut++ = nFirstDay;
1006 *pOut++ = nFirstWeek;
1007 TRACE("ww\n");
1009 else
1011 NEED_SPACE(2 * sizeof(BYTE));
1012 *pOut++ = FMT_DATE_DAY_WEEK;
1013 *pOut++ = nFirstDay;
1014 TRACE("w\n");
1017 fmt_state &= ~FMT_STATE_OPEN_COPY;
1019 else if ((*pFormat == 'y' || *pFormat == 'Y') && COULD_BE(FMT_TYPE_DATE))
1021 /* Date formats: Day of year/Year specifier
1022 * Other formats: Literal
1023 * Types the format if found
1025 int count = -1;
1026 header->type = FMT_TYPE_DATE;
1027 while ((*pFormat == 'y' || *pFormat == 'Y') && count < 4)
1029 pFormat++;
1030 count++;
1032 if (count == 2)
1034 count--; /* 'yyy' has no meaning, despite what MSDN says */
1035 pFormat--;
1037 NEED_SPACE(sizeof(BYTE));
1038 *pOut++ = FMT_DATE_YEAR_DOY + count;
1039 fmt_state &= ~FMT_STATE_OPEN_COPY;
1040 TRACE("%d y's\n", count + 1);
1042 /* -------------
1043 * String tokens
1044 * -------------
1046 else if (*pFormat == '@' && COULD_BE(FMT_TYPE_STRING))
1048 /* String formats: Character from string or space if no char
1049 * Other formats: Literal
1050 * Types the format if found
1052 header->type = FMT_TYPE_STRING;
1053 NEED_SPACE(2 * sizeof(BYTE));
1054 *pOut++ = FMT_STR_COPY_SPACE;
1055 *pOut = 0x0;
1056 while (*pFormat == '@')
1058 *pOut = *pOut + 1;
1059 str_header->copy_chars++;
1060 pFormat++;
1062 TRACE("%d @'s\n", *pOut);
1063 pOut++;
1064 fmt_state &= ~FMT_STATE_OPEN_COPY;
1066 else if (*pFormat == '&' && COULD_BE(FMT_TYPE_STRING))
1068 /* String formats: Character from string or skip if no char
1069 * Other formats: Literal
1070 * Types the format if found
1072 header->type = FMT_TYPE_STRING;
1073 NEED_SPACE(2 * sizeof(BYTE));
1074 *pOut++ = FMT_STR_COPY_SKIP;
1075 *pOut = 0x0;
1076 while (*pFormat == '&')
1078 *pOut = *pOut + 1;
1079 str_header->copy_chars++;
1080 pFormat++;
1082 TRACE("%d &'s\n", *pOut);
1083 pOut++;
1084 fmt_state &= ~FMT_STATE_OPEN_COPY;
1086 else if ((*pFormat == '<' || *pFormat == '>') && COULD_BE(FMT_TYPE_STRING))
1088 /* String formats: Use upper/lower case
1089 * Other formats: Literal
1090 * Types the format if found
1092 header->type = FMT_TYPE_STRING;
1093 if (*pFormat == '<')
1094 str_header->flags |= FMT_FLAG_LT;
1095 else
1096 str_header->flags |= FMT_FLAG_GT;
1097 TRACE("to %s case\n", *pFormat == '<' ? "lower" : "upper");
1098 pFormat++;
1099 fmt_state &= ~FMT_STATE_OPEN_COPY;
1101 else if (*pFormat == '!' && COULD_BE(FMT_TYPE_STRING))
1103 /* String formats: Copy right to left
1104 * Other formats: Literal
1105 * Types the format if found
1107 header->type = FMT_TYPE_STRING;
1108 str_header->flags |= FMT_FLAG_RTL;
1109 pFormat++;
1110 fmt_state &= ~FMT_STATE_OPEN_COPY;
1111 TRACE("copy right-to-left\n");
1113 /* --------
1114 * Literals
1115 * --------
1117 /* FIXME: [ seems to be ignored */
1118 else
1120 if (*pFormat == '%' && header->type == FMT_TYPE_NUMBER)
1122 /* Number formats: Percentage indicator, also a literal
1123 * Other formats: Literal
1124 * Doesn't type the format
1126 num_header->flags |= FMT_FLAG_PERCENT;
1129 if (fmt_state & FMT_STATE_OPEN_COPY)
1131 pOut[-1] = pOut[-1] + 1; /* Increase the length of the open copy */
1132 TRACE("extend copy (char '%c'), length now %d\n", *pFormat, pOut[-1]);
1134 else
1136 /* Create a new open copy */
1137 TRACE("New copy (char '%c')\n", *pFormat);
1138 NEED_SPACE(3 * sizeof(BYTE));
1139 *pOut++ = FMT_GEN_COPY;
1140 *pOut++ = pFormat - lpszFormat;
1141 *pOut++ = 0x1;
1142 fmt_state |= FMT_STATE_OPEN_COPY;
1144 pFormat++;
1148 *pOut++ = FMT_GEN_END;
1150 header->size = pOut - rgbTok;
1151 if (pcbActual)
1152 *pcbActual = header->size;
1154 return S_OK;
1157 /* Number formatting state flags */
1158 #define NUM_WROTE_DEC 0x01 /* Written the decimal separator */
1160 /* Format a variant using a number format */
1161 static HRESULT VARIANT_FormatNumber(LPVARIANT pVarIn, LPOLESTR lpszFormat,
1162 LPBYTE rgbTok, ULONG dwFlags,
1163 BSTR *pbstrOut, LCID lcid)
1165 BYTE rgbDig[256];
1166 NUMPARSE np;
1167 int wholeNumberDigits, fractionalDigits, divisor10 = 0, multiplier10 = 0;
1168 WCHAR buff[256], *pBuff = buff;
1169 VARIANT vString, vBool;
1170 DWORD dwState = 0;
1171 FMT_HEADER *header = (FMT_HEADER*)rgbTok;
1172 FMT_NUMBER_HEADER *numHeader;
1173 const BYTE* pToken = NULL;
1174 HRESULT hRes = S_OK;
1176 TRACE("(%p->(%s%s),%s,%p,0x%08lx,%p,0x%08lx)\n", pVarIn, debugstr_VT(pVarIn),
1177 debugstr_VF(pVarIn), debugstr_w(lpszFormat), rgbTok, dwFlags, pbstrOut,
1178 lcid);
1180 V_VT(&vString) = VT_EMPTY;
1181 V_VT(&vBool) = VT_BOOL;
1183 if (V_TYPE(pVarIn) == VT_EMPTY || V_TYPE(pVarIn) == VT_NULL)
1185 wholeNumberDigits = fractionalDigits = 0;
1186 numHeader = (FMT_NUMBER_HEADER*)(rgbTok + FmtGetNull(header));
1187 V_BOOL(&vBool) = VARIANT_FALSE;
1189 else
1191 /* Get a number string from pVarIn, and parse it */
1192 hRes = VariantChangeTypeEx(&vString, pVarIn, LCID_US, VARIANT_NOUSEROVERRIDE, VT_BSTR);
1193 if (FAILED(hRes))
1194 return hRes;
1196 np.cDig = sizeof(rgbDig);
1197 np.dwInFlags = NUMPRS_STD;
1198 hRes = VarParseNumFromStr(V_BSTR(&vString), LCID_US, 0, &np, rgbDig);
1199 if (FAILED(hRes))
1200 return hRes;
1202 if (np.nPwr10 < 0)
1204 if (-np.nPwr10 >= np.cDig)
1206 /* A real number < +/- 1.0 e.g. 0.1024 or 0.01024 */
1207 wholeNumberDigits = 0;
1208 fractionalDigits = np.cDig;
1209 divisor10 = -np.nPwr10;
1211 else
1213 /* An exactly represented real number e.g. 1.024 */
1214 wholeNumberDigits = np.cDig + np.nPwr10;
1215 fractionalDigits = np.cDig - wholeNumberDigits;
1216 divisor10 = np.cDig - wholeNumberDigits;
1219 else if (np.nPwr10 == 0)
1221 /* An exactly represented whole number e.g. 1024 */
1222 wholeNumberDigits = np.cDig;
1223 fractionalDigits = 0;
1225 else /* np.nPwr10 > 0 */
1227 /* A whole number followed by nPwr10 0's e.g. 102400 */
1228 wholeNumberDigits = np.cDig;
1229 fractionalDigits = 0;
1230 multiplier10 = np.nPwr10;
1233 /* Figure out which format to use */
1234 if (np.dwOutFlags & NUMPRS_NEG)
1236 numHeader = (FMT_NUMBER_HEADER*)(rgbTok + FmtGetNegative(header));
1237 V_BOOL(&vBool) = VARIANT_TRUE;
1239 else if (wholeNumberDigits == 1 && !fractionalDigits && !multiplier10 &&
1240 !divisor10 && rgbDig[0] == 0)
1242 numHeader = (FMT_NUMBER_HEADER*)(rgbTok + FmtGetZero(header));
1243 V_BOOL(&vBool) = VARIANT_FALSE;
1245 else
1247 numHeader = (FMT_NUMBER_HEADER*)(rgbTok + FmtGetPositive(header));
1248 V_BOOL(&vBool) = VARIANT_TRUE;
1251 TRACE("num header: flags = 0x%x, mult=%d, div=%d, whole=%d, fract=%d\n",
1252 numHeader->flags, numHeader->multiplier, numHeader->divisor,
1253 numHeader->whole, numHeader->fractional);
1255 if (numHeader->flags & FMT_FLAG_PERCENT &&
1256 !(wholeNumberDigits == 1 && !fractionalDigits && !multiplier10 &&
1257 !divisor10 && rgbDig[0] == 0))
1259 /* *100 for %'s. Try to 'steal' fractional digits if we can */
1260 TRACE("Fraction - multiply by 100\n");
1261 if (!fractionalDigits)
1262 multiplier10 += 2;
1263 else
1265 fractionalDigits--;
1266 wholeNumberDigits++;
1267 if (!fractionalDigits)
1268 multiplier10++;
1269 else
1271 fractionalDigits--;
1272 wholeNumberDigits++;
1276 TRACE("cDig %d; nPwr10 %d, whole %d, frac %d ", np.cDig,
1277 np.nPwr10, wholeNumberDigits, fractionalDigits);
1278 TRACE("mult %d; div %d\n", multiplier10, divisor10);
1281 pToken = (const BYTE*)numHeader + sizeof(FMT_NUMBER_HEADER);
1283 while (SUCCEEDED(hRes) && *pToken != FMT_GEN_END)
1285 WCHAR defaultChar = '?';
1286 DWORD boolFlag, localeValue = 0;
1288 if (pToken - rgbTok > header->size)
1290 ERR("Ran off the end of the format!\n");
1291 hRes = E_INVALIDARG;
1292 goto VARIANT_FormatNumber_Exit;
1295 switch (*pToken)
1297 case FMT_GEN_COPY:
1298 TRACE("copy %s\n", debugstr_wn(lpszFormat + pToken[1], pToken[2]));
1299 memcpy(pBuff, lpszFormat + pToken[1], pToken[2] * sizeof(WCHAR));
1300 pBuff += pToken[2];
1301 pToken += 2;
1302 break;
1304 case FMT_GEN_INLINE:
1305 pToken += 2;
1306 TRACE("copy %s\n", debugstr_a(pToken));
1307 while (*pToken)
1308 *pBuff++ = *pToken++;
1309 break;
1311 case FMT_NUM_YES_NO:
1312 boolFlag = VAR_BOOLYESNO;
1313 goto VARIANT_FormatNumber_Bool;
1314 break;
1316 case FMT_NUM_ON_OFF:
1317 boolFlag = VAR_BOOLONOFF;
1318 goto VARIANT_FormatNumber_Bool;
1319 break;
1321 case FMT_NUM_TRUE_FALSE:
1322 boolFlag = VAR_LOCALBOOL;
1324 VARIANT_FormatNumber_Bool:
1326 BSTR boolStr = NULL;
1328 if (pToken[1] != FMT_GEN_END)
1330 ERR("Boolean token not at end of format!\n");
1331 hRes = E_INVALIDARG;
1332 goto VARIANT_FormatNumber_Exit;
1334 hRes = VarBstrFromBool(V_BOOL(&vBool), lcid, boolFlag, &boolStr);
1335 if (SUCCEEDED(hRes))
1337 strcpyW(pBuff, boolStr);
1338 SysFreeString(boolStr);
1339 while (*pBuff)
1340 pBuff++;
1343 break;
1345 case FMT_NUM_DECIMAL:
1346 TRACE("write decimal separator\n");
1347 localeValue = LOCALE_SDECIMAL;
1348 defaultChar = '.';
1349 dwState |= NUM_WROTE_DEC;
1350 break;
1352 case FMT_NUM_CURRENCY:
1353 TRACE("write currency symbol\n");
1354 localeValue = LOCALE_SCURRENCY;
1355 defaultChar = '$';
1356 break;
1358 case FMT_NUM_EXP_POS_U:
1359 case FMT_NUM_EXP_POS_L:
1360 case FMT_NUM_EXP_NEG_U:
1361 case FMT_NUM_EXP_NEG_L:
1362 if (*pToken == FMT_NUM_EXP_POS_L || *pToken == FMT_NUM_EXP_NEG_L)
1363 *pBuff++ = 'e';
1364 else
1365 *pBuff++ = 'E';
1366 if (divisor10)
1368 *pBuff++ = '-';
1369 sprintfW(pBuff, szPercentZeroStar_d, pToken[1], divisor10);
1371 else
1373 if (*pToken == FMT_NUM_EXP_POS_L || *pToken == FMT_NUM_EXP_POS_U)
1374 *pBuff++ = '+';
1375 sprintfW(pBuff, szPercentZeroStar_d, pToken[1], multiplier10);
1377 while (*pBuff)
1378 pBuff++;
1379 pToken++;
1380 break;
1382 case FMT_NUM_COPY_SKIP:
1383 if (dwState & NUM_WROTE_DEC)
1385 int count;
1387 TRACE("write %d fractional digits or skip\n", pToken[1]);
1389 for (count = 0; count < fractionalDigits; count++)
1390 pBuff[count] = rgbDig[wholeNumberDigits + count];
1391 pBuff += fractionalDigits;
1393 else
1395 int count;
1397 TRACE("write %d digits or skip\n", pToken[1]);
1399 if (wholeNumberDigits > 1 || rgbDig[0] > 0)
1401 TRACE("write %d whole number digits\n", wholeNumberDigits);
1402 for (count = 0; count < wholeNumberDigits; count++)
1403 *pBuff++ = '0' + rgbDig[count];
1404 TRACE("write %d whole trailing 0's\n", multiplier10);
1405 for (count = 0; count < multiplier10; count++)
1406 *pBuff++ = '0'; /* Write trailing zeros for multiplied values */
1409 pToken++;
1410 break;
1412 case FMT_NUM_COPY_ZERO:
1413 if (dwState & NUM_WROTE_DEC)
1415 int count;
1417 TRACE("write %d fractional digits or 0's\n", pToken[1]);
1419 for (count = 0; count < fractionalDigits; count++)
1420 pBuff[count] = rgbDig[wholeNumberDigits + count];
1421 pBuff += fractionalDigits;
1422 if (pToken[1] > fractionalDigits)
1424 count = pToken[1] - fractionalDigits;
1425 while (count--)
1426 *pBuff++ = '0'; /* Write trailing zeros for missing digits */
1429 else
1431 int count;
1433 TRACE("write %d digits or 0's\n", pToken[1]);
1435 if (pToken[1] > (wholeNumberDigits + multiplier10))
1437 count = pToken[1] - (wholeNumberDigits + multiplier10);
1438 TRACE("write %d leading zeros\n", count);
1439 while(count--)
1440 *pBuff++ = '0'; /* Write leading zeros for missing digits */
1442 TRACE("write %d whole number digits\n", wholeNumberDigits);
1443 for (count = 0; count < wholeNumberDigits; count++)
1444 *pBuff++ = '0' + rgbDig[count];
1445 TRACE("write %d whole trailing 0's\n", multiplier10);
1446 for (count = 0; count < multiplier10; count++)
1447 *pBuff++ = '0'; /* Write trailing zeros for multiplied values */
1449 pToken++;
1450 break;
1452 default:
1453 ERR("Unknown token 0x%02x!\n", *pToken);
1454 hRes = E_INVALIDARG;
1455 goto VARIANT_FormatNumber_Exit;
1456 break;
1458 if (localeValue)
1460 if (GetLocaleInfoW(lcid, localeValue, pBuff,
1461 sizeof(buff)/sizeof(WCHAR)-(pBuff-buff)))
1463 TRACE("added %s\n", debugstr_w(pBuff));
1464 while (*pBuff)
1465 pBuff++;
1467 else
1469 TRACE("added %d '%c'\n", defaultChar, defaultChar);
1470 *pBuff++ = defaultChar;
1473 pToken++;
1476 VARIANT_FormatNumber_Exit:
1477 VariantClear(&vString);
1478 *pBuff = '\0';
1479 TRACE("buff is %s\n", debugstr_w(buff));
1480 if (SUCCEEDED(hRes))
1482 *pbstrOut = SysAllocString(buff);
1483 if (!*pbstrOut)
1484 hRes = E_OUTOFMEMORY;
1486 return hRes;
1489 /* Format a variant using a date format */
1490 static HRESULT VARIANT_FormatDate(LPVARIANT pVarIn, LPOLESTR lpszFormat,
1491 LPBYTE rgbTok, ULONG dwFlags,
1492 BSTR *pbstrOut, LCID lcid)
1494 WCHAR buff[256], *pBuff = buff;
1495 VARIANT vDate;
1496 UDATE udate;
1497 FMT_HEADER *header = (FMT_HEADER*)rgbTok;
1498 FMT_DATE_HEADER *dateHeader;
1499 const BYTE* pToken = NULL;
1500 HRESULT hRes;
1502 TRACE("(%p->(%s%s),%s,%p,0x%08lx,%p,0x%08lx)\n", pVarIn, debugstr_VT(pVarIn),
1503 debugstr_VF(pVarIn), debugstr_w(lpszFormat), rgbTok, dwFlags, pbstrOut,
1504 lcid);
1506 V_VT(&vDate) = VT_EMPTY;
1508 if (V_TYPE(pVarIn) == VT_EMPTY || V_TYPE(pVarIn) == VT_NULL)
1510 dateHeader = (FMT_DATE_HEADER*)(rgbTok + FmtGetNegative(header));
1511 V_DATE(&vDate) = 0;
1513 else
1515 USHORT usFlags = dwFlags & VARIANT_CALENDAR_HIJRI ? VAR_CALENDAR_HIJRI : 0;
1517 hRes = VariantChangeTypeEx(&vDate, pVarIn, LCID_US, usFlags, VT_DATE);
1518 if (FAILED(hRes))
1519 return hRes;
1520 dateHeader = (FMT_DATE_HEADER*)(rgbTok + FmtGetPositive(header));
1523 hRes = VarUdateFromDate(V_DATE(&vDate), 0 /* FIXME: flags? */, &udate);
1524 if (FAILED(hRes))
1525 return hRes;
1526 pToken = (const BYTE*)dateHeader + sizeof(FMT_DATE_HEADER);
1528 while (*pToken != FMT_GEN_END)
1530 DWORD dwVal = 0, localeValue = 0, dwFmt = 0;
1531 LPCWSTR szPrintFmt = NULL;
1532 WCHAR defaultChar = '?';
1534 if (pToken - rgbTok > header->size)
1536 ERR("Ran off the end of the format!\n");
1537 hRes = E_INVALIDARG;
1538 goto VARIANT_FormatDate_Exit;
1541 switch (*pToken)
1543 case FMT_GEN_COPY:
1544 TRACE("copy %s\n", debugstr_wn(lpszFormat + pToken[1], pToken[2]));
1545 memcpy(pBuff, lpszFormat + pToken[1], pToken[2] * sizeof(WCHAR));
1546 pBuff += pToken[2];
1547 pToken += 2;
1548 break;
1550 case FMT_DATE_TIME_SEP:
1551 TRACE("time separator\n");
1552 localeValue = LOCALE_STIME;
1553 defaultChar = ':';
1554 break;
1556 case FMT_DATE_DATE_SEP:
1557 TRACE("date separator\n");
1558 localeValue = LOCALE_SDATE;
1559 defaultChar = '/';
1560 break;
1562 case FMT_DATE_GENERAL:
1564 BSTR date = NULL;
1565 WCHAR *pDate = date;
1566 hRes = VarBstrFromDate(V_DATE(&vDate), lcid, 0, pbstrOut);
1567 if (FAILED(hRes))
1568 goto VARIANT_FormatDate_Exit;
1569 while (*pDate)
1570 *pBuff++ = *pDate++;
1571 SysFreeString(date);
1573 break;
1575 case FMT_DATE_QUARTER:
1576 if (udate.st.wMonth <= 3)
1577 *pBuff++ = '1';
1578 else if (udate.st.wMonth <= 6)
1579 *pBuff++ = '2';
1580 else if (udate.st.wMonth <= 9)
1581 *pBuff++ = '3';
1582 else
1583 *pBuff++ = '4';
1584 break;
1586 case FMT_DATE_TIME_SYS:
1588 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
1589 BSTR date = NULL;
1590 WCHAR *pDate = date;
1591 hRes = VarBstrFromDate(V_DATE(&vDate), lcid, VAR_TIMEVALUEONLY, pbstrOut);
1592 if (FAILED(hRes))
1593 goto VARIANT_FormatDate_Exit;
1594 while (*pDate)
1595 *pBuff++ = *pDate++;
1596 SysFreeString(date);
1598 break;
1600 case FMT_DATE_DAY:
1601 szPrintFmt = szPercent_d;
1602 dwVal = udate.st.wDay;
1603 break;
1605 case FMT_DATE_DAY_0:
1606 szPrintFmt = szPercentZeroTwo_d;
1607 dwVal = udate.st.wDay;
1608 break;
1610 case FMT_DATE_DAY_SHORT:
1611 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
1612 TRACE("short day\n");
1613 localeValue = LOCALE_SABBREVDAYNAME1 + udate.st.wMonth - 1;
1614 defaultChar = '?';
1615 break;
1617 case FMT_DATE_DAY_LONG:
1618 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
1619 TRACE("long day\n");
1620 localeValue = LOCALE_SDAYNAME1 + udate.st.wMonth - 1;
1621 defaultChar = '?';
1622 break;
1624 case FMT_DATE_SHORT:
1625 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
1626 dwFmt = LOCALE_SSHORTDATE;
1627 break;
1629 case FMT_DATE_LONG:
1630 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
1631 dwFmt = LOCALE_SLONGDATE;
1632 break;
1634 case FMT_DATE_MEDIUM:
1635 FIXME("Medium date treated as long date\n");
1636 dwFmt = LOCALE_SLONGDATE;
1637 break;
1639 case FMT_DATE_DAY_WEEK:
1640 szPrintFmt = szPercent_d;
1641 if (pToken[1])
1642 dwVal = udate.st.wDayOfWeek + 2 - pToken[1];
1643 else
1645 GetLocaleInfoW(lcid,LOCALE_RETURN_NUMBER|LOCALE_IFIRSTDAYOFWEEK,
1646 (LPWSTR)&dwVal, sizeof(dwVal)/sizeof(WCHAR));
1647 dwVal = udate.st.wDayOfWeek + 1 - dwVal;
1649 pToken++;
1650 break;
1652 case FMT_DATE_WEEK_YEAR:
1653 szPrintFmt = szPercent_d;
1654 dwVal = udate.wDayOfYear / 7 + 1;
1655 pToken += 2;
1656 FIXME("Ignoring nFirstDay of %d, nFirstWeek of %d\n", pToken[0], pToken[1]);
1657 break;
1659 case FMT_DATE_MON:
1660 szPrintFmt = szPercent_d;
1661 dwVal = udate.st.wMonth;
1662 break;
1664 case FMT_DATE_MON_0:
1665 szPrintFmt = szPercentZeroTwo_d;
1666 dwVal = udate.st.wMonth;
1667 break;
1669 case FMT_DATE_MON_SHORT:
1670 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
1671 TRACE("short month\n");
1672 localeValue = LOCALE_SABBREVMONTHNAME1 + udate.st.wMonth - 1;
1673 defaultChar = '?';
1674 break;
1676 case FMT_DATE_MON_LONG:
1677 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */
1678 TRACE("long month\n");
1679 localeValue = LOCALE_SMONTHNAME1 + udate.st.wMonth - 1;
1680 defaultChar = '?';
1681 break;
1683 case FMT_DATE_YEAR_DOY:
1684 szPrintFmt = szPercent_d;
1685 dwVal = udate.wDayOfYear;
1686 break;
1688 case FMT_DATE_YEAR_0:
1689 szPrintFmt = szPercentZeroTwo_d;
1690 dwVal = udate.st.wYear % 100;
1691 break;
1693 case FMT_DATE_YEAR_LONG:
1694 szPrintFmt = szPercent_d;
1695 dwVal = udate.st.wYear;
1696 break;
1698 case FMT_DATE_MIN:
1699 szPrintFmt = szPercent_d;
1700 dwVal = udate.st.wMinute;
1701 break;
1703 case FMT_DATE_MIN_0:
1704 szPrintFmt = szPercentZeroTwo_d;
1705 dwVal = udate.st.wMinute;
1706 break;
1708 case FMT_DATE_SEC:
1709 szPrintFmt = szPercent_d;
1710 dwVal = udate.st.wSecond;
1711 break;
1713 case FMT_DATE_SEC_0:
1714 szPrintFmt = szPercentZeroTwo_d;
1715 dwVal = udate.st.wSecond;
1716 break;
1718 case FMT_DATE_HOUR:
1719 szPrintFmt = szPercent_d;
1720 dwVal = udate.st.wHour;
1721 break;
1723 case FMT_DATE_HOUR_0:
1724 szPrintFmt = szPercentZeroTwo_d;
1725 dwVal = udate.st.wHour;
1726 break;
1728 case FMT_DATE_HOUR_12:
1729 szPrintFmt = szPercent_d;
1730 dwVal = udate.st.wHour ? udate.st.wHour > 12 ? udate.st.wHour - 12 : udate.st.wHour : 12;
1731 break;
1733 case FMT_DATE_HOUR_12_0:
1734 szPrintFmt = szPercentZeroTwo_d;
1735 dwVal = udate.st.wHour ? udate.st.wHour > 12 ? udate.st.wHour - 12 : udate.st.wHour : 12;
1736 break;
1738 case FMT_DATE_AMPM_SYS1:
1739 case FMT_DATE_AMPM_SYS2:
1740 localeValue = udate.st.wHour < 12 ? LOCALE_S1159 : LOCALE_S2359;
1741 defaultChar = '?';
1742 break;
1744 case FMT_DATE_AMPM_UPPER:
1745 *pBuff++ = udate.st.wHour < 12 ? 'A' : 'P';
1746 *pBuff++ = 'M';
1747 break;
1749 case FMT_DATE_A_UPPER:
1750 *pBuff++ = udate.st.wHour < 12 ? 'A' : 'P';
1751 break;
1753 case FMT_DATE_AMPM_LOWER:
1754 *pBuff++ = udate.st.wHour < 12 ? 'a' : 'p';
1755 *pBuff++ = 'm';
1756 break;
1758 case FMT_DATE_A_LOWER:
1759 *pBuff++ = udate.st.wHour < 12 ? 'a' : 'p';
1760 break;
1762 default:
1763 ERR("Unknown token 0x%02x!\n", *pToken);
1764 hRes = E_INVALIDARG;
1765 goto VARIANT_FormatDate_Exit;
1766 break;
1768 if (localeValue)
1770 *pBuff = '\0';
1771 if (GetLocaleInfoW(lcid, localeValue, pBuff,
1772 sizeof(buff)/sizeof(WCHAR)-(pBuff-buff)))
1774 TRACE("added %s\n", debugstr_w(pBuff));
1775 while (*pBuff)
1776 pBuff++;
1778 else
1780 TRACE("added %d %c\n", defaultChar, defaultChar);
1781 *pBuff++ = defaultChar;
1784 else if (dwFmt)
1786 WCHAR fmt_buff[80];
1788 if (!GetLocaleInfoW(lcid, dwFmt, fmt_buff, sizeof(fmt_buff)/sizeof(WCHAR)) ||
1789 !GetDateFormatW(lcid, 0, &udate.st, fmt_buff, pBuff,
1790 sizeof(buff)/sizeof(WCHAR)-(pBuff-buff)))
1792 hRes = E_INVALIDARG;
1793 goto VARIANT_FormatDate_Exit;
1795 while (*pBuff)
1796 pBuff++;
1798 else if (szPrintFmt)
1800 sprintfW(pBuff, szPrintFmt, dwVal);
1801 while (*pBuff)
1802 pBuff++;
1804 pToken++;
1807 VARIANT_FormatDate_Exit:
1808 *pBuff = '\0';
1809 TRACE("buff is %s\n", debugstr_w(buff));
1810 if (SUCCEEDED(hRes))
1812 *pbstrOut = SysAllocString(buff);
1813 if (!*pbstrOut)
1814 hRes = E_OUTOFMEMORY;
1816 return hRes;
1819 /* Format a variant using a string format */
1820 static HRESULT VARIANT_FormatString(LPVARIANT pVarIn, LPOLESTR lpszFormat,
1821 LPBYTE rgbTok, ULONG dwFlags,
1822 BSTR *pbstrOut, LCID lcid)
1824 static const WCHAR szEmpty[] = { '\0' };
1825 WCHAR buff[256], *pBuff = buff;
1826 WCHAR *pSrc;
1827 FMT_HEADER *header = (FMT_HEADER*)rgbTok;
1828 FMT_STRING_HEADER *strHeader;
1829 const BYTE* pToken = NULL;
1830 VARIANT vStr;
1831 int blanks_first;
1832 BOOL bUpper = FALSE;
1833 HRESULT hRes = S_OK;
1835 TRACE("(%p->(%s%s),%s,%p,0x%08lx,%p,0x%08lx)\n", pVarIn, debugstr_VT(pVarIn),
1836 debugstr_VF(pVarIn), debugstr_w(lpszFormat), rgbTok, dwFlags, pbstrOut,
1837 lcid);
1839 V_VT(&vStr) = VT_EMPTY;
1841 if (V_TYPE(pVarIn) == VT_EMPTY || V_TYPE(pVarIn) == VT_NULL)
1843 strHeader = (FMT_STRING_HEADER*)(rgbTok + FmtGetNegative(header));
1844 V_BSTR(&vStr) = (WCHAR*)szEmpty;
1846 else
1848 hRes = VariantChangeTypeEx(&vStr, pVarIn, LCID_US, VARIANT_NOUSEROVERRIDE, VT_BSTR);
1849 if (FAILED(hRes))
1850 return hRes;
1852 if (V_BSTR(pVarIn)[0] == '\0')
1853 strHeader = (FMT_STRING_HEADER*)(rgbTok + FmtGetNegative(header));
1854 else
1855 strHeader = (FMT_STRING_HEADER*)(rgbTok + FmtGetPositive(header));
1857 pSrc = V_BSTR(&vStr);
1858 if ((strHeader->flags & (FMT_FLAG_LT|FMT_FLAG_GT)) == FMT_FLAG_GT)
1859 bUpper = TRUE;
1860 blanks_first = strHeader->copy_chars - strlenW(pSrc);
1861 pToken = (const BYTE*)strHeader + sizeof(FMT_DATE_HEADER);
1863 while (*pToken != FMT_GEN_END)
1865 int dwCount = 0;
1867 if (pToken - rgbTok > header->size)
1869 ERR("Ran off the end of the format!\n");
1870 hRes = E_INVALIDARG;
1871 goto VARIANT_FormatString_Exit;
1874 switch (*pToken)
1876 case FMT_GEN_COPY:
1877 TRACE("copy %s\n", debugstr_wn(lpszFormat + pToken[1], pToken[2]));
1878 memcpy(pBuff, lpszFormat + pToken[1], pToken[2] * sizeof(WCHAR));
1879 pBuff += pToken[2];
1880 pToken += 2;
1881 break;
1883 case FMT_STR_COPY_SPACE:
1884 case FMT_STR_COPY_SKIP:
1885 dwCount = pToken[1];
1886 if (*pToken == FMT_STR_COPY_SPACE && blanks_first > 0)
1888 TRACE("insert %d initial spaces\n", blanks_first);
1889 while (dwCount > 0 && blanks_first > 0)
1891 *pBuff++ = ' ';
1892 dwCount--;
1893 blanks_first--;
1896 TRACE("copy %d chars%s\n", dwCount,
1897 *pToken == FMT_STR_COPY_SPACE ? " with space" :"");
1898 while (dwCount > 0 && *pSrc)
1900 if (bUpper)
1901 *pBuff++ = toupperW(*pSrc);
1902 else
1903 *pBuff++ = tolowerW(*pSrc);
1904 dwCount--;
1905 pSrc++;
1907 if (*pToken == FMT_STR_COPY_SPACE && dwCount > 0)
1909 TRACE("insert %d spaces\n", dwCount);
1910 while (dwCount-- > 0)
1911 *pBuff++ = ' ';
1913 pToken++;
1914 break;
1916 default:
1917 ERR("Unknown token 0x%02x!\n", *pToken);
1918 hRes = E_INVALIDARG;
1919 goto VARIANT_FormatString_Exit;
1920 break;
1922 pToken++;
1925 VARIANT_FormatString_Exit:
1926 /* Copy out any remaining chars */
1927 while (*pSrc)
1929 if (bUpper)
1930 *pBuff++ = toupperW(*pSrc);
1931 else
1932 *pBuff++ = tolowerW(*pSrc);
1933 pSrc++;
1935 VariantClear(&vStr);
1936 *pBuff = '\0';
1937 TRACE("buff is %s\n", debugstr_w(buff));
1938 if (SUCCEEDED(hRes))
1940 *pbstrOut = SysAllocString(buff);
1941 if (!*pbstrOut)
1942 hRes = E_OUTOFMEMORY;
1944 return hRes;
1947 #define NUMBER_VTBITS (VTBIT_I1|VTBIT_UI1|VTBIT_I2|VTBIT_UI2| \
1948 VTBIT_I4|VTBIT_UI4|VTBIT_I8|VTBIT_UI8| \
1949 VTBIT_R4|VTBIT_R8|VTBIT_CY|VTBIT_DECIMAL| \
1950 (1<<VT_BOOL)|(1<<VT_INT)|(1<<VT_UINT))
1952 /**********************************************************************
1953 * VarFormatFromTokens [OLEAUT32.139]
1955 HRESULT WINAPI VarFormatFromTokens(LPVARIANT pVarIn, LPOLESTR lpszFormat,
1956 LPBYTE rgbTok, ULONG dwFlags,
1957 BSTR *pbstrOut, LCID lcid)
1959 FMT_SHORT_HEADER *header = (FMT_SHORT_HEADER *)rgbTok;
1960 VARIANT vTmp;
1961 HRESULT hres;
1963 TRACE("(%p,%s,%p,%lx,%p,0x%08lx)\n", pVarIn, debugstr_w(lpszFormat),
1964 rgbTok, dwFlags, pbstrOut, lcid);
1966 if (!pbstrOut)
1967 return E_INVALIDARG;
1969 *pbstrOut = NULL;
1971 if (!pVarIn || !rgbTok)
1972 return E_INVALIDARG;
1974 if (*rgbTok == FMT_TO_STRING || header->type == FMT_TYPE_GENERAL)
1976 /* According to MSDN, general format acts somewhat like the 'Str'
1977 * function in Visual Basic.
1979 VarFormatFromTokens_AsStr:
1980 V_VT(&vTmp) = VT_EMPTY;
1981 hres = VariantChangeTypeEx(&vTmp, pVarIn, lcid, dwFlags, VT_BSTR);
1982 *pbstrOut = V_BSTR(&vTmp);
1984 else
1986 if (header->type == FMT_TYPE_NUMBER ||
1987 (header->type == FMT_TYPE_UNKNOWN && ((1 << V_TYPE(pVarIn)) & NUMBER_VTBITS)))
1989 hres = VARIANT_FormatNumber(pVarIn, lpszFormat, rgbTok, dwFlags, pbstrOut, lcid);
1991 else if (header->type == FMT_TYPE_DATE ||
1992 (header->type == FMT_TYPE_UNKNOWN && V_TYPE(pVarIn) == VT_DATE))
1994 hres = VARIANT_FormatDate(pVarIn, lpszFormat, rgbTok, dwFlags, pbstrOut, lcid);
1996 else if (header->type == FMT_TYPE_STRING || V_TYPE(pVarIn) == VT_BSTR)
1998 hres = VARIANT_FormatString(pVarIn, lpszFormat, rgbTok, dwFlags, pbstrOut, lcid);
2000 else
2002 ERR("unrecognised format type 0x%02x\n", header->type);
2003 return E_INVALIDARG;
2005 /* If the coercion failed, still try to create output, unless the
2006 * VAR_FORMAT_NOSUBSTITUTE flag is set.
2008 if ((hres == DISP_E_OVERFLOW || hres == DISP_E_TYPEMISMATCH) &&
2009 !(dwFlags & VAR_FORMAT_NOSUBSTITUTE))
2010 goto VarFormatFromTokens_AsStr;
2013 return hres;
2016 /**********************************************************************
2017 * VarFormat [OLEAUT32.87]
2019 * Format a variant from a format string.
2021 * PARAMS
2022 * pVarIn [I] Variant to format
2023 * lpszFormat [I] Format string (see notes)
2024 * nFirstDay [I] First day of the week, (See VarTokenizeFormatString() for details)
2025 * nFirstWeek [I] First week of the year (See VarTokenizeFormatString() for details)
2026 * dwFlags [I] Flags for the format (VAR_ flags from "oleauto.h")
2027 * pbstrOut [O] Destination for formatted string.
2029 * RETURNS
2030 * Success: S_OK. pbstrOut contains the formatted value.
2031 * Failure: E_INVALIDARG, if any parameter is invalid.
2032 * E_OUTOFMEMORY, if enough memory cannot be allocated.
2033 * DISP_E_TYPEMISMATCH, if the variant cannot be formatted.
2035 * NOTES
2036 * - See Variant-Formats for details concerning creating format strings.
2037 * - This function uses LOCALE_USER_DEFAULT when calling VarTokenizeFormatString()
2038 * and VarFormatFromTokens().
2040 HRESULT WINAPI VarFormat(LPVARIANT pVarIn, LPOLESTR lpszFormat,
2041 int nFirstDay, int nFirstWeek, ULONG dwFlags,
2042 BSTR *pbstrOut)
2044 BYTE buff[256];
2045 HRESULT hres;
2047 TRACE("(%p->(%s%s),%s,%d,%d,0x%08lx,%p)\n", pVarIn, debugstr_VT(pVarIn),
2048 debugstr_VF(pVarIn), debugstr_w(lpszFormat), nFirstDay, nFirstWeek,
2049 dwFlags, pbstrOut);
2051 if (!pbstrOut)
2052 return E_INVALIDARG;
2053 *pbstrOut = NULL;
2055 hres = VarTokenizeFormatString(lpszFormat, buff, sizeof(buff), nFirstDay,
2056 nFirstWeek, LOCALE_USER_DEFAULT, NULL);
2057 if (SUCCEEDED(hres))
2058 hres = VarFormatFromTokens(pVarIn, lpszFormat, buff, dwFlags,
2059 pbstrOut, LOCALE_USER_DEFAULT);
2060 TRACE("returning 0x%08lx, %s\n", hres, debugstr_w(*pbstrOut));
2061 return hres;
2064 /**********************************************************************
2065 * VarFormatDateTime [OLEAUT32.97]
2067 * Format a variant value as a date and/or time.
2069 * PARAMS
2070 * pVarIn [I] Variant to format
2071 * nFormat [I] Format type (see notes)
2072 * dwFlags [I] Flags for the format (VAR_ flags from "oleauto.h")
2073 * pbstrOut [O] Destination for formatted string.
2075 * RETURNS
2076 * Success: S_OK. pbstrOut contains the formatted value.
2077 * Failure: E_INVALIDARG, if any parameter is invalid.
2078 * E_OUTOFMEMORY, if enough memory cannot be allocated.
2079 * DISP_E_TYPEMISMATCH, if the variant cannot be formatted.
2081 * NOTES
2082 * This function uses LOCALE_USER_DEFAULT when determining the date format
2083 * characters to use.
2084 * Possible values for the nFormat parameter are:
2085 *| Value Meaning
2086 *| ----- -------
2087 *| 0 General date format
2088 *| 1 Long date format
2089 *| 2 Short date format
2090 *| 3 Long time format
2091 *| 4 Short time format
2093 HRESULT WINAPI VarFormatDateTime(LPVARIANT pVarIn, INT nFormat, ULONG dwFlags, BSTR *pbstrOut)
2095 static const WCHAR szEmpty[] = { '\0' };
2096 const BYTE* lpFmt = NULL;
2098 TRACE("(%p->(%s%s),%d,0x%08lx,%p)\n", pVarIn, debugstr_VT(pVarIn),
2099 debugstr_VF(pVarIn), nFormat, dwFlags, pbstrOut);
2101 if (!pVarIn || !pbstrOut || nFormat < 0 || nFormat > 4)
2102 return E_INVALIDARG;
2104 switch (nFormat)
2106 case 0: lpFmt = fmtGeneralDate; break;
2107 case 1: lpFmt = fmtLongDate; break;
2108 case 2: lpFmt = fmtShortDate; break;
2109 case 3: lpFmt = fmtLongTime; break;
2110 case 4: lpFmt = fmtShortTime; break;
2112 return VarFormatFromTokens(pVarIn, (LPWSTR)szEmpty, (BYTE*)lpFmt, dwFlags,
2113 pbstrOut, LOCALE_USER_DEFAULT);
2116 #define GETLOCALENUMBER(type,field) GetLocaleInfoW(LOCALE_USER_DEFAULT, \
2117 type|LOCALE_RETURN_NUMBER, \
2118 (LPWSTR)&numfmt.field, \
2119 sizeof(numfmt.field)/sizeof(WCHAR))
2121 /**********************************************************************
2122 * VarFormatNumber [OLEAUT32.107]
2124 * Format a variant value as a number.
2126 * PARAMS
2127 * pVarIn [I] Variant to format
2128 * nDigits [I] Number of digits following the decimal point (-1 = user default)
2129 * nLeading [I] Use a leading zero (-2 = user default, -1 = yes, 0 = no)
2130 * nParens [I] Use brackets for values < 0 (-2 = user default, -1 = yes, 0 = no)
2131 * nGrouping [I] Use grouping characters (-2 = user default, -1 = yes, 0 = no)
2132 * dwFlags [I] Currently unused, set to zero
2133 * pbstrOut [O] Destination for formatted string.
2135 * RETURNS
2136 * Success: S_OK. pbstrOut contains the formatted value.
2137 * Failure: E_INVALIDARG, if any parameter is invalid.
2138 * E_OUTOFMEMORY, if enough memory cannot be allocated.
2139 * DISP_E_TYPEMISMATCH, if the variant cannot be formatted.
2141 * NOTES
2142 * This function uses LOCALE_USER_DEFAULT when determining the number format
2143 * characters to use.
2145 HRESULT WINAPI VarFormatNumber(LPVARIANT pVarIn, INT nDigits, INT nLeading, INT nParens,
2146 INT nGrouping, ULONG dwFlags, BSTR *pbstrOut)
2148 HRESULT hRet;
2149 VARIANT vStr;
2151 TRACE("(%p->(%s%s),%d,%d,%d,%d,0x%08lx,%p)\n", pVarIn, debugstr_VT(pVarIn),
2152 debugstr_VF(pVarIn), nDigits, nLeading, nParens, nGrouping, dwFlags, pbstrOut);
2154 if (!pVarIn || !pbstrOut || nDigits > 9)
2155 return E_INVALIDARG;
2157 *pbstrOut = NULL;
2159 V_VT(&vStr) = VT_EMPTY;
2160 hRet = VariantCopyInd(&vStr, pVarIn);
2162 if (SUCCEEDED(hRet))
2163 hRet = VariantChangeTypeEx(&vStr, &vStr, LOCALE_USER_DEFAULT, 0, VT_BSTR);
2165 if (SUCCEEDED(hRet))
2167 WCHAR buff[256], decimal[8], thousands[8];
2168 NUMBERFMTW numfmt;
2170 /* Although MSDN makes it clear that the native versions of these functions
2171 * are implemented using VarTokenizeFormatString()/VarFormatFromTokens(),
2172 * using NLS gives us the same result.
2174 if (nDigits < 0)
2175 GETLOCALENUMBER(LOCALE_IDIGITS, NumDigits);
2176 else
2177 numfmt.NumDigits = nDigits;
2179 if (nLeading == -2)
2180 GETLOCALENUMBER(LOCALE_ILZERO, LeadingZero);
2181 else if (nLeading == -1)
2182 numfmt.LeadingZero = 1;
2183 else
2184 numfmt.LeadingZero = 0;
2186 if (nGrouping == -2)
2188 WCHAR grouping[16];
2189 grouping[2] = '\0';
2190 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, grouping,
2191 sizeof(grouping)/sizeof(WCHAR));
2192 numfmt.Grouping = grouping[2] == '2' ? 32 : grouping[0] - '0';
2194 else if (nGrouping == -1)
2195 numfmt.Grouping = 3; /* 3 = "n,nnn.nn" */
2196 else
2197 numfmt.Grouping = 0; /* 0 = No grouping */
2199 if (nParens == -2)
2200 GETLOCALENUMBER(LOCALE_INEGNUMBER, NegativeOrder);
2201 else if (nParens == -1)
2202 numfmt.NegativeOrder = 0; /* 0 = "(xxx)" */
2203 else
2204 numfmt.NegativeOrder = 1; /* 1 = "-xxx" */
2206 numfmt.lpDecimalSep = decimal;
2207 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, decimal,
2208 sizeof(decimal)/sizeof(WCHAR));
2209 numfmt.lpThousandSep = thousands;
2210 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, thousands,
2211 sizeof(thousands)/sizeof(WCHAR));
2213 if (GetNumberFormatW(LOCALE_USER_DEFAULT, 0, V_BSTR(&vStr), &numfmt,
2214 buff, sizeof(buff)/sizeof(WCHAR)))
2216 *pbstrOut = SysAllocString(buff);
2217 if (!*pbstrOut)
2218 hRet = E_OUTOFMEMORY;
2220 else
2221 hRet = DISP_E_TYPEMISMATCH;
2223 SysFreeString(V_BSTR(&vStr));
2225 return hRet;
2228 /**********************************************************************
2229 * VarFormatPercent [OLEAUT32.117]
2231 * Format a variant value as a percentage.
2233 * PARAMS
2234 * pVarIn [I] Variant to format
2235 * nDigits [I] Number of digits following the decimal point (-1 = user default)
2236 * nLeading [I] Use a leading zero (-2 = user default, -1 = yes, 0 = no)
2237 * nParens [I] Use brackets for values < 0 (-2 = user default, -1 = yes, 0 = no)
2238 * nGrouping [I] Use grouping characters (-2 = user default, -1 = yes, 0 = no)
2239 * dwFlags [I] Currently unused, set to zero
2240 * pbstrOut [O] Destination for formatted string.
2242 * RETURNS
2243 * Success: S_OK. pbstrOut contains the formatted value.
2244 * Failure: E_INVALIDARG, if any parameter is invalid.
2245 * E_OUTOFMEMORY, if enough memory cannot be allocated.
2246 * DISP_E_OVERFLOW, if overflow occurs during the conversion.
2247 * DISP_E_TYPEMISMATCH, if the variant cannot be formatted.
2249 * NOTES
2250 * This function uses LOCALE_USER_DEFAULT when determining the number format
2251 * characters to use.
2253 HRESULT WINAPI VarFormatPercent(LPVARIANT pVarIn, INT nDigits, INT nLeading, INT nParens,
2254 INT nGrouping, ULONG dwFlags, BSTR *pbstrOut)
2256 static const WCHAR szPercent[] = { '%','\0' };
2257 static const WCHAR szPercentBracket[] = { '%',')','\0' };
2258 WCHAR buff[256];
2259 HRESULT hRet;
2260 VARIANT vDbl;
2262 TRACE("(%p->(%s%s),%d,%d,%d,%d,0x%08lx,%p)\n", pVarIn, debugstr_VT(pVarIn),
2263 debugstr_VF(pVarIn), nDigits, nLeading, nParens, nGrouping,
2264 dwFlags, pbstrOut);
2266 if (!pVarIn || !pbstrOut || nDigits > 9)
2267 return E_INVALIDARG;
2269 *pbstrOut = NULL;
2271 V_VT(&vDbl) = VT_EMPTY;
2272 hRet = VariantCopyInd(&vDbl, pVarIn);
2274 if (SUCCEEDED(hRet))
2276 hRet = VariantChangeTypeEx(&vDbl, &vDbl, LOCALE_USER_DEFAULT, 0, VT_R8);
2278 if (SUCCEEDED(hRet))
2280 if (V_R8(&vDbl) > (R8_MAX / 100.0))
2281 return DISP_E_OVERFLOW;
2283 V_R8(&vDbl) *= 100.0;
2284 hRet = VarFormatNumber(&vDbl, nDigits, nLeading, nParens,
2285 nGrouping, dwFlags, pbstrOut);
2287 if (SUCCEEDED(hRet))
2289 DWORD dwLen = strlenW(*pbstrOut);
2290 BOOL bBracket = (*pbstrOut)[dwLen] == ')' ? TRUE : FALSE;
2292 dwLen -= bBracket;
2293 memcpy(buff, *pbstrOut, dwLen * sizeof(WCHAR));
2294 strcpyW(buff + dwLen, bBracket ? szPercentBracket : szPercent);
2295 SysFreeString(*pbstrOut);
2296 *pbstrOut = SysAllocString(buff);
2297 if (!*pbstrOut)
2298 hRet = E_OUTOFMEMORY;
2302 return hRet;
2305 /**********************************************************************
2306 * VarFormatCurrency [OLEAUT32.127]
2308 * Format a variant value as a currency.
2310 * PARAMS
2311 * pVarIn [I] Variant to format
2312 * nDigits [I] Number of digits following the decimal point (-1 = user default)
2313 * nLeading [I] Use a leading zero (-2 = user default, -1 = yes, 0 = no)
2314 * nParens [I] Use brackets for values < 0 (-2 = user default, -1 = yes, 0 = no)
2315 * nGrouping [I] Use grouping characters (-2 = user default, -1 = yes, 0 = no)
2316 * dwFlags [I] Currently unused, set to zero
2317 * pbstrOut [O] Destination for formatted string.
2319 * RETURNS
2320 * Success: S_OK. pbstrOut contains the formatted value.
2321 * Failure: E_INVALIDARG, if any parameter is invalid.
2322 * E_OUTOFMEMORY, if enough memory cannot be allocated.
2323 * DISP_E_TYPEMISMATCH, if the variant cannot be formatted.
2325 * NOTES
2326 * This function uses LOCALE_USER_DEFAULT when determining the currency format
2327 * characters to use.
2329 HRESULT WINAPI VarFormatCurrency(LPVARIANT pVarIn, INT nDigits, INT nLeading,
2330 INT nParens, INT nGrouping, ULONG dwFlags,
2331 BSTR *pbstrOut)
2333 HRESULT hRet;
2334 VARIANT vStr;
2336 TRACE("(%p->(%s%s),%d,%d,%d,%d,0x%08lx,%p)\n", pVarIn, debugstr_VT(pVarIn),
2337 debugstr_VF(pVarIn), nDigits, nLeading, nParens, nGrouping, dwFlags, pbstrOut);
2339 if (!pVarIn || !pbstrOut || nDigits > 9)
2340 return E_INVALIDARG;
2342 *pbstrOut = NULL;
2344 V_VT(&vStr) = VT_EMPTY;
2345 hRet = VariantCopyInd(&vStr, pVarIn);
2347 if (SUCCEEDED(hRet))
2348 hRet = VariantChangeTypeEx(&vStr, &vStr, LOCALE_USER_DEFAULT, 0, VT_BSTR);
2350 if (SUCCEEDED(hRet))
2352 WCHAR buff[256], decimal[8], thousands[8], currency[8];
2353 CURRENCYFMTW numfmt;
2355 if (nDigits < 0)
2356 GETLOCALENUMBER(LOCALE_IDIGITS, NumDigits);
2357 else
2358 numfmt.NumDigits = nDigits;
2360 if (nLeading == -2)
2361 GETLOCALENUMBER(LOCALE_ILZERO, LeadingZero);
2362 else if (nLeading == -1)
2363 numfmt.LeadingZero = 1;
2364 else
2365 numfmt.LeadingZero = 0;
2367 if (nGrouping == -2)
2369 WCHAR nGrouping[16];
2370 nGrouping[2] = '\0';
2371 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, nGrouping,
2372 sizeof(nGrouping)/sizeof(WCHAR));
2373 numfmt.Grouping = nGrouping[2] == '2' ? 32 : nGrouping[0] - '0';
2375 else if (nGrouping == -1)
2376 numfmt.Grouping = 3; /* 3 = "n,nnn.nn" */
2377 else
2378 numfmt.Grouping = 0; /* 0 = No grouping */
2380 if (nParens == -2)
2381 GETLOCALENUMBER(LOCALE_INEGCURR, NegativeOrder);
2382 else if (nParens == -1)
2383 numfmt.NegativeOrder = 0; /* 0 = "(xxx)" */
2384 else
2385 numfmt.NegativeOrder = 1; /* 1 = "-xxx" */
2387 GETLOCALENUMBER(LOCALE_ICURRENCY, PositiveOrder);
2389 numfmt.lpDecimalSep = decimal;
2390 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, decimal,
2391 sizeof(decimal)/sizeof(WCHAR));
2392 numfmt.lpThousandSep = thousands;
2393 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, thousands,
2394 sizeof(thousands)/sizeof(WCHAR));
2395 numfmt.lpCurrencySymbol = currency;
2396 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, currency,
2397 sizeof(currency)/sizeof(WCHAR));
2399 /* use NLS as per VarFormatNumber() */
2400 if (GetCurrencyFormatW(LOCALE_USER_DEFAULT, 0, V_BSTR(&vStr), &numfmt,
2401 buff, sizeof(buff)/sizeof(WCHAR)))
2403 *pbstrOut = SysAllocString(buff);
2404 if (!*pbstrOut)
2405 hRet = E_OUTOFMEMORY;
2407 else
2408 hRet = DISP_E_TYPEMISMATCH;
2410 SysFreeString(V_BSTR(&vStr));
2412 return hRet;