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