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