1 /* 2 * Variant formatting functions 3 * 4 * Copyright 2008 Damjan Jovanovic 5 * Copyright 2003 Jon Griffiths 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2.1 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this library; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 20 * 21 * NOTES 22 * Since the formatting functions aren't properly documented, I used the 23 * Visual Basic documentation as a guide to implementing these functions. This 24 * means that some named or user-defined formats may work slightly differently. 25 * Please submit a test case if you find a difference. 26 */ 27 28 #include <string.h> 29 #include <stdlib.h> 30 #include <stdarg.h> 31 #include <stdio.h> 32 33 #include "windef.h" 34 #include "winbase.h" 35 #include "winerror.h" 36 #include "variant.h" 37 #include "wine/debug.h" 38 39 WINE_DEFAULT_DEBUG_CHANNEL(variant); 40 41 /* Make sure internal conversions to strings use the '.','+'/'-' and ',' 42 * format chars from the US locale. This enables us to parse the created 43 * strings to determine the number of decimal places, exponent, etc. 44 */ 45 #define LCID_US MAKELCID(MAKELANGID(LANG_ENGLISH,SUBLANG_ENGLISH_US),SORT_DEFAULT) 46 47 static const WCHAR szPercent_d[] = { '%','d','\0' }; 48 static const WCHAR szPercentZeroTwo_d[] = { '%','0','2','d','\0' }; 49 static const WCHAR szPercentZeroStar_d[] = { '%','0','*','d','\0' }; 50 51 /****************************************************************************** 52 * Variant-Formats {OLEAUT32} 53 * 54 * NOTES 55 * When formatting a variant a variety of format strings may be used to generate 56 * different kinds of formatted output. A format string consists of either a named 57 * format, or a user-defined format. 58 * 59 * The following named formats are defined: 60 *| Name Description 61 *| ---- ----------- 62 *| General Date Display Date, and time for non-integer values 63 *| Short Date Short date format as defined by locale settings 64 *| Medium Date Medium date format as defined by locale settings 65 *| Long Date Long date format as defined by locale settings 66 *| Short Time Short Time format as defined by locale settings 67 *| Medium Time Medium time format as defined by locale settings 68 *| Long Time Long time format as defined by locale settings 69 *| True/False Localised text of "True" or "False" 70 *| Yes/No Localised text of "Yes" or "No" 71 *| On/Off Localised text of "On" or "Off" 72 *| General Number No thousands separator. No decimal points for integers 73 *| Currency General currency format using localised characters 74 *| Fixed At least one whole and two fractional digits 75 *| Standard Same as 'Fixed', but including decimal separators 76 *| Percent Multiply by 100 and display a trailing '%' character 77 *| Scientific Display with exponent 78 * 79 * User-defined formats consist of a combination of tokens and literal 80 * characters. Literal characters are copied unmodified to the formatted 81 * output at the position they occupy in the format string. Any character 82 * that is not recognised as a token is treated as a literal. A literal can 83 * also be specified by preceding it with a backslash character 84 * (e.g. "\L\i\t\e\r\a\l") or enclosing it in double quotes. 85 * 86 * A user-defined format can have up to 4 sections, depending on the type of 87 * format. The following table lists sections and their meaning: 88 *| Format Type Sections Meaning 89 *| ----------- -------- ------- 90 *| Number 1 Use the same format for all numbers 91 *| Number 2 Use format 1 for positive and 2 for negative numbers 92 *| Number 3 Use format 1 for positive, 2 for zero, and 3 93 *| for negative numbers. 94 *| Number 4 Use format 1 for positive, 2 for zero, 3 for 95 *| negative, and 4 for null numbers. 96 *| String 1 Use the same format for all strings 97 *| String 2 Use format 2 for null and empty strings, otherwise 98 *| use format 1. 99 *| Date 1 Use the same format for all dates 100 * 101 * The formatting tokens fall into several categories depending on the type 102 * of formatted output. For more information on each type, see 103 * VarFormat-Dates(), VarFormat-Strings() and VarFormat-Numbers(). 104 * 105 * SEE ALSO 106 * VarTokenizeFormatString(), VarFormatFromTokens(), VarFormat(), 107 * VarFormatDateTime(), VarFormatNumber(), VarFormatCurrency(). 108 */ 109 110 /****************************************************************************** 111 * VarFormat-Strings {OLEAUT32} 112 * 113 * NOTES 114 * When formatting a variant as a string, it is first converted to a VT_BSTR. 115 * The user-format string defines which characters are copied into which 116 * positions in the output string. Literals may be inserted in the format 117 * string. When creating the formatted string, excess characters in the string 118 * (those not consumed by a token) are appended to the end of the output. If 119 * there are more tokens than characters in the string to format, spaces will 120 * be inserted at the start of the string if the '@' token was used. 121 * 122 * By default strings are converted to lowercase, or uppercase if the '>' token 123 * is encountered. This applies to the whole string: it is not possible to 124 * generate a mixed-case output string. 125 * 126 * In user-defined string formats, the following tokens are recognised: 127 *| Token Description 128 *| ----- ----------- 129 *| '@' Copy a char from the source, or a space if no chars are left. 130 *| '&' Copy a char from the source, or write nothing if no chars are left. 131 *| '<' Output the whole string as lower-case (the default). 132 *| '>' Output the whole string as upper-case. 133 *| '!' MSDN indicates that this character should cause right-to-left 134 *| copying, however tests show that it is tokenised but not processed. 135 */ 136 137 /* 138 * Common format definitions 139 */ 140 141 /* Format types */ 142 #define FMT_TYPE_UNKNOWN 0x0 143 #define FMT_TYPE_GENERAL 0x1 144 #define FMT_TYPE_NUMBER 0x2 145 #define FMT_TYPE_DATE 0x3 146 #define FMT_TYPE_STRING 0x4 147 148 #define FMT_TO_STRING 0x0 /* If header->size == this, act like VB's Str() fn */ 149 150 typedef struct tagFMT_SHORT_HEADER 151 { 152 BYTE size; /* Size of tokenised block (including header), or FMT_TO_STRING */ 153 BYTE type; /* Allowable types (FMT_TYPE_*) */ 154 BYTE offset[1]; /* Offset of the first (and only) format section */ 155 } FMT_SHORT_HEADER; 156 157 typedef struct tagFMT_HEADER 158 { 159 BYTE size; /* Total size of the whole tokenised block (including header) */ 160 BYTE type; /* Allowable types (FMT_TYPE_*) */ 161 BYTE starts[4]; /* Offset of each of the 4 format sections, or 0 if none */ 162 } FMT_HEADER; 163 164 #define FmtGetPositive(x) (x->starts[0]) 165 #define FmtGetNegative(x) (x->starts[1] ? x->starts[1] : x->starts[0]) 166 #define FmtGetZero(x) (x->starts[2] ? x->starts[2] : x->starts[0]) 167 #define FmtGetNull(x) (x->starts[3] ? x->starts[3] : x->starts[0]) 168 169 /* 170 * String formats 171 */ 172 173 #define FMT_FLAG_LT 0x1 /* Has '<' (lower case) */ 174 #define FMT_FLAG_GT 0x2 /* Has '>' (upper case) */ 175 #define FMT_FLAG_RTL 0x4 /* Has '!' (Copy right to left) */ 176 177 typedef struct tagFMT_STRING_HEADER 178 { 179 BYTE flags; /* LT, GT, RTL */ 180 BYTE unknown1; 181 BYTE unknown2; 182 BYTE copy_chars; /* Number of chars to be copied */ 183 BYTE unknown3; 184 } FMT_STRING_HEADER; 185 186 /* 187 * Number formats 188 */ 189 190 #define FMT_FLAG_PERCENT 0x1 /* Has '%' (Percentage) */ 191 #define FMT_FLAG_EXPONENT 0x2 /* Has 'e' (Exponent/Scientific notation) */ 192 #define FMT_FLAG_THOUSANDS 0x4 /* Has ',' (Standard use of the thousands separator) */ 193 #define FMT_FLAG_BOOL 0x20 /* Boolean format */ 194 195 typedef struct tagFMT_NUMBER_HEADER 196 { 197 BYTE flags; /* PERCENT, EXPONENT, THOUSANDS, BOOL */ 198 BYTE multiplier; /* Multiplier, 100 for percentages */ 199 BYTE divisor; /* Divisor, 1000 if '%%' was used */ 200 BYTE whole; /* Number of digits before the decimal point */ 201 BYTE fractional; /* Number of digits after the decimal point */ 202 } FMT_NUMBER_HEADER; 203 204 /* 205 * Date Formats 206 */ 207 typedef struct tagFMT_DATE_HEADER 208 { 209 BYTE flags; 210 BYTE unknown1; 211 BYTE unknown2; 212 BYTE unknown3; 213 BYTE unknown4; 214 } FMT_DATE_HEADER; 215 216 /* 217 * Format token values 218 */ 219 #define FMT_GEN_COPY 0x00 /* \n, "lit" => 0,pos,len: Copy len chars from input+pos */ 220 #define FMT_GEN_INLINE 0x01 /* => 1,len,[chars]: Copy len chars from token stream */ 221 #define FMT_GEN_END 0x02 /* \0,; => 2: End of the tokenised format */ 222 #define FMT_DATE_TIME_SEP 0x03 /* Time separator char */ 223 #define FMT_DATE_DATE_SEP 0x04 /* Date separator char */ 224 #define FMT_DATE_GENERAL 0x05 /* General format date */ 225 #define FMT_DATE_QUARTER 0x06 /* Quarter of the year from 1-4 */ 226 #define FMT_DATE_TIME_SYS 0x07 /* System long time format */ 227 #define FMT_DATE_DAY 0x08 /* Day with no leading 0 */ 228 #define FMT_DATE_DAY_0 0x09 /* Day with leading 0 */ 229 #define FMT_DATE_DAY_SHORT 0x0A /* Short day name */ 230 #define FMT_DATE_DAY_LONG 0x0B /* Long day name */ 231 #define FMT_DATE_SHORT 0x0C /* Short date format */ 232 #define FMT_DATE_LONG 0x0D /* Long date format */ 233 #define FMT_DATE_MEDIUM 0x0E /* Medium date format */ 234 #define FMT_DATE_DAY_WEEK 0x0F /* First day of the week */ 235 #define FMT_DATE_WEEK_YEAR 0x10 /* First week of the year */ 236 #define FMT_DATE_MON 0x11 /* Month with no leading 0 */ 237 #define FMT_DATE_MON_0 0x12 /* Month with leading 0 */ 238 #define FMT_DATE_MON_SHORT 0x13 /* Short month name */ 239 #define FMT_DATE_MON_LONG 0x14 /* Long month name */ 240 #define FMT_DATE_YEAR_DOY 0x15 /* Day of the year with no leading 0 */ 241 #define FMT_DATE_YEAR_0 0x16 /* 2 digit year with leading 0 */ 242 /* NOTE: token 0x17 is not defined, 'yyy' is not valid */ 243 #define FMT_DATE_YEAR_LONG 0x18 /* 4 digit year */ 244 #define FMT_DATE_MIN 0x1A /* Minutes with no leading 0 */ 245 #define FMT_DATE_MIN_0 0x1B /* Minutes with leading 0 */ 246 #define FMT_DATE_SEC 0x1C /* Seconds with no leading 0 */ 247 #define FMT_DATE_SEC_0 0x1D /* Seconds with leading 0 */ 248 #define FMT_DATE_HOUR 0x1E /* Hours with no leading 0 */ 249 #define FMT_DATE_HOUR_0 0x1F /* Hours with leading 0 */ 250 #define FMT_DATE_HOUR_12 0x20 /* Hours with no leading 0, 12 hour clock */ 251 #define FMT_DATE_HOUR_12_0 0x21 /* Hours with leading 0, 12 hour clock */ 252 #define FMT_DATE_TIME_UNK2 0x23 /* same as FMT_DATE_HOUR_0, for "short time" format */ 253 /* FIXME: probably missing some here */ 254 #define FMT_DATE_AMPM_SYS1 0x2E /* AM/PM as defined by system settings */ 255 #define FMT_DATE_AMPM_UPPER 0x2F /* Upper-case AM or PM */ 256 #define FMT_DATE_A_UPPER 0x30 /* Upper-case A or P */ 257 #define FMT_DATE_AMPM_SYS2 0x31 /* AM/PM as defined by system settings */ 258 #define FMT_DATE_AMPM_LOWER 0x32 /* Lower-case AM or PM */ 259 #define FMT_DATE_A_LOWER 0x33 /* Lower-case A or P */ 260 #define FMT_NUM_COPY_ZERO 0x34 /* Copy 1 digit or 0 if no digit */ 261 #define FMT_NUM_COPY_SKIP 0x35 /* Copy 1 digit or skip if no digit */ 262 #define FMT_NUM_DECIMAL 0x36 /* Decimal separator */ 263 #define FMT_NUM_EXP_POS_U 0x37 /* Scientific notation, uppercase, + sign */ 264 #define FMT_NUM_EXP_NEG_U 0x38 /* Scientific notation, uppercase, - sign */ 265 #define FMT_NUM_EXP_POS_L 0x39 /* Scientific notation, lowercase, + sign */ 266 #define FMT_NUM_EXP_NEG_L 0x3A /* Scientific notation, lowercase, - sign */ 267 #define FMT_NUM_CURRENCY 0x3B /* Currency symbol */ 268 #define FMT_NUM_TRUE_FALSE 0x3D /* Convert to "True" or "False" */ 269 #define FMT_NUM_YES_NO 0x3E /* Convert to "Yes" or "No" */ 270 #define FMT_NUM_ON_OFF 0x3F /* Convert to "On" or "Off" */ 271 #define FMT_STR_COPY_SPACE 0x40 /* Copy len chars with space if no char */ 272 #define FMT_STR_COPY_SKIP 0x41 /* Copy len chars or skip if no char */ 273 274 /* Named Formats and their tokenised values */ 275 static const WCHAR szGeneralDate[] = { 'G','e','n','e','r','a','l',' ','D','a','t','e','\0' }; 276 static const BYTE fmtGeneralDate[0x0a] = 277 { 278 0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER), 279 0x0,0x0,0x0,0x0,0x0, 280 FMT_DATE_GENERAL,FMT_GEN_END 281 }; 282 283 static const WCHAR szShortDate[] = { 'S','h','o','r','t',' ','D','a','t','e','\0' }; 284 static const BYTE fmtShortDate[0x0a] = 285 { 286 0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER), 287 0x0,0x0,0x0,0x0,0x0, 288 FMT_DATE_SHORT,FMT_GEN_END 289 }; 290 291 static const WCHAR szMediumDate[] = { 'M','e','d','i','u','m',' ','D','a','t','e','\0' }; 292 static const BYTE fmtMediumDate[0x0a] = 293 { 294 0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER), 295 0x0,0x0,0x0,0x0,0x0, 296 FMT_DATE_MEDIUM,FMT_GEN_END 297 }; 298 299 static const WCHAR szLongDate[] = { 'L','o','n','g',' ','D','a','t','e','\0' }; 300 static const BYTE fmtLongDate[0x0a] = 301 { 302 0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER), 303 0x0,0x0,0x0,0x0,0x0, 304 FMT_DATE_LONG,FMT_GEN_END 305 }; 306 307 static const WCHAR szShortTime[] = { 'S','h','o','r','t',' ','T','i','m','e','\0' }; 308 static const BYTE fmtShortTime[0x0c] = 309 { 310 0x0c,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER), 311 0x0,0x0,0x0,0x0,0x0, 312 FMT_DATE_TIME_UNK2,FMT_DATE_TIME_SEP,FMT_DATE_MIN_0,FMT_GEN_END 313 }; 314 315 static const WCHAR szMediumTime[] = { 'M','e','d','i','u','m',' ','T','i','m','e','\0' }; 316 static const BYTE fmtMediumTime[0x11] = 317 { 318 0x11,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER), 319 0x0,0x0,0x0,0x0,0x0, 320 FMT_DATE_HOUR_12_0,FMT_DATE_TIME_SEP,FMT_DATE_MIN_0, 321 FMT_GEN_INLINE,0x01,' ','\0',FMT_DATE_AMPM_SYS1,FMT_GEN_END 322 }; 323 324 static const WCHAR szLongTime[] = { 'L','o','n','g',' ','T','i','m','e','\0' }; 325 static const BYTE fmtLongTime[0x0d] = 326 { 327 0x0a,FMT_TYPE_DATE,sizeof(FMT_SHORT_HEADER), 328 0x0,0x0,0x0,0x0,0x0, 329 FMT_DATE_TIME_SYS,FMT_GEN_END 330 }; 331 332 static const WCHAR szTrueFalse[] = { 'T','r','u','e','/','F','a','l','s','e','\0' }; 333 static const BYTE fmtTrueFalse[0x0d] = 334 { 335 0x0d,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0, 336 FMT_FLAG_BOOL,0x0,0x0,0x0,0x0, 337 FMT_NUM_TRUE_FALSE,FMT_GEN_END 338 }; 339 340 static const WCHAR szYesNo[] = { 'Y','e','s','/','N','o','\0' }; 341 static const BYTE fmtYesNo[0x0d] = 342 { 343 0x0d,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0, 344 FMT_FLAG_BOOL,0x0,0x0,0x0,0x0, 345 FMT_NUM_YES_NO,FMT_GEN_END 346 }; 347 348 static const WCHAR szOnOff[] = { 'O','n','/','O','f','f','\0' }; 349 static const BYTE fmtOnOff[0x0d] = 350 { 351 0x0d,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0, 352 FMT_FLAG_BOOL,0x0,0x0,0x0,0x0, 353 FMT_NUM_ON_OFF,FMT_GEN_END 354 }; 355 356 static const WCHAR szGeneralNumber[] = { 'G','e','n','e','r','a','l',' ','N','u','m','b','e','r','\0' }; 357 static const BYTE fmtGeneralNumber[sizeof(FMT_HEADER)] = 358 { 359 sizeof(FMT_HEADER),FMT_TYPE_GENERAL,sizeof(FMT_HEADER),0x0,0x0,0x0 360 }; 361 362 static const WCHAR szCurrency[] = { 'C','u','r','r','e','n','c','y','\0' }; 363 static const BYTE fmtCurrency[0x26] = 364 { 365 0x26,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x12,0x0,0x0, 366 /* Positive numbers */ 367 FMT_FLAG_THOUSANDS,0xcc,0x0,0x1,0x2, 368 FMT_NUM_CURRENCY,FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2, 369 FMT_GEN_END, 370 /* Negative numbers */ 371 FMT_FLAG_THOUSANDS,0xcc,0x0,0x1,0x2, 372 FMT_GEN_INLINE,0x1,'(','\0',FMT_NUM_CURRENCY,FMT_NUM_COPY_ZERO,0x1, 373 FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,FMT_GEN_INLINE,0x1,')','\0', 374 FMT_GEN_END 375 }; 376 377 static const WCHAR szFixed[] = { 'F','i','x','e','d','\0' }; 378 static const BYTE fmtFixed[0x11] = 379 { 380 0x11,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0, 381 0x0,0x0,0x0,0x1,0x2, 382 FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,FMT_GEN_END 383 }; 384 385 static const WCHAR szStandard[] = { 'S','t','a','n','d','a','r','d','\0' }; 386 static const BYTE fmtStandard[0x11] = 387 { 388 0x11,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0, 389 FMT_FLAG_THOUSANDS,0x0,0x0,0x1,0x2, 390 FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,FMT_GEN_END 391 }; 392 393 static const WCHAR szPercent[] = { 'P','e','r','c','e','n','t','\0' }; 394 static const BYTE fmtPercent[0x15] = 395 { 396 0x15,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0, 397 FMT_FLAG_PERCENT,0x1,0x0,0x1,0x2, 398 FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2, 399 FMT_GEN_INLINE,0x1,'%','\0',FMT_GEN_END 400 }; 401 402 static const WCHAR szScientific[] = { 'S','c','i','e','n','t','i','f','i','c','\0' }; 403 static const BYTE fmtScientific[0x13] = 404 { 405 0x13,FMT_TYPE_NUMBER,sizeof(FMT_HEADER),0x0,0x0,0x0, 406 FMT_FLAG_EXPONENT,0x0,0x0,0x1,0x2, 407 FMT_NUM_COPY_ZERO,0x1,FMT_NUM_DECIMAL,FMT_NUM_COPY_ZERO,0x2,FMT_NUM_EXP_POS_U,0x2,FMT_GEN_END 408 }; 409 410 typedef struct tagNAMED_FORMAT 411 { 412 LPCWSTR name; 413 const BYTE* format; 414 } NAMED_FORMAT; 415 416 /* Format name to tokenised format. Must be kept sorted by name */ 417 static const NAMED_FORMAT VARIANT_NamedFormats[] = 418 { 419 { szCurrency, fmtCurrency }, 420 { szFixed, fmtFixed }, 421 { szGeneralDate, fmtGeneralDate }, 422 { szGeneralNumber, fmtGeneralNumber }, 423 { szLongDate, fmtLongDate }, 424 { szLongTime, fmtLongTime }, 425 { szMediumDate, fmtMediumDate }, 426 { szMediumTime, fmtMediumTime }, 427 { szOnOff, fmtOnOff }, 428 { szPercent, fmtPercent }, 429 { szScientific, fmtScientific }, 430 { szShortDate, fmtShortDate }, 431 { szShortTime, fmtShortTime }, 432 { szStandard, fmtStandard }, 433 { szTrueFalse, fmtTrueFalse }, 434 { szYesNo, fmtYesNo } 435 }; 436 typedef const NAMED_FORMAT *LPCNAMED_FORMAT; 437 438 static int __cdecl FormatCompareFn(const void *l, const void *r) 439 { 440 return wcsicmp(((LPCNAMED_FORMAT)l)->name, ((LPCNAMED_FORMAT)r)->name); 441 } 442 443 static inline const BYTE *VARIANT_GetNamedFormat(LPCWSTR lpszFormat) 444 { 445 NAMED_FORMAT key; 446 LPCNAMED_FORMAT fmt; 447 448 key.name = lpszFormat; 449 fmt = bsearch(&key, VARIANT_NamedFormats, ARRAY_SIZE(VARIANT_NamedFormats), 450 sizeof(NAMED_FORMAT), FormatCompareFn); 451 return fmt ? fmt->format : NULL; 452 } 453 454 /* Return an error if the token for the value will not fit in the destination */ 455 #define NEED_SPACE(x) if (cbTok < (int)(x)) return TYPE_E_BUFFERTOOSMALL; cbTok -= (x) 456 457 /* Non-zero if the format is unknown or a given type */ 458 #define COULD_BE(typ) ((!fmt_number && header->type==FMT_TYPE_UNKNOWN)||header->type==typ) 459 460 /* State during tokenising */ 461 #define FMT_STATE_OPEN_COPY 0x1 /* Last token written was a copy */ 462 #define FMT_STATE_WROTE_DECIMAL 0x2 /* Already wrote a decimal separator */ 463 #define FMT_STATE_SEEN_HOURS 0x4 /* See the hh specifier */ 464 #define FMT_STATE_WROTE_MINUTES 0x8 /* Wrote minutes */ 465 466 /********************************************************************** 467 * VarTokenizeFormatString [OLEAUT32.140] 468 * 469 * Convert a format string into tokenised form. 470 * 471 * PARAMS 472 * lpszFormat [I] Format string to tokenise 473 * rgbTok [O] Destination for tokenised format 474 * cbTok [I] Size of rgbTok in bytes 475 * nFirstDay [I] First day of the week (1-7, or 0 for current system default) 476 * nFirstWeek [I] How to treat the first week (see notes) 477 * lcid [I] Locale Id of the format string 478 * pcbActual [O] If non-NULL, filled with the first token generated 479 * 480 * RETURNS 481 * Success: S_OK. rgbTok contains the tokenised format. 482 * Failure: E_INVALIDARG, if any argument is invalid. 483 * TYPE_E_BUFFERTOOSMALL, if rgbTok is not large enough. 484 * 485 * NOTES 486 * Valid values for the nFirstWeek parameter are: 487 *| Value Meaning 488 *| ----- ------- 489 *| 0 Use the current system default 490 *| 1 The first week is that containing Jan 1 491 *| 2 Four or more days of the first week are in the current year 492 *| 3 The first week is 7 days long 493 * See Variant-Formats(), VarFormatFromTokens(). 494 */ 495 HRESULT WINAPI VarTokenizeFormatString(LPOLESTR lpszFormat, LPBYTE rgbTok, 496 int cbTok, int nFirstDay, int nFirstWeek, 497 LCID lcid, int *pcbActual) 498 { 499 /* Note: none of these strings should be NUL terminated */ 500 static const WCHAR szTTTTT[] = { 't','t','t','t','t' }; 501 static const WCHAR szAMPM[] = { 'A','M','P','M' }; 502 static const WCHAR szampm[] = { 'a','m','p','m' }; 503 static const WCHAR szAMSlashPM[] = { 'A','M','/','P','M' }; 504 static const WCHAR szamSlashpm[] = { 'a','m','/','p','m' }; 505 const BYTE *namedFmt; 506 FMT_HEADER *header = (FMT_HEADER*)rgbTok; 507 FMT_STRING_HEADER *str_header = (FMT_STRING_HEADER*)(rgbTok + sizeof(FMT_HEADER)); 508 FMT_NUMBER_HEADER *num_header = (FMT_NUMBER_HEADER*)str_header; 509 BYTE* pOut = rgbTok + sizeof(FMT_HEADER) + sizeof(FMT_STRING_HEADER); 510 BYTE* pLastHours = NULL; 511 BYTE fmt_number = 0; 512 DWORD fmt_state = 0; 513 LPCWSTR pFormat = lpszFormat; 514 515 TRACE("(%s,%p,%d,%d,%d,0x%08x,%p)\n", debugstr_w(lpszFormat), rgbTok, cbTok, 516 nFirstDay, nFirstWeek, lcid, pcbActual); 517 518 if (!rgbTok || 519 nFirstDay < 0 || nFirstDay > 7 || nFirstWeek < 0 || nFirstWeek > 3) 520 return E_INVALIDARG; 521 522 if (!lpszFormat || !*lpszFormat) 523 { 524 /* An empty string means 'general format' */ 525 NEED_SPACE(sizeof(BYTE)); 526 *rgbTok = FMT_TO_STRING; 527 if (pcbActual) 528 *pcbActual = FMT_TO_STRING; 529 return S_OK; 530 } 531 532 if (cbTok > 255) 533 cbTok = 255; /* Ensure we error instead of wrapping */ 534 535 /* Named formats */ 536 namedFmt = VARIANT_GetNamedFormat(lpszFormat); 537 if (namedFmt) 538 { 539 NEED_SPACE(namedFmt[0]); 540 memcpy(rgbTok, namedFmt, namedFmt[0]); 541 TRACE("Using pre-tokenised named format %s\n", debugstr_w(lpszFormat)); 542 /* FIXME: pcbActual */ 543 return S_OK; 544 } 545 546 /* Insert header */ 547 NEED_SPACE(sizeof(FMT_HEADER) + sizeof(FMT_STRING_HEADER)); 548 memset(header, 0, sizeof(FMT_HEADER)); 549 memset(str_header, 0, sizeof(FMT_STRING_HEADER)); 550 551 header->starts[fmt_number] = sizeof(FMT_HEADER); 552 553 while (*pFormat) 554 { 555 /* -------------- 556 * General tokens 557 * -------------- 558 */ 559 if (*pFormat == ';') 560 { 561 while (*pFormat == ';') 562 { 563 TRACE(";\n"); 564 if (++fmt_number > 3) 565 return E_INVALIDARG; /* too many formats */ 566 pFormat++; 567 } 568 if (*pFormat) 569 { 570 TRACE("New header\n"); 571 NEED_SPACE(sizeof(BYTE) + sizeof(FMT_STRING_HEADER)); 572 *pOut++ = FMT_GEN_END; 573 574 header->starts[fmt_number] = pOut - rgbTok; 575 str_header = (FMT_STRING_HEADER*)pOut; 576 num_header = (FMT_NUMBER_HEADER*)pOut; 577 memset(str_header, 0, sizeof(FMT_STRING_HEADER)); 578 pOut += sizeof(FMT_STRING_HEADER); 579 fmt_state = 0; 580 pLastHours = NULL; 581 } 582 } 583 else if (*pFormat == '\\') 584 { 585 /* Escaped character */ 586 if (pFormat[1]) 587 { 588 NEED_SPACE(3 * sizeof(BYTE)); 589 pFormat++; 590 *pOut++ = FMT_GEN_COPY; 591 *pOut++ = pFormat - lpszFormat; 592 *pOut++ = 0x1; 593 fmt_state |= FMT_STATE_OPEN_COPY; 594 TRACE("'\\'\n"); 595 } 596 else 597 fmt_state &= ~FMT_STATE_OPEN_COPY; 598 pFormat++; 599 } 600 else if (*pFormat == '"') 601 { 602 /* Escaped string 603 * Note: Native encodes "" as a copy of length zero. That's just dumb, so 604 * here we avoid encoding anything in this case. 605 */ 606 if (!pFormat[1]) 607 pFormat++; 608 else if (pFormat[1] == '"') 609 { 610 pFormat += 2; 611 } 612 else 613 { 614 LPCWSTR start = ++pFormat; 615 while (*pFormat && *pFormat != '"') 616 pFormat++; 617 NEED_SPACE(3 * sizeof(BYTE)); 618 *pOut++ = FMT_GEN_COPY; 619 *pOut++ = start - lpszFormat; 620 *pOut++ = pFormat - start; 621 if (*pFormat == '"') 622 pFormat++; 623 TRACE("Quoted string pos %d, len %d\n", pOut[-2], pOut[-1]); 624 } 625 fmt_state &= ~FMT_STATE_OPEN_COPY; 626 } 627 /* ------------- 628 * Number tokens 629 * ------------- 630 */ 631 else if (*pFormat == '0' && COULD_BE(FMT_TYPE_NUMBER)) 632 { 633 /* Number formats: Digit from number or '0' if no digits 634 * Other formats: Literal 635 * Types the format if found 636 */ 637 header->type = FMT_TYPE_NUMBER; 638 NEED_SPACE(2 * sizeof(BYTE)); 639 *pOut++ = FMT_NUM_COPY_ZERO; 640 *pOut = 0x0; 641 while (*pFormat == '0') 642 { 643 *pOut = *pOut + 1; 644 pFormat++; 645 } 646 if (fmt_state & FMT_STATE_WROTE_DECIMAL) 647 num_header->fractional += *pOut; 648 else 649 num_header->whole += *pOut; 650 TRACE("%d 0's\n", *pOut); 651 pOut++; 652 fmt_state &= ~FMT_STATE_OPEN_COPY; 653 } 654 else if (*pFormat == '#' && COULD_BE(FMT_TYPE_NUMBER)) 655 { 656 /* Number formats: Digit from number or blank if no digits 657 * Other formats: Literal 658 * Types the format if found 659 */ 660 header->type = FMT_TYPE_NUMBER; 661 NEED_SPACE(2 * sizeof(BYTE)); 662 *pOut++ = FMT_NUM_COPY_SKIP; 663 *pOut = 0x0; 664 while (*pFormat == '#') 665 { 666 *pOut = *pOut + 1; 667 pFormat++; 668 } 669 if (fmt_state & FMT_STATE_WROTE_DECIMAL) 670 num_header->fractional += *pOut; 671 else 672 num_header->whole += *pOut; 673 TRACE("%d #'s\n", *pOut); 674 pOut++; 675 fmt_state &= ~FMT_STATE_OPEN_COPY; 676 } 677 else if (*pFormat == '.' && COULD_BE(FMT_TYPE_NUMBER) && 678 !(fmt_state & FMT_STATE_WROTE_DECIMAL)) 679 { 680 /* Number formats: Decimal separator when 1st seen, literal thereafter 681 * Other formats: Literal 682 * Types the format if found 683 */ 684 header->type = FMT_TYPE_NUMBER; 685 NEED_SPACE(sizeof(BYTE)); 686 *pOut++ = FMT_NUM_DECIMAL; 687 fmt_state |= FMT_STATE_WROTE_DECIMAL; 688 fmt_state &= ~FMT_STATE_OPEN_COPY; 689 pFormat++; 690 TRACE("decimal sep\n"); 691 } 692 else if ((*pFormat == 'e' || *pFormat == 'E') && (pFormat[1] == '-' || 693 pFormat[1] == '+') && header->type == FMT_TYPE_NUMBER) 694 { 695 /* Number formats: Exponent specifier 696 * Other formats: Literal 697 */ 698 num_header->flags |= FMT_FLAG_EXPONENT; 699 NEED_SPACE(2 * sizeof(BYTE)); 700 if (*pFormat == 'e') { 701 if (pFormat[1] == '+') 702 *pOut = FMT_NUM_EXP_POS_L; 703 else 704 *pOut = FMT_NUM_EXP_NEG_L; 705 } else { 706 if (pFormat[1] == '+') 707 *pOut = FMT_NUM_EXP_POS_U; 708 else 709 *pOut = FMT_NUM_EXP_NEG_U; 710 } 711 pFormat += 2; 712 *++pOut = 0x0; 713 while (*pFormat == '0') 714 { 715 *pOut = *pOut + 1; 716 pFormat++; 717 } 718 pOut++; 719 TRACE("exponent\n"); 720 } 721 /* FIXME: %% => Divide by 1000 */ 722 else if (*pFormat == ',' && header->type == FMT_TYPE_NUMBER) 723 { 724 /* Number formats: Use the thousands separator 725 * Other formats: Literal 726 */ 727 num_header->flags |= FMT_FLAG_THOUSANDS; 728 pFormat++; 729 fmt_state &= ~FMT_STATE_OPEN_COPY; 730 TRACE("thousands sep\n"); 731 } 732 /* ----------- 733 * Date tokens 734 * ----------- 735 */ 736 else if (*pFormat == '/' && COULD_BE(FMT_TYPE_DATE)) 737 { 738 /* Date formats: Date separator 739 * Other formats: Literal 740 * Types the format if found 741 */ 742 header->type = FMT_TYPE_DATE; 743 NEED_SPACE(sizeof(BYTE)); 744 *pOut++ = FMT_DATE_DATE_SEP; 745 pFormat++; 746 fmt_state &= ~FMT_STATE_OPEN_COPY; 747 TRACE("date sep\n"); 748 } 749 else if (*pFormat == ':' && COULD_BE(FMT_TYPE_DATE)) 750 { 751 /* Date formats: Time separator 752 * Other formats: Literal 753 * Types the format if found 754 */ 755 header->type = FMT_TYPE_DATE; 756 NEED_SPACE(sizeof(BYTE)); 757 *pOut++ = FMT_DATE_TIME_SEP; 758 pFormat++; 759 fmt_state &= ~FMT_STATE_OPEN_COPY; 760 TRACE("time sep\n"); 761 } 762 else if ((*pFormat == 'a' || *pFormat == 'A') && 763 !_wcsnicmp(pFormat, szAMPM, ARRAY_SIZE(szAMPM))) 764 { 765 /* Date formats: System AM/PM designation 766 * Other formats: Literal 767 * Types the format if found 768 */ 769 header->type = FMT_TYPE_DATE; 770 NEED_SPACE(sizeof(BYTE)); 771 pFormat += ARRAY_SIZE(szAMPM); 772 if (!wcsncmp(pFormat, szampm, ARRAY_SIZE(szampm))) 773 *pOut++ = FMT_DATE_AMPM_SYS2; 774 else 775 *pOut++ = FMT_DATE_AMPM_SYS1; 776 if (pLastHours) 777 *pLastHours = *pLastHours + 2; 778 TRACE("ampm\n"); 779 } 780 else if (*pFormat == 'a' && pFormat[1] == '/' && 781 (pFormat[2] == 'p' || pFormat[2] == 'P')) 782 { 783 /* Date formats: lowercase a or p designation 784 * Other formats: Literal 785 * Types the format if found 786 */ 787 header->type = FMT_TYPE_DATE; 788 NEED_SPACE(sizeof(BYTE)); 789 pFormat += 3; 790 *pOut++ = FMT_DATE_A_LOWER; 791 if (pLastHours) 792 *pLastHours = *pLastHours + 2; 793 TRACE("a/p\n"); 794 } 795 else if (*pFormat == 'A' && pFormat[1] == '/' && 796 (pFormat[2] == 'p' || pFormat[2] == 'P')) 797 { 798 /* Date formats: Uppercase a or p designation 799 * Other formats: Literal 800 * Types the format if found 801 */ 802 header->type = FMT_TYPE_DATE; 803 NEED_SPACE(sizeof(BYTE)); 804 pFormat += 3; 805 *pOut++ = FMT_DATE_A_UPPER; 806 if (pLastHours) 807 *pLastHours = *pLastHours + 2; 808 TRACE("A/P\n"); 809 } 810 else if (*pFormat == 'a' && !wcsncmp(pFormat, szamSlashpm, ARRAY_SIZE(szamSlashpm))) 811 { 812 /* Date formats: lowercase AM or PM designation 813 * Other formats: Literal 814 * Types the format if found 815 */ 816 header->type = FMT_TYPE_DATE; 817 NEED_SPACE(sizeof(BYTE)); 818 pFormat += ARRAY_SIZE(szamSlashpm); 819 *pOut++ = FMT_DATE_AMPM_LOWER; 820 if (pLastHours) 821 *pLastHours = *pLastHours + 2; 822 TRACE("AM/PM\n"); 823 } 824 else if (*pFormat == 'A' && !wcsncmp(pFormat, szAMSlashPM, ARRAY_SIZE(szAMSlashPM))) 825 { 826 /* Date formats: Uppercase AM or PM designation 827 * Other formats: Literal 828 * Types the format if found 829 */ 830 header->type = FMT_TYPE_DATE; 831 NEED_SPACE(sizeof(BYTE)); 832 pFormat += ARRAY_SIZE(szAMSlashPM); 833 *pOut++ = FMT_DATE_AMPM_UPPER; 834 TRACE("AM/PM\n"); 835 } 836 else if ((*pFormat == 'c' || *pFormat == 'C') && COULD_BE(FMT_TYPE_DATE)) 837 { 838 /* Date formats: General date format 839 * Other formats: Literal 840 * Types the format if found 841 */ 842 header->type = FMT_TYPE_DATE; 843 NEED_SPACE(sizeof(BYTE)); 844 pFormat += ARRAY_SIZE(szAMSlashPM); 845 *pOut++ = FMT_DATE_GENERAL; 846 TRACE("gen date\n"); 847 } 848 else if ((*pFormat == 'd' || *pFormat == 'D') && COULD_BE(FMT_TYPE_DATE)) 849 { 850 /* Date formats: Day specifier 851 * Other formats: Literal 852 * Types the format if found 853 */ 854 int count = -1; 855 header->type = FMT_TYPE_DATE; 856 while ((*pFormat == 'd' || *pFormat == 'D') && count < 6) 857 { 858 pFormat++; 859 count++; 860 } 861 NEED_SPACE(sizeof(BYTE)); 862 *pOut++ = FMT_DATE_DAY + count; 863 fmt_state &= ~FMT_STATE_OPEN_COPY; 864 /* When we find the days token, reset the seen hours state so that 865 * 'mm' is again written as month when encountered. 866 */ 867 fmt_state &= ~FMT_STATE_SEEN_HOURS; 868 TRACE("%d d's\n", count + 1); 869 } 870 else if ((*pFormat == 'h' || *pFormat == 'H') && COULD_BE(FMT_TYPE_DATE)) 871 { 872 /* Date formats: Hour specifier 873 * Other formats: Literal 874 * Types the format if found 875 */ 876 header->type = FMT_TYPE_DATE; 877 NEED_SPACE(sizeof(BYTE)); 878 pFormat++; 879 /* Record the position of the hours specifier - if we encounter 880 * an am/pm specifier we will change the hours from 24 to 12. 881 */ 882 pLastHours = pOut; 883 if (*pFormat == 'h' || *pFormat == 'H') 884 { 885 pFormat++; 886 *pOut++ = FMT_DATE_HOUR_0; 887 TRACE("hh\n"); 888 } 889 else 890 { 891 *pOut++ = FMT_DATE_HOUR; 892 TRACE("h\n"); 893 } 894 fmt_state &= ~FMT_STATE_OPEN_COPY; 895 /* Note that now we have seen an hours token, the next occurrence of 896 * 'mm' indicates minutes, not months. 897 */ 898 fmt_state |= FMT_STATE_SEEN_HOURS; 899 } 900 else if ((*pFormat == 'm' || *pFormat == 'M') && COULD_BE(FMT_TYPE_DATE)) 901 { 902 /* Date formats: Month specifier (or Minute specifier, after hour specifier) 903 * Other formats: Literal 904 * Types the format if found 905 */ 906 int count = -1; 907 header->type = FMT_TYPE_DATE; 908 while ((*pFormat == 'm' || *pFormat == 'M') && count < 4) 909 { 910 pFormat++; 911 count++; 912 } 913 NEED_SPACE(sizeof(BYTE)); 914 if (count <= 1 && fmt_state & FMT_STATE_SEEN_HOURS && 915 !(fmt_state & FMT_STATE_WROTE_MINUTES)) 916 { 917 /* We have seen an hours specifier and not yet written a minutes 918 * specifier. Write this as minutes and thereafter as months. 919 */ 920 *pOut++ = count == 1 ? FMT_DATE_MIN_0 : FMT_DATE_MIN; 921 fmt_state |= FMT_STATE_WROTE_MINUTES; /* Hereafter write months */ 922 } 923 else 924 *pOut++ = FMT_DATE_MON + count; /* Months */ 925 fmt_state &= ~FMT_STATE_OPEN_COPY; 926 TRACE("%d m's\n", count + 1); 927 } 928 else if ((*pFormat == 'n' || *pFormat == 'N') && COULD_BE(FMT_TYPE_DATE)) 929 { 930 /* Date formats: Minute specifier 931 * Other formats: Literal 932 * Types the format if found 933 */ 934 header->type = FMT_TYPE_DATE; 935 NEED_SPACE(sizeof(BYTE)); 936 pFormat++; 937 if (*pFormat == 'n' || *pFormat == 'N') 938 { 939 pFormat++; 940 *pOut++ = FMT_DATE_MIN_0; 941 TRACE("nn\n"); 942 } 943 else 944 { 945 *pOut++ = FMT_DATE_MIN; 946 TRACE("n\n"); 947 } 948 fmt_state &= ~FMT_STATE_OPEN_COPY; 949 } 950 else if ((*pFormat == 'q' || *pFormat == 'Q') && COULD_BE(FMT_TYPE_DATE)) 951 { 952 /* Date formats: Quarter specifier 953 * Other formats: Literal 954 * Types the format if found 955 */ 956 header->type = FMT_TYPE_DATE; 957 NEED_SPACE(sizeof(BYTE)); 958 *pOut++ = FMT_DATE_QUARTER; 959 pFormat++; 960 fmt_state &= ~FMT_STATE_OPEN_COPY; 961 TRACE("quarter\n"); 962 } 963 else if ((*pFormat == 's' || *pFormat == 'S') && COULD_BE(FMT_TYPE_DATE)) 964 { 965 /* Date formats: Second specifier 966 * Other formats: Literal 967 * Types the format if found 968 */ 969 header->type = FMT_TYPE_DATE; 970 NEED_SPACE(sizeof(BYTE)); 971 pFormat++; 972 if (*pFormat == 's' || *pFormat == 'S') 973 { 974 pFormat++; 975 *pOut++ = FMT_DATE_SEC_0; 976 TRACE("ss\n"); 977 } 978 else 979 { 980 *pOut++ = FMT_DATE_SEC; 981 TRACE("s\n"); 982 } 983 fmt_state &= ~FMT_STATE_OPEN_COPY; 984 } 985 else if ((*pFormat == 't' || *pFormat == 'T') && 986 !_wcsnicmp(pFormat, szTTTTT, ARRAY_SIZE(szTTTTT))) 987 { 988 /* Date formats: System time specifier 989 * Other formats: Literal 990 * Types the format if found 991 */ 992 header->type = FMT_TYPE_DATE; 993 pFormat += ARRAY_SIZE(szTTTTT); 994 NEED_SPACE(sizeof(BYTE)); 995 *pOut++ = FMT_DATE_TIME_SYS; 996 fmt_state &= ~FMT_STATE_OPEN_COPY; 997 } 998 else if ((*pFormat == 'w' || *pFormat == 'W') && COULD_BE(FMT_TYPE_DATE)) 999 { 1000 /* Date formats: Week of the year/Day of the week 1001 * Other formats: Literal 1002 * Types the format if found 1003 */ 1004 header->type = FMT_TYPE_DATE; 1005 pFormat++; 1006 if (*pFormat == 'w' || *pFormat == 'W') 1007 { 1008 NEED_SPACE(3 * sizeof(BYTE)); 1009 pFormat++; 1010 *pOut++ = FMT_DATE_WEEK_YEAR; 1011 *pOut++ = nFirstDay; 1012 *pOut++ = nFirstWeek; 1013 TRACE("ww\n"); 1014 } 1015 else 1016 { 1017 NEED_SPACE(2 * sizeof(BYTE)); 1018 *pOut++ = FMT_DATE_DAY_WEEK; 1019 *pOut++ = nFirstDay; 1020 TRACE("w\n"); 1021 } 1022 1023 fmt_state &= ~FMT_STATE_OPEN_COPY; 1024 } 1025 else if ((*pFormat == 'y' || *pFormat == 'Y') && COULD_BE(FMT_TYPE_DATE)) 1026 { 1027 /* Date formats: Day of year/Year specifier 1028 * Other formats: Literal 1029 * Types the format if found 1030 */ 1031 int count = -1; 1032 header->type = FMT_TYPE_DATE; 1033 while ((*pFormat == 'y' || *pFormat == 'Y') && count < 4) 1034 { 1035 pFormat++; 1036 count++; 1037 } 1038 if (count == 2) 1039 { 1040 count--; /* 'yyy' has no meaning, despite what MSDN says */ 1041 pFormat--; 1042 } 1043 NEED_SPACE(sizeof(BYTE)); 1044 *pOut++ = FMT_DATE_YEAR_DOY + count; 1045 fmt_state &= ~FMT_STATE_OPEN_COPY; 1046 TRACE("%d y's\n", count + 1); 1047 } 1048 /* ------------- 1049 * String tokens 1050 * ------------- 1051 */ 1052 else if (*pFormat == '@' && COULD_BE(FMT_TYPE_STRING)) 1053 { 1054 /* String formats: Character from string or space if no char 1055 * Other formats: Literal 1056 * Types the format if found 1057 */ 1058 header->type = FMT_TYPE_STRING; 1059 NEED_SPACE(2 * sizeof(BYTE)); 1060 *pOut++ = FMT_STR_COPY_SPACE; 1061 *pOut = 0x0; 1062 while (*pFormat == '@') 1063 { 1064 *pOut = *pOut + 1; 1065 str_header->copy_chars++; 1066 pFormat++; 1067 } 1068 TRACE("%d @'s\n", *pOut); 1069 pOut++; 1070 fmt_state &= ~FMT_STATE_OPEN_COPY; 1071 } 1072 else if (*pFormat == '&' && COULD_BE(FMT_TYPE_STRING)) 1073 { 1074 /* String formats: Character from string or skip if no char 1075 * Other formats: Literal 1076 * Types the format if found 1077 */ 1078 header->type = FMT_TYPE_STRING; 1079 NEED_SPACE(2 * sizeof(BYTE)); 1080 *pOut++ = FMT_STR_COPY_SKIP; 1081 *pOut = 0x0; 1082 while (*pFormat == '&') 1083 { 1084 *pOut = *pOut + 1; 1085 str_header->copy_chars++; 1086 pFormat++; 1087 } 1088 TRACE("%d &'s\n", *pOut); 1089 pOut++; 1090 fmt_state &= ~FMT_STATE_OPEN_COPY; 1091 } 1092 else if ((*pFormat == '<' || *pFormat == '>') && COULD_BE(FMT_TYPE_STRING)) 1093 { 1094 /* String formats: Use upper/lower case 1095 * Other formats: Literal 1096 * Types the format if found 1097 */ 1098 header->type = FMT_TYPE_STRING; 1099 if (*pFormat == '<') 1100 str_header->flags |= FMT_FLAG_LT; 1101 else 1102 str_header->flags |= FMT_FLAG_GT; 1103 TRACE("to %s case\n", *pFormat == '<' ? "lower" : "upper"); 1104 pFormat++; 1105 fmt_state &= ~FMT_STATE_OPEN_COPY; 1106 } 1107 else if (*pFormat == '!' && COULD_BE(FMT_TYPE_STRING)) 1108 { 1109 /* String formats: Copy right to left 1110 * Other formats: Literal 1111 * Types the format if found 1112 */ 1113 header->type = FMT_TYPE_STRING; 1114 str_header->flags |= FMT_FLAG_RTL; 1115 pFormat++; 1116 fmt_state &= ~FMT_STATE_OPEN_COPY; 1117 TRACE("copy right-to-left\n"); 1118 } 1119 /* -------- 1120 * Literals 1121 * -------- 1122 */ 1123 /* FIXME: [ seems to be ignored */ 1124 else 1125 { 1126 if (*pFormat == '%' && header->type == FMT_TYPE_NUMBER) 1127 { 1128 /* Number formats: Percentage indicator, also a literal 1129 * Other formats: Literal 1130 * Doesn't type the format 1131 */ 1132 num_header->flags |= FMT_FLAG_PERCENT; 1133 } 1134 1135 if (fmt_state & FMT_STATE_OPEN_COPY) 1136 { 1137 pOut[-1] = pOut[-1] + 1; /* Increase the length of the open copy */ 1138 TRACE("extend copy (char '%c'), length now %d\n", *pFormat, pOut[-1]); 1139 } 1140 else 1141 { 1142 /* Create a new open copy */ 1143 TRACE("New copy (char '%c')\n", *pFormat); 1144 NEED_SPACE(3 * sizeof(BYTE)); 1145 *pOut++ = FMT_GEN_COPY; 1146 *pOut++ = pFormat - lpszFormat; 1147 *pOut++ = 0x1; 1148 fmt_state |= FMT_STATE_OPEN_COPY; 1149 } 1150 pFormat++; 1151 } 1152 } 1153 1154 *pOut++ = FMT_GEN_END; 1155 1156 header->size = pOut - rgbTok; 1157 if (pcbActual) 1158 *pcbActual = header->size; 1159 1160 return S_OK; 1161 } 1162 1163 /* Number formatting state flags */ 1164 #define NUM_WROTE_DEC 0x01 /* Written the decimal separator */ 1165 #define NUM_WRITE_ON 0x02 /* Started to write the number */ 1166 #define NUM_WROTE_SIGN 0x04 /* Written the negative sign */ 1167 1168 /* Format a variant using a number format */ 1169 static HRESULT VARIANT_FormatNumber(LPVARIANT pVarIn, LPOLESTR lpszFormat, 1170 LPBYTE rgbTok, ULONG dwFlags, 1171 BSTR *pbstrOut, LCID lcid) 1172 { 1173 BYTE rgbDig[256], *prgbDig; 1174 NUMPARSE np; 1175 int have_int, need_int = 0, have_frac, need_frac, exponent = 0, pad = 0; 1176 WCHAR buff[256], *pBuff = buff; 1177 WCHAR thousandSeparator[32]; 1178 VARIANT vString, vBool; 1179 DWORD dwState = 0; 1180 FMT_HEADER *header = (FMT_HEADER*)rgbTok; 1181 FMT_NUMBER_HEADER *numHeader; 1182 const BYTE* pToken = NULL; 1183 HRESULT hRes = S_OK; 1184 1185 TRACE("(%s,%s,%p,0x%08x,%p,0x%08x)\n", debugstr_variant(pVarIn), debugstr_w(lpszFormat), 1186 rgbTok, dwFlags, pbstrOut, lcid); 1187 1188 V_VT(&vString) = VT_EMPTY; 1189 V_VT(&vBool) = VT_BOOL; 1190 1191 if (V_TYPE(pVarIn) == VT_EMPTY || V_TYPE(pVarIn) == VT_NULL) 1192 { 1193 have_int = have_frac = 0; 1194 numHeader = (FMT_NUMBER_HEADER*)(rgbTok + FmtGetNull(header)); 1195 V_BOOL(&vBool) = VARIANT_FALSE; 1196 } 1197 else 1198 { 1199 /* Get a number string from pVarIn, and parse it */ 1200 hRes = VariantChangeTypeEx(&vString, pVarIn, lcid, VARIANT_NOUSEROVERRIDE, VT_BSTR); 1201 if (FAILED(hRes)) 1202 return hRes; 1203 1204 np.cDig = sizeof(rgbDig); 1205 np.dwInFlags = NUMPRS_STD; 1206 hRes = VarParseNumFromStr(V_BSTR(&vString), lcid, 0, &np, rgbDig); 1207 if (FAILED(hRes)) 1208 return hRes; 1209 1210 have_int = np.cDig; 1211 have_frac = 0; 1212 exponent = np.nPwr10; 1213 1214 /* Figure out which format to use */ 1215 if (np.dwOutFlags & NUMPRS_NEG) 1216 { 1217 numHeader = (FMT_NUMBER_HEADER*)(rgbTok + FmtGetNegative(header)); 1218 V_BOOL(&vBool) = VARIANT_TRUE; 1219 } 1220 else if (have_int == 1 && !exponent && rgbDig[0] == 0) 1221 { 1222 numHeader = (FMT_NUMBER_HEADER*)(rgbTok + FmtGetZero(header)); 1223 V_BOOL(&vBool) = VARIANT_FALSE; 1224 } 1225 else 1226 { 1227 numHeader = (FMT_NUMBER_HEADER*)(rgbTok + FmtGetPositive(header)); 1228 V_BOOL(&vBool) = VARIANT_TRUE; 1229 } 1230 1231 TRACE("num header: flags = 0x%x, mult=%d, div=%d, whole=%d, fract=%d\n", 1232 numHeader->flags, numHeader->multiplier, numHeader->divisor, 1233 numHeader->whole, numHeader->fractional); 1234 1235 need_int = numHeader->whole; 1236 need_frac = numHeader->fractional; 1237 1238 if (numHeader->flags & FMT_FLAG_PERCENT && 1239 !(have_int == 1 && !exponent && rgbDig[0] == 0)) 1240 exponent += 2; 1241 1242 if (numHeader->flags & FMT_FLAG_EXPONENT) 1243 { 1244 /* Exponent format: length of the integral number part is fixed and 1245 specified by the format. */ 1246 pad = need_int - have_int; 1247 exponent -= pad; 1248 if (pad < 0) 1249 { 1250 have_int = need_int; 1251 have_frac -= pad; 1252 pad = 0; 1253 } 1254 } 1255 else 1256 { 1257 /* Convert the exponent */ 1258 pad = max(exponent, -have_int); 1259 exponent -= pad; 1260 if (pad < 0) 1261 { 1262 have_int += pad; 1263 have_frac = -pad; 1264 pad = 0; 1265 } 1266 if(exponent < 0 && exponent > (-256 + have_int + have_frac)) 1267 { 1268 /* Remove exponent notation */ 1269 memmove(rgbDig - exponent, rgbDig, have_int + have_frac); 1270 ZeroMemory(rgbDig, -exponent); 1271 have_frac -= exponent; 1272 exponent = 0; 1273 } 1274 } 1275 1276 /* Rounding the number */ 1277 if (have_frac > need_frac) 1278 { 1279 prgbDig = &rgbDig[have_int + need_frac]; 1280 have_frac = need_frac; 1281 if (*prgbDig >= 5) 1282 { 1283 while (prgbDig-- > rgbDig && *prgbDig == 9) 1284 *prgbDig = 0; 1285 if (prgbDig < rgbDig) 1286 { 1287 /* We reached the first digit and that was also a 9 */ 1288 rgbDig[0] = 1; 1289 if (numHeader->flags & FMT_FLAG_EXPONENT) 1290 exponent++; 1291 else 1292 { 1293 rgbDig[have_int + need_frac] = 0; 1294 if (exponent < 0) 1295 exponent++; 1296 else 1297 have_int++; 1298 } 1299 } 1300 else 1301 (*prgbDig)++; 1302 } 1303 /* We converted trailing digits to zeroes => have_frac has changed */ 1304 while (have_frac > 0 && rgbDig[have_int + have_frac - 1] == 0) 1305 have_frac--; 1306 } 1307 TRACE("have_int=%d,need_int=%d,have_frac=%d,need_frac=%d,pad=%d,exp=%d\n", 1308 have_int, need_int, have_frac, need_frac, pad, exponent); 1309 } 1310 1311 if (numHeader->flags & FMT_FLAG_THOUSANDS) 1312 { 1313 if (!GetLocaleInfoW(lcid, LOCALE_STHOUSAND, thousandSeparator, ARRAY_SIZE(thousandSeparator))) 1314 { 1315 thousandSeparator[0] = ','; 1316 thousandSeparator[1] = 0; 1317 } 1318 } 1319 1320 pToken = (const BYTE*)numHeader + sizeof(FMT_NUMBER_HEADER); 1321 prgbDig = rgbDig; 1322 1323 while (SUCCEEDED(hRes) && *pToken != FMT_GEN_END) 1324 { 1325 WCHAR defaultChar = '?'; 1326 DWORD boolFlag, localeValue = 0; 1327 BOOL shouldAdvance = TRUE; 1328 1329 if (pToken - rgbTok > header->size) 1330 { 1331 ERR("Ran off the end of the format!\n"); 1332 hRes = E_INVALIDARG; 1333 goto VARIANT_FormatNumber_Exit; 1334 } 1335 1336 switch (*pToken) 1337 { 1338 case FMT_GEN_COPY: 1339 TRACE("copy %s\n", debugstr_wn(lpszFormat + pToken[1], pToken[2])); 1340 memcpy(pBuff, lpszFormat + pToken[1], pToken[2] * sizeof(WCHAR)); 1341 pBuff += pToken[2]; 1342 pToken += 2; 1343 break; 1344 1345 case FMT_GEN_INLINE: 1346 pToken += 2; 1347 TRACE("copy %s\n", debugstr_a((LPCSTR)pToken)); 1348 while (*pToken) 1349 *pBuff++ = *pToken++; 1350 break; 1351 1352 case FMT_NUM_YES_NO: 1353 boolFlag = VAR_BOOLYESNO; 1354 goto VARIANT_FormatNumber_Bool; 1355 1356 case FMT_NUM_ON_OFF: 1357 boolFlag = VAR_BOOLONOFF; 1358 goto VARIANT_FormatNumber_Bool; 1359 1360 case FMT_NUM_TRUE_FALSE: 1361 boolFlag = VAR_LOCALBOOL; 1362 1363 VARIANT_FormatNumber_Bool: 1364 { 1365 BSTR boolStr = NULL; 1366 1367 if (pToken[1] != FMT_GEN_END) 1368 { 1369 ERR("Boolean token not at end of format!\n"); 1370 hRes = E_INVALIDARG; 1371 goto VARIANT_FormatNumber_Exit; 1372 } 1373 hRes = VarBstrFromBool(V_BOOL(&vBool), lcid, boolFlag, &boolStr); 1374 if (SUCCEEDED(hRes)) 1375 { 1376 lstrcpyW(pBuff, boolStr); 1377 SysFreeString(boolStr); 1378 while (*pBuff) 1379 pBuff++; 1380 } 1381 } 1382 break; 1383 1384 case FMT_NUM_DECIMAL: 1385 if ((np.dwOutFlags & NUMPRS_NEG) && !(dwState & NUM_WROTE_SIGN) && !header->starts[1]) 1386 { 1387 /* last chance for a negative sign in the .# case */ 1388 TRACE("write negative sign\n"); 1389 localeValue = LOCALE_SNEGATIVESIGN; 1390 defaultChar = '-'; 1391 dwState |= NUM_WROTE_SIGN; 1392 shouldAdvance = FALSE; 1393 break; 1394 } 1395 TRACE("write decimal separator\n"); 1396 localeValue = LOCALE_SDECIMAL; 1397 defaultChar = '.'; 1398 dwState |= NUM_WROTE_DEC; 1399 break; 1400 1401 case FMT_NUM_CURRENCY: 1402 TRACE("write currency symbol\n"); 1403 localeValue = LOCALE_SCURRENCY; 1404 defaultChar = '$'; 1405 break; 1406 1407 case FMT_NUM_EXP_POS_U: 1408 case FMT_NUM_EXP_POS_L: 1409 case FMT_NUM_EXP_NEG_U: 1410 case FMT_NUM_EXP_NEG_L: 1411 if (*pToken == FMT_NUM_EXP_POS_L || *pToken == FMT_NUM_EXP_NEG_L) 1412 *pBuff++ = 'e'; 1413 else 1414 *pBuff++ = 'E'; 1415 if (exponent < 0) 1416 { 1417 *pBuff++ = '-'; 1418 swprintf(pBuff, szPercentZeroStar_d, pToken[1], -exponent); 1419 } 1420 else 1421 { 1422 if (*pToken == FMT_NUM_EXP_POS_L || *pToken == FMT_NUM_EXP_POS_U) 1423 *pBuff++ = '+'; 1424 swprintf(pBuff, szPercentZeroStar_d, pToken[1], exponent); 1425 } 1426 while (*pBuff) 1427 pBuff++; 1428 pToken++; 1429 break; 1430 1431 case FMT_NUM_COPY_ZERO: 1432 dwState |= NUM_WRITE_ON; 1433 /* Fall through */ 1434 1435 case FMT_NUM_COPY_SKIP: 1436 TRACE("write %d %sdigits or %s\n", pToken[1], 1437 dwState & NUM_WROTE_DEC ? "fractional " : "", 1438 *pToken == FMT_NUM_COPY_ZERO ? "0" : "skip"); 1439 1440 if (dwState & NUM_WROTE_DEC) 1441 { 1442 int count, i; 1443 1444 if (!(numHeader->flags & FMT_FLAG_EXPONENT) && exponent < 0) 1445 { 1446 /* Pad with 0 before writing the fractional digits */ 1447 pad = max(exponent, -pToken[1]); 1448 exponent -= pad; 1449 count = min(have_frac, pToken[1] + pad); 1450 for (i = 0; i > pad; i--) 1451 *pBuff++ = '0'; 1452 } 1453 else 1454 count = min(have_frac, pToken[1]); 1455 1456 pad += pToken[1] - count; 1457 have_frac -= count; 1458 while (count--) 1459 *pBuff++ = '0' + *prgbDig++; 1460 if (*pToken == FMT_NUM_COPY_ZERO) 1461 { 1462 for (; pad > 0; pad--) 1463 *pBuff++ = '0'; /* Write zeros for missing trailing digits */ 1464 } 1465 } 1466 else 1467 { 1468 int count, count_max, position; 1469 1470 if ((np.dwOutFlags & NUMPRS_NEG) && !(dwState & NUM_WROTE_SIGN) && !header->starts[1]) 1471 { 1472 TRACE("write negative sign\n"); 1473 localeValue = LOCALE_SNEGATIVESIGN; 1474 defaultChar = '-'; 1475 dwState |= NUM_WROTE_SIGN; 1476 shouldAdvance = FALSE; 1477 break; 1478 } 1479 1480 position = have_int + pad; 1481 if (dwState & NUM_WRITE_ON) 1482 position = max(position, need_int); 1483 need_int -= pToken[1]; 1484 count_max = have_int + pad - need_int; 1485 if (count_max < 0) 1486 count_max = 0; 1487 if (dwState & NUM_WRITE_ON) 1488 { 1489 count = pToken[1] - count_max; 1490 TRACE("write %d leading zeros\n", count); 1491 while (count-- > 0) 1492 { 1493 *pBuff++ = '0'; 1494 if ((numHeader->flags & FMT_FLAG_THOUSANDS) && 1495 position > 1 && (--position % 3) == 0) 1496 { 1497 int k; 1498 TRACE("write thousand separator\n"); 1499 for (k = 0; thousandSeparator[k]; k++) 1500 *pBuff++ = thousandSeparator[k]; 1501 } 1502 } 1503 } 1504 if (*pToken == FMT_NUM_COPY_ZERO || have_int > 1 || 1505 (have_int > 0 && *prgbDig > 0)) 1506 { 1507 count = min(count_max, have_int); 1508 count_max -= count; 1509 have_int -= count; 1510 TRACE("write %d whole number digits\n", count); 1511 while (count--) 1512 { 1513 dwState |= NUM_WRITE_ON; 1514 *pBuff++ = '0' + *prgbDig++; 1515 if ((numHeader->flags & FMT_FLAG_THOUSANDS) && 1516 position > 1 && (--position % 3) == 0) 1517 { 1518 int k; 1519 TRACE("write thousand separator\n"); 1520 for (k = 0; thousandSeparator[k]; k++) 1521 *pBuff++ = thousandSeparator[k]; 1522 } 1523 } 1524 } 1525 count = min(count_max, pad); 1526 pad -= count; 1527 TRACE("write %d whole trailing 0's\n", count); 1528 while (count--) 1529 { 1530 *pBuff++ = '0'; 1531 if ((numHeader->flags & FMT_FLAG_THOUSANDS) && 1532 position > 1 && (--position % 3) == 0) 1533 { 1534 int k; 1535 TRACE("write thousand separator\n"); 1536 for (k = 0; thousandSeparator[k]; k++) 1537 *pBuff++ = thousandSeparator[k]; 1538 } 1539 } 1540 } 1541 pToken++; 1542 break; 1543 1544 default: 1545 ERR("Unknown token 0x%02x!\n", *pToken); 1546 hRes = E_INVALIDARG; 1547 goto VARIANT_FormatNumber_Exit; 1548 } 1549 if (localeValue) 1550 { 1551 if (GetLocaleInfoW(lcid, localeValue, pBuff, ARRAY_SIZE(buff)-(pBuff-buff))) 1552 { 1553 TRACE("added %s\n", debugstr_w(pBuff)); 1554 while (*pBuff) 1555 pBuff++; 1556 } 1557 else 1558 { 1559 TRACE("added %d '%c'\n", defaultChar, defaultChar); 1560 *pBuff++ = defaultChar; 1561 } 1562 } 1563 if (shouldAdvance) 1564 pToken++; 1565 } 1566 1567 VARIANT_FormatNumber_Exit: 1568 VariantClear(&vString); 1569 *pBuff = '\0'; 1570 TRACE("buff is %s\n", debugstr_w(buff)); 1571 if (SUCCEEDED(hRes)) 1572 { 1573 *pbstrOut = SysAllocString(buff); 1574 if (!*pbstrOut) 1575 hRes = E_OUTOFMEMORY; 1576 } 1577 return hRes; 1578 } 1579 1580 /* Format a variant using a date format */ 1581 static HRESULT VARIANT_FormatDate(LPVARIANT pVarIn, LPOLESTR lpszFormat, 1582 LPBYTE rgbTok, ULONG dwFlags, 1583 BSTR *pbstrOut, LCID lcid) 1584 { 1585 WCHAR buff[256], *pBuff = buff; 1586 VARIANT vDate; 1587 UDATE udate; 1588 FMT_HEADER *header = (FMT_HEADER*)rgbTok; 1589 FMT_DATE_HEADER *dateHeader; 1590 const BYTE* pToken = NULL; 1591 HRESULT hRes; 1592 1593 TRACE("(%s,%s,%p,0x%08x,%p,0x%08x)\n", debugstr_variant(pVarIn), 1594 debugstr_w(lpszFormat), rgbTok, dwFlags, pbstrOut, lcid); 1595 1596 V_VT(&vDate) = VT_EMPTY; 1597 1598 if (V_TYPE(pVarIn) == VT_EMPTY || V_TYPE(pVarIn) == VT_NULL) 1599 { 1600 dateHeader = (FMT_DATE_HEADER*)(rgbTok + FmtGetNegative(header)); 1601 V_DATE(&vDate) = 0; 1602 } 1603 else 1604 { 1605 USHORT usFlags = dwFlags & VARIANT_CALENDAR_HIJRI ? VAR_CALENDAR_HIJRI : 0; 1606 1607 hRes = VariantChangeTypeEx(&vDate, pVarIn, lcid, usFlags, VT_DATE); 1608 if (FAILED(hRes)) 1609 return hRes; 1610 dateHeader = (FMT_DATE_HEADER*)(rgbTok + FmtGetPositive(header)); 1611 } 1612 1613 hRes = VarUdateFromDate(V_DATE(&vDate), 0 /* FIXME: flags? */, &udate); 1614 if (FAILED(hRes)) 1615 return hRes; 1616 pToken = (const BYTE*)dateHeader + sizeof(FMT_DATE_HEADER); 1617 1618 while (*pToken != FMT_GEN_END) 1619 { 1620 DWORD dwVal = 0, localeValue = 0, dwFmt = 0; 1621 LPCWSTR szPrintFmt = NULL; 1622 WCHAR defaultChar = '?'; 1623 1624 if (pToken - rgbTok > header->size) 1625 { 1626 ERR("Ran off the end of the format!\n"); 1627 hRes = E_INVALIDARG; 1628 goto VARIANT_FormatDate_Exit; 1629 } 1630 1631 switch (*pToken) 1632 { 1633 case FMT_GEN_COPY: 1634 TRACE("copy %s\n", debugstr_wn(lpszFormat + pToken[1], pToken[2])); 1635 memcpy(pBuff, lpszFormat + pToken[1], pToken[2] * sizeof(WCHAR)); 1636 pBuff += pToken[2]; 1637 pToken += 2; 1638 break; 1639 1640 case FMT_GEN_INLINE: 1641 pToken += 2; 1642 TRACE("copy %s\n", debugstr_a((LPCSTR)pToken)); 1643 while (*pToken) 1644 *pBuff++ = *pToken++; 1645 break; 1646 1647 case FMT_DATE_TIME_SEP: 1648 TRACE("time separator\n"); 1649 localeValue = LOCALE_STIME; 1650 defaultChar = ':'; 1651 break; 1652 1653 case FMT_DATE_DATE_SEP: 1654 TRACE("date separator\n"); 1655 localeValue = LOCALE_SDATE; 1656 defaultChar = '/'; 1657 break; 1658 1659 case FMT_DATE_GENERAL: 1660 { 1661 BSTR date = NULL; 1662 WCHAR *pDate; 1663 hRes = VarBstrFromDate(V_DATE(&vDate), lcid, 0, &date); 1664 if (FAILED(hRes)) 1665 goto VARIANT_FormatDate_Exit; 1666 pDate = date; 1667 while (*pDate) 1668 *pBuff++ = *pDate++; 1669 SysFreeString(date); 1670 } 1671 break; 1672 1673 case FMT_DATE_QUARTER: 1674 if (udate.st.wMonth <= 3) 1675 *pBuff++ = '1'; 1676 else if (udate.st.wMonth <= 6) 1677 *pBuff++ = '2'; 1678 else if (udate.st.wMonth <= 9) 1679 *pBuff++ = '3'; 1680 else 1681 *pBuff++ = '4'; 1682 break; 1683 1684 case FMT_DATE_TIME_SYS: 1685 { 1686 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */ 1687 BSTR date = NULL; 1688 WCHAR *pDate; 1689 hRes = VarBstrFromDate(V_DATE(&vDate), lcid, VAR_TIMEVALUEONLY, &date); 1690 if (FAILED(hRes)) 1691 goto VARIANT_FormatDate_Exit; 1692 pDate = date; 1693 while (*pDate) 1694 *pBuff++ = *pDate++; 1695 SysFreeString(date); 1696 } 1697 break; 1698 1699 case FMT_DATE_DAY: 1700 szPrintFmt = szPercent_d; 1701 dwVal = udate.st.wDay; 1702 break; 1703 1704 case FMT_DATE_DAY_0: 1705 szPrintFmt = szPercentZeroTwo_d; 1706 dwVal = udate.st.wDay; 1707 break; 1708 1709 case FMT_DATE_DAY_SHORT: 1710 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */ 1711 TRACE("short day\n"); 1712 localeValue = LOCALE_SABBREVDAYNAME1 + (udate.st.wDayOfWeek + 6)%7; 1713 defaultChar = '?'; 1714 break; 1715 1716 case FMT_DATE_DAY_LONG: 1717 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */ 1718 TRACE("long day\n"); 1719 localeValue = LOCALE_SDAYNAME1 + (udate.st.wDayOfWeek + 6)%7; 1720 defaultChar = '?'; 1721 break; 1722 1723 case FMT_DATE_SHORT: 1724 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */ 1725 dwFmt = LOCALE_SSHORTDATE; 1726 break; 1727 1728 case FMT_DATE_LONG: 1729 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */ 1730 dwFmt = LOCALE_SLONGDATE; 1731 break; 1732 1733 case FMT_DATE_MEDIUM: 1734 FIXME("Medium date treated as long date\n"); 1735 dwFmt = LOCALE_SLONGDATE; 1736 break; 1737 1738 case FMT_DATE_DAY_WEEK: 1739 szPrintFmt = szPercent_d; 1740 if (pToken[1]) 1741 dwVal = udate.st.wDayOfWeek + 2 - pToken[1]; 1742 else 1743 { 1744 GetLocaleInfoW(lcid,LOCALE_RETURN_NUMBER|LOCALE_IFIRSTDAYOFWEEK, 1745 (LPWSTR)&dwVal, sizeof(dwVal)/sizeof(WCHAR)); 1746 dwVal = udate.st.wDayOfWeek + 1 - dwVal; 1747 } 1748 pToken++; 1749 break; 1750 1751 case FMT_DATE_WEEK_YEAR: 1752 szPrintFmt = szPercent_d; 1753 dwVal = udate.wDayOfYear / 7 + 1; 1754 pToken += 2; 1755 FIXME("Ignoring nFirstDay of %d, nFirstWeek of %d\n", pToken[0], pToken[1]); 1756 break; 1757 1758 case FMT_DATE_MON: 1759 szPrintFmt = szPercent_d; 1760 dwVal = udate.st.wMonth; 1761 break; 1762 1763 case FMT_DATE_MON_0: 1764 szPrintFmt = szPercentZeroTwo_d; 1765 dwVal = udate.st.wMonth; 1766 break; 1767 1768 case FMT_DATE_MON_SHORT: 1769 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */ 1770 TRACE("short month\n"); 1771 localeValue = LOCALE_SABBREVMONTHNAME1 + udate.st.wMonth - 1; 1772 defaultChar = '?'; 1773 break; 1774 1775 case FMT_DATE_MON_LONG: 1776 /* FIXME: VARIANT_CALENDAR HIJRI should cause Hijri output */ 1777 TRACE("long month\n"); 1778 localeValue = LOCALE_SMONTHNAME1 + udate.st.wMonth - 1; 1779 defaultChar = '?'; 1780 break; 1781 1782 case FMT_DATE_YEAR_DOY: 1783 szPrintFmt = szPercent_d; 1784 dwVal = udate.wDayOfYear; 1785 break; 1786 1787 case FMT_DATE_YEAR_0: 1788 szPrintFmt = szPercentZeroTwo_d; 1789 dwVal = udate.st.wYear % 100; 1790 break; 1791 1792 case FMT_DATE_YEAR_LONG: 1793 szPrintFmt = szPercent_d; 1794 dwVal = udate.st.wYear; 1795 break; 1796 1797 case FMT_DATE_MIN: 1798 szPrintFmt = szPercent_d; 1799 dwVal = udate.st.wMinute; 1800 break; 1801 1802 case FMT_DATE_MIN_0: 1803 szPrintFmt = szPercentZeroTwo_d; 1804 dwVal = udate.st.wMinute; 1805 break; 1806 1807 case FMT_DATE_SEC: 1808 szPrintFmt = szPercent_d; 1809 dwVal = udate.st.wSecond; 1810 break; 1811 1812 case FMT_DATE_SEC_0: 1813 szPrintFmt = szPercentZeroTwo_d; 1814 dwVal = udate.st.wSecond; 1815 break; 1816 1817 case FMT_DATE_HOUR: 1818 szPrintFmt = szPercent_d; 1819 dwVal = udate.st.wHour; 1820 break; 1821 1822 case FMT_DATE_HOUR_0: 1823 case FMT_DATE_TIME_UNK2: 1824 szPrintFmt = szPercentZeroTwo_d; 1825 dwVal = udate.st.wHour; 1826 break; 1827 1828 case FMT_DATE_HOUR_12: 1829 szPrintFmt = szPercent_d; 1830 dwVal = udate.st.wHour ? udate.st.wHour > 12 ? udate.st.wHour - 12 : udate.st.wHour : 12; 1831 break; 1832 1833 case FMT_DATE_HOUR_12_0: 1834 szPrintFmt = szPercentZeroTwo_d; 1835 dwVal = udate.st.wHour ? udate.st.wHour > 12 ? udate.st.wHour - 12 : udate.st.wHour : 12; 1836 break; 1837 1838 case FMT_DATE_AMPM_SYS1: 1839 case FMT_DATE_AMPM_SYS2: 1840 localeValue = udate.st.wHour < 12 ? LOCALE_S1159 : LOCALE_S2359; 1841 defaultChar = '?'; 1842 break; 1843 1844 case FMT_DATE_AMPM_UPPER: 1845 *pBuff++ = udate.st.wHour < 12 ? 'A' : 'P'; 1846 *pBuff++ = 'M'; 1847 break; 1848 1849 case FMT_DATE_A_UPPER: 1850 *pBuff++ = udate.st.wHour < 12 ? 'A' : 'P'; 1851 break; 1852 1853 case FMT_DATE_AMPM_LOWER: 1854 *pBuff++ = udate.st.wHour < 12 ? 'a' : 'p'; 1855 *pBuff++ = 'm'; 1856 break; 1857 1858 case FMT_DATE_A_LOWER: 1859 *pBuff++ = udate.st.wHour < 12 ? 'a' : 'p'; 1860 break; 1861 1862 default: 1863 ERR("Unknown token 0x%02x!\n", *pToken); 1864 hRes = E_INVALIDARG; 1865 goto VARIANT_FormatDate_Exit; 1866 } 1867 if (localeValue) 1868 { 1869 *pBuff = '\0'; 1870 if (GetLocaleInfoW(lcid, localeValue, pBuff, ARRAY_SIZE(buff)-(pBuff-buff))) 1871 { 1872 TRACE("added %s\n", debugstr_w(pBuff)); 1873 while (*pBuff) 1874 pBuff++; 1875 } 1876 else 1877 { 1878 TRACE("added %d %c\n", defaultChar, defaultChar); 1879 *pBuff++ = defaultChar; 1880 } 1881 } 1882 else if (dwFmt) 1883 { 1884 WCHAR fmt_buff[80]; 1885 1886 if (!GetLocaleInfoW(lcid, dwFmt, fmt_buff, ARRAY_SIZE(fmt_buff)) || 1887 !get_date_format(lcid, 0, &udate.st, fmt_buff, pBuff, ARRAY_SIZE(buff)-(pBuff-buff))) 1888 { 1889 hRes = E_INVALIDARG; 1890 goto VARIANT_FormatDate_Exit; 1891 } 1892 while (*pBuff) 1893 pBuff++; 1894 } 1895 else if (szPrintFmt) 1896 { 1897 swprintf(pBuff, szPrintFmt, dwVal); 1898 while (*pBuff) 1899 pBuff++; 1900 } 1901 pToken++; 1902 } 1903 1904 VARIANT_FormatDate_Exit: 1905 *pBuff = '\0'; 1906 TRACE("buff is %s\n", debugstr_w(buff)); 1907 if (SUCCEEDED(hRes)) 1908 { 1909 *pbstrOut = SysAllocString(buff); 1910 if (!*pbstrOut) 1911 hRes = E_OUTOFMEMORY; 1912 } 1913 return hRes; 1914 } 1915 1916 /* Format a variant using a string format */ 1917 static HRESULT VARIANT_FormatString(LPVARIANT pVarIn, LPOLESTR lpszFormat, 1918 LPBYTE rgbTok, ULONG dwFlags, 1919 BSTR *pbstrOut, LCID lcid) 1920 { 1921 static WCHAR szEmpty[] = { '\0' }; 1922 WCHAR buff[256], *pBuff = buff; 1923 WCHAR *pSrc; 1924 FMT_HEADER *header = (FMT_HEADER*)rgbTok; 1925 FMT_STRING_HEADER *strHeader; 1926 const BYTE* pToken = NULL; 1927 VARIANT vStr; 1928 int blanks_first; 1929 BOOL bUpper = FALSE; 1930 HRESULT hRes = S_OK; 1931 1932 TRACE("%s,%s,%p,0x%08x,%p,0x%08x)\n", debugstr_variant(pVarIn), debugstr_w(lpszFormat), 1933 rgbTok, dwFlags, pbstrOut, lcid); 1934 1935 V_VT(&vStr) = VT_EMPTY; 1936 1937 if (V_TYPE(pVarIn) == VT_EMPTY || V_TYPE(pVarIn) == VT_NULL) 1938 { 1939 strHeader = (FMT_STRING_HEADER*)(rgbTok + FmtGetNegative(header)); 1940 V_BSTR(&vStr) = szEmpty; 1941 } 1942 else 1943 { 1944 hRes = VariantChangeTypeEx(&vStr, pVarIn, lcid, VARIANT_NOUSEROVERRIDE, VT_BSTR); 1945 if (FAILED(hRes)) 1946 return hRes; 1947 1948 if (V_BSTR(&vStr)[0] == '\0') 1949 strHeader = (FMT_STRING_HEADER*)(rgbTok + FmtGetNegative(header)); 1950 else 1951 strHeader = (FMT_STRING_HEADER*)(rgbTok + FmtGetPositive(header)); 1952 } 1953 pSrc = V_BSTR(&vStr); 1954 if ((strHeader->flags & (FMT_FLAG_LT|FMT_FLAG_GT)) == FMT_FLAG_GT) 1955 bUpper = TRUE; 1956 blanks_first = strHeader->copy_chars - lstrlenW(pSrc); 1957 pToken = (const BYTE*)strHeader + sizeof(FMT_DATE_HEADER); 1958 1959 while (*pToken != FMT_GEN_END) 1960 { 1961 int dwCount = 0; 1962 1963 if (pToken - rgbTok > header->size) 1964 { 1965 ERR("Ran off the end of the format!\n"); 1966 hRes = E_INVALIDARG; 1967 goto VARIANT_FormatString_Exit; 1968 } 1969 1970 switch (*pToken) 1971 { 1972 case FMT_GEN_COPY: 1973 TRACE("copy %s\n", debugstr_wn(lpszFormat + pToken[1], pToken[2])); 1974 memcpy(pBuff, lpszFormat + pToken[1], pToken[2] * sizeof(WCHAR)); 1975 pBuff += pToken[2]; 1976 pToken += 2; 1977 break; 1978 1979 case FMT_STR_COPY_SPACE: 1980 case FMT_STR_COPY_SKIP: 1981 dwCount = pToken[1]; 1982 if (*pToken == FMT_STR_COPY_SPACE && blanks_first > 0) 1983 { 1984 TRACE("insert %d initial spaces\n", blanks_first); 1985 while (dwCount > 0 && blanks_first > 0) 1986 { 1987 *pBuff++ = ' '; 1988 dwCount--; 1989 blanks_first--; 1990 } 1991 } 1992 TRACE("copy %d chars%s\n", dwCount, 1993 *pToken == FMT_STR_COPY_SPACE ? " with space" :""); 1994 while (dwCount > 0 && *pSrc) 1995 { 1996 if (bUpper) 1997 *pBuff++ = towupper(*pSrc); 1998 else 1999 *pBuff++ = towlower(*pSrc); 2000 dwCount--; 2001 pSrc++; 2002 } 2003 if (*pToken == FMT_STR_COPY_SPACE && dwCount > 0) 2004 { 2005 TRACE("insert %d spaces\n", dwCount); 2006 while (dwCount-- > 0) 2007 *pBuff++ = ' '; 2008 } 2009 pToken++; 2010 break; 2011 2012 default: 2013 ERR("Unknown token 0x%02x!\n", *pToken); 2014 hRes = E_INVALIDARG; 2015 goto VARIANT_FormatString_Exit; 2016 } 2017 pToken++; 2018 } 2019 2020 VARIANT_FormatString_Exit: 2021 /* Copy out any remaining chars */ 2022 while (*pSrc) 2023 { 2024 if (bUpper) 2025 *pBuff++ = towupper(*pSrc); 2026 else 2027 *pBuff++ = towlower(*pSrc); 2028 pSrc++; 2029 } 2030 VariantClear(&vStr); 2031 *pBuff = '\0'; 2032 TRACE("buff is %s\n", debugstr_w(buff)); 2033 if (SUCCEEDED(hRes)) 2034 { 2035 *pbstrOut = SysAllocString(buff); 2036 if (!*pbstrOut) 2037 hRes = E_OUTOFMEMORY; 2038 } 2039 return hRes; 2040 } 2041 2042 #define NUMBER_VTBITS (VTBIT_I1|VTBIT_UI1|VTBIT_I2|VTBIT_UI2| \ 2043 VTBIT_I4|VTBIT_UI4|VTBIT_I8|VTBIT_UI8| \ 2044 VTBIT_R4|VTBIT_R8|VTBIT_CY|VTBIT_DECIMAL| \ 2045 VTBIT_BOOL|VTBIT_INT|VTBIT_UINT) 2046 2047 /********************************************************************** 2048 * VarFormatFromTokens [OLEAUT32.139] 2049 */ 2050 HRESULT WINAPI VarFormatFromTokens(LPVARIANT pVarIn, LPOLESTR lpszFormat, 2051 LPBYTE rgbTok, ULONG dwFlags, 2052 BSTR *pbstrOut, LCID lcid) 2053 { 2054 FMT_SHORT_HEADER *header = (FMT_SHORT_HEADER *)rgbTok; 2055 VARIANT vTmp; 2056 HRESULT hres; 2057 2058 TRACE("(%p,%s,%p,%x,%p,0x%08x)\n", pVarIn, debugstr_w(lpszFormat), 2059 rgbTok, dwFlags, pbstrOut, lcid); 2060 2061 if (!pbstrOut) 2062 return E_INVALIDARG; 2063 2064 *pbstrOut = NULL; 2065 2066 if (!pVarIn || !rgbTok) 2067 return E_INVALIDARG; 2068 2069 if (V_VT(pVarIn) == VT_NULL) 2070 return S_OK; 2071 2072 if (*rgbTok == FMT_TO_STRING || header->type == FMT_TYPE_GENERAL) 2073 { 2074 /* According to MSDN, general format acts somewhat like the 'Str' 2075 * function in Visual Basic. 2076 */ 2077 VarFormatFromTokens_AsStr: 2078 V_VT(&vTmp) = VT_EMPTY; 2079 hres = VariantChangeTypeEx(&vTmp, pVarIn, lcid, dwFlags, VT_BSTR); 2080 *pbstrOut = V_BSTR(&vTmp); 2081 } 2082 else 2083 { 2084 if (header->type == FMT_TYPE_NUMBER || 2085 (header->type == FMT_TYPE_UNKNOWN && ((1 << V_TYPE(pVarIn)) & NUMBER_VTBITS))) 2086 { 2087 hres = VARIANT_FormatNumber(pVarIn, lpszFormat, rgbTok, dwFlags, pbstrOut, lcid); 2088 } 2089 else if (header->type == FMT_TYPE_DATE || 2090 (header->type == FMT_TYPE_UNKNOWN && V_TYPE(pVarIn) == VT_DATE)) 2091 { 2092 hres = VARIANT_FormatDate(pVarIn, lpszFormat, rgbTok, dwFlags, pbstrOut, lcid); 2093 } 2094 else if (header->type == FMT_TYPE_STRING || V_TYPE(pVarIn) == VT_BSTR) 2095 { 2096 hres = VARIANT_FormatString(pVarIn, lpszFormat, rgbTok, dwFlags, pbstrOut, lcid); 2097 } 2098 else 2099 { 2100 ERR("unrecognised format type 0x%02x\n", header->type); 2101 return E_INVALIDARG; 2102 } 2103 /* If the coercion failed, still try to create output, unless the 2104 * VAR_FORMAT_NOSUBSTITUTE flag is set. 2105 */ 2106 if ((hres == DISP_E_OVERFLOW || hres == DISP_E_TYPEMISMATCH) && 2107 !(dwFlags & VAR_FORMAT_NOSUBSTITUTE)) 2108 goto VarFormatFromTokens_AsStr; 2109 } 2110 2111 return hres; 2112 } 2113 2114 /********************************************************************** 2115 * VarFormat [OLEAUT32.87] 2116 * 2117 * Format a variant from a format string. 2118 * 2119 * PARAMS 2120 * pVarIn [I] Variant to format 2121 * lpszFormat [I] Format string (see notes) 2122 * nFirstDay [I] First day of the week, (See VarTokenizeFormatString() for details) 2123 * nFirstWeek [I] First week of the year (See VarTokenizeFormatString() for details) 2124 * dwFlags [I] Flags for the format (VAR_ flags from "oleauto.h") 2125 * pbstrOut [O] Destination for formatted string. 2126 * 2127 * RETURNS 2128 * Success: S_OK. pbstrOut contains the formatted value. 2129 * Failure: E_INVALIDARG, if any parameter is invalid. 2130 * E_OUTOFMEMORY, if enough memory cannot be allocated. 2131 * DISP_E_TYPEMISMATCH, if the variant cannot be formatted. 2132 * 2133 * NOTES 2134 * - See Variant-Formats for details concerning creating format strings. 2135 * - This function uses LOCALE_USER_DEFAULT when calling VarTokenizeFormatString() 2136 * and VarFormatFromTokens(). 2137 */ 2138 HRESULT WINAPI VarFormat(LPVARIANT pVarIn, LPOLESTR lpszFormat, 2139 int nFirstDay, int nFirstWeek, ULONG dwFlags, 2140 BSTR *pbstrOut) 2141 { 2142 BYTE buff[256]; 2143 HRESULT hres; 2144 2145 TRACE("(%s,%s,%d,%d,0x%08x,%p)\n", debugstr_variant(pVarIn), debugstr_w(lpszFormat), 2146 nFirstDay, nFirstWeek, dwFlags, pbstrOut); 2147 2148 if (!pbstrOut) 2149 return E_INVALIDARG; 2150 *pbstrOut = NULL; 2151 2152 hres = VarTokenizeFormatString(lpszFormat, buff, sizeof(buff), nFirstDay, 2153 nFirstWeek, LOCALE_USER_DEFAULT, NULL); 2154 if (SUCCEEDED(hres)) 2155 hres = VarFormatFromTokens(pVarIn, lpszFormat, buff, dwFlags, 2156 pbstrOut, LOCALE_USER_DEFAULT); 2157 TRACE("returning 0x%08x, %s\n", hres, debugstr_w(*pbstrOut)); 2158 return hres; 2159 } 2160 2161 /********************************************************************** 2162 * VarFormatDateTime [OLEAUT32.97] 2163 * 2164 * Format a variant value as a date and/or time. 2165 * 2166 * PARAMS 2167 * pVarIn [I] Variant to format 2168 * nFormat [I] Format type (see notes) 2169 * dwFlags [I] Flags for the format (VAR_ flags from "oleauto.h") 2170 * pbstrOut [O] Destination for formatted string. 2171 * 2172 * RETURNS 2173 * Success: S_OK. pbstrOut contains the formatted value. 2174 * Failure: E_INVALIDARG, if any parameter is invalid. 2175 * E_OUTOFMEMORY, if enough memory cannot be allocated. 2176 * DISP_E_TYPEMISMATCH, if the variant cannot be formatted. 2177 * 2178 * NOTES 2179 * This function uses LOCALE_USER_DEFAULT when determining the date format 2180 * characters to use. 2181 * Possible values for the nFormat parameter are: 2182 *| Value Meaning 2183 *| ----- ------- 2184 *| 0 General date format 2185 *| 1 Long date format 2186 *| 2 Short date format 2187 *| 3 Long time format 2188 *| 4 Short time format 2189 */ 2190 HRESULT WINAPI VarFormatDateTime(LPVARIANT pVarIn, INT nFormat, ULONG dwFlags, BSTR *pbstrOut) 2191 { 2192 static WCHAR szEmpty[] = { '\0' }; 2193 const BYTE* lpFmt = NULL; 2194 2195 TRACE("%s,%d,0x%08x,%p)\n", debugstr_variant(pVarIn), nFormat, dwFlags, pbstrOut); 2196 2197 if (!pVarIn || !pbstrOut || nFormat < 0 || nFormat > 4) 2198 return E_INVALIDARG; 2199 2200 switch (nFormat) 2201 { 2202 case 0: lpFmt = fmtGeneralDate; break; 2203 case 1: lpFmt = fmtLongDate; break; 2204 case 2: lpFmt = fmtShortDate; break; 2205 case 3: lpFmt = fmtLongTime; break; 2206 case 4: lpFmt = fmtShortTime; break; 2207 } 2208 return VarFormatFromTokens(pVarIn, szEmpty, (BYTE*)lpFmt, dwFlags, 2209 pbstrOut, LOCALE_USER_DEFAULT); 2210 } 2211 2212 #define GETLOCALENUMBER(type,field) GetLocaleInfoW(LOCALE_USER_DEFAULT, \ 2213 type|LOCALE_RETURN_NUMBER, \ 2214 (LPWSTR)&numfmt.field, \ 2215 sizeof(numfmt.field)/sizeof(WCHAR)) 2216 2217 /********************************************************************** 2218 * VarFormatNumber [OLEAUT32.107] 2219 * 2220 * Format a variant value as a number. 2221 * 2222 * PARAMS 2223 * pVarIn [I] Variant to format 2224 * nDigits [I] Number of digits following the decimal point (-1 = user default) 2225 * nLeading [I] Use a leading zero (-2 = user default, -1 = yes, 0 = no) 2226 * nParens [I] Use brackets for values < 0 (-2 = user default, -1 = yes, 0 = no) 2227 * nGrouping [I] Use grouping characters (-2 = user default, -1 = yes, 0 = no) 2228 * dwFlags [I] Currently unused, set to zero 2229 * pbstrOut [O] Destination for formatted string. 2230 * 2231 * RETURNS 2232 * Success: S_OK. pbstrOut contains the formatted value. 2233 * Failure: E_INVALIDARG, if any parameter is invalid. 2234 * E_OUTOFMEMORY, if enough memory cannot be allocated. 2235 * DISP_E_TYPEMISMATCH, if the variant cannot be formatted. 2236 * 2237 * NOTES 2238 * This function uses LOCALE_USER_DEFAULT when determining the number format 2239 * characters to use. 2240 */ 2241 HRESULT WINAPI VarFormatNumber(LPVARIANT pVarIn, INT nDigits, INT nLeading, INT nParens, 2242 INT nGrouping, ULONG dwFlags, BSTR *pbstrOut) 2243 { 2244 HRESULT hRet; 2245 VARIANT vStr; 2246 2247 TRACE("(%s,%d,%d,%d,%d,0x%08x,%p)\n", debugstr_variant(pVarIn), nDigits, nLeading, 2248 nParens, nGrouping, dwFlags, pbstrOut); 2249 2250 if (!pVarIn || !pbstrOut || nDigits > 9) 2251 return E_INVALIDARG; 2252 2253 *pbstrOut = NULL; 2254 2255 V_VT(&vStr) = VT_EMPTY; 2256 hRet = VariantCopyInd(&vStr, pVarIn); 2257 2258 if (SUCCEEDED(hRet)) 2259 hRet = VariantChangeTypeEx(&vStr, &vStr, LCID_US, 0, VT_BSTR); 2260 2261 if (SUCCEEDED(hRet)) 2262 { 2263 WCHAR buff[256], decimal[8], thousands[8]; 2264 NUMBERFMTW numfmt; 2265 2266 /* Although MSDN makes it clear that the native versions of these functions 2267 * are implemented using VarTokenizeFormatString()/VarFormatFromTokens(), 2268 * using NLS gives us the same result. 2269 */ 2270 if (nDigits < 0) 2271 GETLOCALENUMBER(LOCALE_IDIGITS, NumDigits); 2272 else 2273 numfmt.NumDigits = nDigits; 2274 2275 if (nLeading == -2) 2276 GETLOCALENUMBER(LOCALE_ILZERO, LeadingZero); 2277 else if (nLeading == -1) 2278 numfmt.LeadingZero = 1; 2279 else 2280 numfmt.LeadingZero = 0; 2281 2282 if (nGrouping == -2) 2283 { 2284 WCHAR grouping[10]; 2285 grouping[2] = '\0'; 2286 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SGROUPING, grouping, ARRAY_SIZE(grouping)); 2287 numfmt.Grouping = grouping[2] == '2' ? 32 : grouping[0] - '0'; 2288 } 2289 else if (nGrouping == -1) 2290 numfmt.Grouping = 3; /* 3 = "n,nnn.nn" */ 2291 else 2292 numfmt.Grouping = 0; /* 0 = No grouping */ 2293 2294 if (nParens == -2) 2295 GETLOCALENUMBER(LOCALE_INEGNUMBER, NegativeOrder); 2296 else if (nParens == -1) 2297 numfmt.NegativeOrder = 0; /* 0 = "(xxx)" */ 2298 else 2299 numfmt.NegativeOrder = 1; /* 1 = "-xxx" */ 2300 2301 numfmt.lpDecimalSep = decimal; 2302 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, decimal, ARRAY_SIZE(decimal)); 2303 numfmt.lpThousandSep = thousands; 2304 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, thousands, ARRAY_SIZE(thousands)); 2305 2306 if (GetNumberFormatW(LOCALE_USER_DEFAULT, 0, V_BSTR(&vStr), &numfmt, buff, ARRAY_SIZE(buff))) 2307 { 2308 *pbstrOut = SysAllocString(buff); 2309 if (!*pbstrOut) 2310 hRet = E_OUTOFMEMORY; 2311 } 2312 else 2313 hRet = DISP_E_TYPEMISMATCH; 2314 2315 SysFreeString(V_BSTR(&vStr)); 2316 } 2317 return hRet; 2318 } 2319 2320 /********************************************************************** 2321 * VarFormatPercent [OLEAUT32.117] 2322 * 2323 * Format a variant value as a percentage. 2324 * 2325 * PARAMS 2326 * pVarIn [I] Variant to format 2327 * nDigits [I] Number of digits following the decimal point (-1 = user default) 2328 * nLeading [I] Use a leading zero (-2 = user default, -1 = yes, 0 = no) 2329 * nParens [I] Use brackets for values < 0 (-2 = user default, -1 = yes, 0 = no) 2330 * nGrouping [I] Use grouping characters (-2 = user default, -1 = yes, 0 = no) 2331 * dwFlags [I] Currently unused, set to zero 2332 * pbstrOut [O] Destination for formatted string. 2333 * 2334 * RETURNS 2335 * Success: S_OK. pbstrOut contains the formatted value. 2336 * Failure: E_INVALIDARG, if any parameter is invalid. 2337 * E_OUTOFMEMORY, if enough memory cannot be allocated. 2338 * DISP_E_OVERFLOW, if overflow occurs during the conversion. 2339 * DISP_E_TYPEMISMATCH, if the variant cannot be formatted. 2340 * 2341 * NOTES 2342 * This function uses LOCALE_USER_DEFAULT when determining the number format 2343 * characters to use. 2344 */ 2345 HRESULT WINAPI VarFormatPercent(LPVARIANT pVarIn, INT nDigits, INT nLeading, INT nParens, 2346 INT nGrouping, ULONG dwFlags, BSTR *pbstrOut) 2347 { 2348 static const WCHAR szPercent[] = { '%','\0' }; 2349 static const WCHAR szPercentBracket[] = { '%',')','\0' }; 2350 WCHAR buff[256]; 2351 HRESULT hRet; 2352 VARIANT vDbl; 2353 2354 TRACE("(%s,%d,%d,%d,%d,0x%08x,%p)\n", debugstr_variant(pVarIn), nDigits, nLeading, 2355 nParens, nGrouping, dwFlags, pbstrOut); 2356 2357 if (!pVarIn || !pbstrOut || nDigits > 9) 2358 return E_INVALIDARG; 2359 2360 *pbstrOut = NULL; 2361 2362 V_VT(&vDbl) = VT_EMPTY; 2363 hRet = VariantCopyInd(&vDbl, pVarIn); 2364 2365 if (SUCCEEDED(hRet)) 2366 { 2367 hRet = VariantChangeTypeEx(&vDbl, &vDbl, LOCALE_USER_DEFAULT, 0, VT_R8); 2368 2369 if (SUCCEEDED(hRet)) 2370 { 2371 if (V_R8(&vDbl) > (R8_MAX / 100.0)) 2372 return DISP_E_OVERFLOW; 2373 2374 V_R8(&vDbl) *= 100.0; 2375 hRet = VarFormatNumber(&vDbl, nDigits, nLeading, nParens, 2376 nGrouping, dwFlags, pbstrOut); 2377 2378 if (SUCCEEDED(hRet)) 2379 { 2380 DWORD dwLen = lstrlenW(*pbstrOut); 2381 BOOL bBracket = (*pbstrOut)[dwLen] == ')'; 2382 2383 dwLen -= bBracket; 2384 memcpy(buff, *pbstrOut, dwLen * sizeof(WCHAR)); 2385 lstrcpyW(buff + dwLen, bBracket ? szPercentBracket : szPercent); 2386 SysFreeString(*pbstrOut); 2387 *pbstrOut = SysAllocString(buff); 2388 if (!*pbstrOut) 2389 hRet = E_OUTOFMEMORY; 2390 } 2391 } 2392 } 2393 return hRet; 2394 } 2395 2396 /********************************************************************** 2397 * VarFormatCurrency [OLEAUT32.127] 2398 * 2399 * Format a variant value as a currency. 2400 * 2401 * PARAMS 2402 * pVarIn [I] Variant to format 2403 * nDigits [I] Number of digits following the decimal point (-1 = user default) 2404 * nLeading [I] Use a leading zero (-2 = user default, -1 = yes, 0 = no) 2405 * nParens [I] Use brackets for values < 0 (-2 = user default, -1 = yes, 0 = no) 2406 * nGrouping [I] Use grouping characters (-2 = user default, -1 = yes, 0 = no) 2407 * dwFlags [I] Currently unused, set to zero 2408 * pbstrOut [O] Destination for formatted string. 2409 * 2410 * RETURNS 2411 * Success: S_OK. pbstrOut contains the formatted value. 2412 * Failure: E_INVALIDARG, if any parameter is invalid. 2413 * E_OUTOFMEMORY, if enough memory cannot be allocated. 2414 * DISP_E_TYPEMISMATCH, if the variant cannot be formatted. 2415 * 2416 * NOTES 2417 * This function uses LOCALE_USER_DEFAULT when determining the currency format 2418 * characters to use. 2419 */ 2420 HRESULT WINAPI VarFormatCurrency(LPVARIANT pVarIn, INT nDigits, INT nLeading, 2421 INT nParens, INT nGrouping, ULONG dwFlags, 2422 BSTR *pbstrOut) 2423 { 2424 HRESULT hRet; 2425 VARIANT vStr; 2426 2427 TRACE("(%s,%d,%d,%d,%d,0x%08x,%p)\n", debugstr_variant(pVarIn), nDigits, nLeading, 2428 nParens, nGrouping, dwFlags, pbstrOut); 2429 2430 if (!pVarIn || !pbstrOut || nDigits > 9) 2431 return E_INVALIDARG; 2432 2433 *pbstrOut = NULL; 2434 2435 V_VT(&vStr) = VT_EMPTY; 2436 hRet = VariantCopyInd(&vStr, pVarIn); 2437 2438 if (SUCCEEDED(hRet)) 2439 hRet = VariantChangeTypeEx(&vStr, &vStr, LOCALE_USER_DEFAULT, 0, VT_BSTR); 2440 2441 if (SUCCEEDED(hRet)) 2442 { 2443 WCHAR buff[256], decimal[8], thousands[4], currency[13]; 2444 CURRENCYFMTW numfmt; 2445 2446 if (nDigits < 0) 2447 GETLOCALENUMBER(LOCALE_IDIGITS, NumDigits); 2448 else 2449 numfmt.NumDigits = nDigits; 2450 2451 if (nLeading == -2) 2452 GETLOCALENUMBER(LOCALE_ILZERO, LeadingZero); 2453 else if (nLeading == -1) 2454 numfmt.LeadingZero = 1; 2455 else 2456 numfmt.LeadingZero = 0; 2457 2458 if (nGrouping == -2) 2459 { 2460 WCHAR grouping[10]; 2461 grouping[2] = '\0'; 2462 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SGROUPING, grouping, ARRAY_SIZE(grouping)); 2463 numfmt.Grouping = grouping[2] == '2' ? 32 : grouping[0] - '0'; 2464 } 2465 else if (nGrouping == -1) 2466 numfmt.Grouping = 3; /* 3 = "n,nnn.nn" */ 2467 else 2468 numfmt.Grouping = 0; /* 0 = No grouping */ 2469 2470 if (nParens == -2) 2471 GETLOCALENUMBER(LOCALE_INEGCURR, NegativeOrder); 2472 else if (nParens == -1) 2473 numfmt.NegativeOrder = 0; /* 0 = "(xxx)" */ 2474 else 2475 numfmt.NegativeOrder = 1; /* 1 = "-xxx" */ 2476 2477 GETLOCALENUMBER(LOCALE_ICURRENCY, PositiveOrder); 2478 2479 numfmt.lpDecimalSep = decimal; 2480 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, decimal, ARRAY_SIZE(decimal)); 2481 numfmt.lpThousandSep = thousands; 2482 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, thousands, ARRAY_SIZE(thousands)); 2483 numfmt.lpCurrencySymbol = currency; 2484 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SCURRENCY, currency, ARRAY_SIZE(currency)); 2485 2486 /* use NLS as per VarFormatNumber() */ 2487 if (GetCurrencyFormatW(LOCALE_USER_DEFAULT, 0, V_BSTR(&vStr), &numfmt, buff, ARRAY_SIZE(buff))) 2488 { 2489 *pbstrOut = SysAllocString(buff); 2490 if (!*pbstrOut) 2491 hRet = E_OUTOFMEMORY; 2492 } 2493 else 2494 hRet = DISP_E_TYPEMISMATCH; 2495 2496 SysFreeString(V_BSTR(&vStr)); 2497 } 2498 return hRet; 2499 } 2500 2501 /********************************************************************** 2502 * VarMonthName [OLEAUT32.129] 2503 * 2504 * Print the specified month as localized name. 2505 * 2506 * PARAMS 2507 * iMonth [I] month number 1..12 2508 * fAbbrev [I] 0 - full name, !0 - abbreviated name 2509 * dwFlags [I] flag stuff. only VAR_CALENDAR_HIJRI possible. 2510 * pbstrOut [O] Destination for month name 2511 * 2512 * RETURNS 2513 * Success: S_OK. pbstrOut contains the name. 2514 * Failure: E_INVALIDARG, if any parameter is invalid. 2515 * E_OUTOFMEMORY, if enough memory cannot be allocated. 2516 */ 2517 HRESULT WINAPI VarMonthName(INT iMonth, INT fAbbrev, ULONG dwFlags, BSTR *pbstrOut) 2518 { 2519 DWORD localeValue; 2520 INT size; 2521 2522 if ((iMonth < 1) || (iMonth > 12)) 2523 return E_INVALIDARG; 2524 2525 if (dwFlags) 2526 FIXME("Does not support dwFlags 0x%x, ignoring.\n", dwFlags); 2527 2528 if (fAbbrev) 2529 localeValue = LOCALE_SABBREVMONTHNAME1 + iMonth - 1; 2530 else 2531 localeValue = LOCALE_SMONTHNAME1 + iMonth - 1; 2532 2533 size = GetLocaleInfoW(LOCALE_USER_DEFAULT,localeValue, NULL, 0); 2534 if (!size) { 2535 ERR("GetLocaleInfo 0x%x failed.\n", localeValue); 2536 return HRESULT_FROM_WIN32(GetLastError()); 2537 } 2538 *pbstrOut = SysAllocStringLen(NULL,size - 1); 2539 if (!*pbstrOut) 2540 return E_OUTOFMEMORY; 2541 size = GetLocaleInfoW(LOCALE_USER_DEFAULT,localeValue, *pbstrOut, size); 2542 if (!size) { 2543 ERR("GetLocaleInfo of 0x%x failed in 2nd stage?!\n", localeValue); 2544 SysFreeString(*pbstrOut); 2545 return HRESULT_FROM_WIN32(GetLastError()); 2546 } 2547 return S_OK; 2548 } 2549 2550 /********************************************************************** 2551 * VarWeekdayName [OLEAUT32.129] 2552 * 2553 * Print the specified weekday as localized name. 2554 * 2555 * PARAMS 2556 * iWeekday [I] day of week, 1..7, 1="the first day of the week" 2557 * fAbbrev [I] 0 - full name, !0 - abbreviated name 2558 * iFirstDay [I] first day of week, 2559 * 0=system default, 1=Sunday, 2=Monday, .. (contrary to MSDN) 2560 * dwFlags [I] flag stuff. only VAR_CALENDAR_HIJRI possible. 2561 * pbstrOut [O] Destination for weekday name. 2562 * 2563 * RETURNS 2564 * Success: S_OK, pbstrOut contains the name. 2565 * Failure: E_INVALIDARG, if any parameter is invalid. 2566 * E_OUTOFMEMORY, if enough memory cannot be allocated. 2567 */ 2568 HRESULT WINAPI VarWeekdayName(INT iWeekday, INT fAbbrev, INT iFirstDay, 2569 ULONG dwFlags, BSTR *pbstrOut) 2570 { 2571 DWORD localeValue; 2572 INT size; 2573 2574 /* Windows XP oleaut32.dll doesn't allow iWekday==0, contrary to MSDN */ 2575 if (iWeekday < 1 || iWeekday > 7) 2576 return E_INVALIDARG; 2577 if (iFirstDay < 0 || iFirstDay > 7) 2578 return E_INVALIDARG; 2579 if (!pbstrOut) 2580 return E_INVALIDARG; 2581 2582 if (dwFlags) 2583 FIXME("Does not support dwFlags 0x%x, ignoring.\n", dwFlags); 2584 2585 /* If we have to use the default firstDay, find which one it is */ 2586 if (iFirstDay == 0) { 2587 DWORD firstDay; 2588 localeValue = LOCALE_RETURN_NUMBER | LOCALE_IFIRSTDAYOFWEEK; 2589 size = GetLocaleInfoW(LOCALE_USER_DEFAULT, localeValue, 2590 (LPWSTR)&firstDay, sizeof(firstDay) / sizeof(WCHAR)); 2591 if (!size) { 2592 ERR("GetLocaleInfo 0x%x failed.\n", localeValue); 2593 return HRESULT_FROM_WIN32(GetLastError()); 2594 } 2595 iFirstDay = firstDay + 2; 2596 } 2597 2598 /* Determine what we need to return */ 2599 localeValue = fAbbrev ? LOCALE_SABBREVDAYNAME1 : LOCALE_SDAYNAME1; 2600 localeValue += (7 + iWeekday - 1 + iFirstDay - 2) % 7; 2601 2602 /* Determine the size of the data, allocate memory and retrieve the data */ 2603 size = GetLocaleInfoW(LOCALE_USER_DEFAULT, localeValue, NULL, 0); 2604 if (!size) { 2605 ERR("GetLocaleInfo 0x%x failed.\n", localeValue); 2606 return HRESULT_FROM_WIN32(GetLastError()); 2607 } 2608 *pbstrOut = SysAllocStringLen(NULL, size - 1); 2609 if (!*pbstrOut) 2610 return E_OUTOFMEMORY; 2611 size = GetLocaleInfoW(LOCALE_USER_DEFAULT, localeValue, *pbstrOut, size); 2612 if (!size) { 2613 ERR("GetLocaleInfo 0x%x failed in 2nd stage?!\n", localeValue); 2614 SysFreeString(*pbstrOut); 2615 return HRESULT_FROM_WIN32(GetLastError()); 2616 } 2617 return S_OK; 2618 } 2619