source: trunk/src/helpers/nls.c@ 176

Last change on this file since 176 was 174, checked in by umoeller, 23 years ago

Misc updates.

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 21.1 KB
Line 
1
2/*
3 *@@sourcefile nls.c:
4 * contains a few helpers for National Language Support (NLS),
5 * such as printing strings with the format specified by
6 * the "Country" object.
7 *
8 * Usage: All OS/2 programs.
9 *
10 * Function prefixes (new with V0.81):
11 * -- nls* NLS helpers
12 *
13 * This file is new with 0.9.16, but contains functions
14 * formerly in stringh.c.
15 *
16 * Note: Version numbering in this file relates to XWorkplace version
17 * numbering.
18 *
19 *@@header "helpers\nls.h"
20 *@@added V0.9.16 (2001-10-11) [umoeller]
21 */
22
23/*
24 * Copyright (C) 1997-2002 Ulrich M”ller.
25 * This file is part of the "XWorkplace helpers" source package.
26 * This is free software; you can redistribute it and/or modify
27 * it under the terms of the GNU General Public License as published
28 * by the Free Software Foundation, in version 2 as it comes in the
29 * "COPYING" file of the XWorkplace main distribution.
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
34 */
35
36#define OS2EMX_PLAIN_CHAR
37 // this is needed for "os2emx.h"; if this is defined,
38 // emx will define PSZ as _signed_ char, otherwise
39 // as unsigned char
40
41#define INCL_DOSNLS
42#define INCL_DOSDATETIME
43#define INCL_DOSERRORS
44#define INCL_WINSHELLDATA
45#include <os2.h>
46
47#include <stdlib.h>
48#include <stdio.h>
49#include <string.h>
50#include <math.h>
51
52// XWP's setup.h replaces strchr and the like, and
53// we want the originals in here
54#define DONT_REPLACE_FOR_DBCS
55#include "setup.h" // code generation and debugging options
56
57#include "helpers\nls.h"
58#include "helpers\prfh.h"
59#include "helpers\standards.h"
60
61#pragma hdrstop
62
63/*
64 *@@category: Helpers\National Language Support
65 * See nls.c.
66 */
67
68/* ******************************************************************
69 *
70 * DBCS support
71 *
72 ********************************************************************/
73
74#define MAX_LEADBYTE 256
75
76#pragma pack(1)
77
78typedef struct _DBCSVECTOR
79{
80 BYTE bLow;
81 BYTE bHigh;
82} DBCSVECTOR;
83
84#pragma pack()
85
86BOOL G_afLeadByte[MAX_LEADBYTE] = {0};
87ULONG G_fDBCS = -1; // not queried yet
88COUNTRYCODE G_cc = { 0, 0 };
89DBCSVECTOR G_aDBCSVector[8];
90
91/*
92 *@@ nlsDBCS:
93 * returns TRUE if the system is currently using DBCS.
94 *
95 *@@added V0.9.19 (2002-06-13) [umoeller]
96 */
97
98BOOL nlsDBCS(VOID)
99{
100 int i;
101
102 if (G_fDBCS != -1)
103 // already queried:
104 return G_fDBCS;
105
106 if (DosQueryDBCSEnv(8 * sizeof(DBCSVECTOR),
107 &G_cc,
108 (PCHAR)G_aDBCSVector))
109 // not DBCS:
110 return (G_fDBCS = FALSE);
111
112 for (i = 0;
113 i < 8;
114 ++i)
115 {
116 if ( (G_aDBCSVector[i].bLow)
117 && (G_aDBCSVector[i].bHigh)
118 )
119 {
120 int n;
121 for (n = G_aDBCSVector[i].bLow;
122 n <= G_aDBCSVector[i].bHigh;
123 ++n)
124 G_afLeadByte[n] = TRUE;
125 G_fDBCS = TRUE;
126 }
127 else
128 break;
129 }
130
131 return G_fDBCS;
132}
133
134/*
135 *@@ nlsQueryDBCSChar:
136 * returns the type of the DBCS character with
137 * the given index. Note that the index is the
138 * DBCS character index, not just the array
139 * index into the CHAR array.
140 *
141 * Returns:
142 *
143 * -- TYPE_SBCS: ulOfs is single byte.
144 *
145 * -- TYPE_DBCS_1ST: ulOfs is a double-byte lead char.
146 *
147 * -- TYPE_DBCS_2ND: ulOfs is a double-byte trail char.
148 *
149 * Preconditions:
150 *
151 * -- nlsDBCS must have been called to initialize our
152 * globals, and must have returned TRUE.
153 *
154 *@@added V0.9.19 (2002-06-13) [umoeller]
155 */
156
157ULONG nlsQueryDBCSChar(PCSZ pcszString,
158 ULONG ulOfs)
159
160{
161 ULONG ulDBCSType = TYPE_SBCS;
162 ULONG i;
163
164 for (i = 0;
165 i <= ulOfs;
166 ++i)
167 {
168 switch (ulDBCSType)
169 {
170 case TYPE_SBCS:
171 case TYPE_DBCS_2ND:
172 ulDBCSType = G_afLeadByte[pcszString[i]];
173 break;
174
175 case TYPE_DBCS_1ST :
176 ulDBCSType = TYPE_DBCS_2ND;
177 break;
178 }
179 }
180
181 return ulDBCSType;
182}
183
184/*
185 *@@ nlschr:
186 * replacement for strchr with DBCS support.
187 *
188 * If the system is not running with DBCS,
189 * this calls plain strchr automatically.
190 *
191 *@@added V0.9.19 (2002-06-13) [umoeller]
192 */
193
194PSZ nlschr(PCSZ p, char c)
195{
196 PCSZ p2;
197 ULONG ulDBCS;
198
199 if (!nlsDBCS())
200 // not DBCS:
201 return strchr(p, c);
202
203 // we're on DBCS:
204 for (p2 = p;
205 *p2;
206 ++p2)
207 {
208 if (*p2 == c)
209 {
210 // match: return this only if it's SBCS;
211 // if it's a DBCS lead char, skip it
212 switch (ulDBCS = nlsQueryDBCSChar(p, p2 - p))
213 {
214 case TYPE_SBCS:
215 return (PSZ)p2;
216
217 case TYPE_DBCS_1ST:
218 ++p2;
219 }
220 }
221 }
222
223 return NULL;
224}
225
226/*
227 *@@ nlsrchr:
228 * replacement for strrchr with DBCS support.
229 *
230 * If the system is not running with DBCS,
231 * this calls plain strrchr automatically.
232 *
233 *@@added V0.9.19 (2002-06-13) [umoeller]
234 */
235
236PSZ nlsrchr(PCSZ p, char c)
237{
238 PCSZ p2;
239 ULONG ulDBCS,
240 ulLength;
241
242 if (!nlsDBCS())
243 // not DBCS:
244 return strrchr(p, c);
245
246 // we're on DBCS:
247 ulLength = strlen(p);
248 for (p2 = p + ulLength - 1;
249 p2 >= p;
250 --p2)
251 {
252 if (*p2 == c)
253 {
254 // match: return this only if it's SBCS;
255 // if it's a DBCS trail char, skip it
256 switch (ulDBCS = nlsQueryDBCSChar(p, p2 - p))
257 {
258 case TYPE_SBCS:
259 return (PSZ)p2;
260
261 case TYPE_DBCS_2ND:
262 --p2;
263 }
264 }
265 }
266
267 return NULL;
268}
269
270/* ******************************************************************
271 *
272 * Country-dependent formatting
273 *
274 ********************************************************************/
275
276/*
277 *@@ nlsQueryCountrySettings:
278 * this returns the most frequently used country settings
279 * all at once into a COUNTRYSETTINGS structure (prfh.h).
280 * This data corresponds to the user settings in the
281 * WPS "Country" object (which writes the data in "PM_National"
282 * in OS2.INI).
283 *
284 * In case a key cannot be found, the following (English)
285 * default values are set:
286 * -- ulDateFormat = 0 (English date format, mm.dd.yyyy);
287 * -- ulTimeFormat = 0 (12-hour clock);
288 * -- cDateSep = '/' (date separator);
289 * -- cTimeSep = ':' (time separator);
290 * -- cDecimal = '.' (decimal separator).
291 * -- cThousands = ',' (thousands separator).
292 *
293 *@@added V0.9.0 [umoeller]
294 *@@changed V0.9.7 (2000-12-02) [umoeller]: added cDecimal
295 */
296
297VOID nlsQueryCountrySettings(PCOUNTRYSETTINGS pcs)
298{
299 if (pcs)
300 {
301 pcs->ulDateFormat = PrfQueryProfileInt(HINI_USER,
302 (PSZ)PMINIAPP_NATIONAL,
303 "iDate",
304 0);
305 pcs->ulTimeFormat = PrfQueryProfileInt(HINI_USER,
306 (PSZ)PMINIAPP_NATIONAL,
307 "iTime",
308 0);
309 pcs->cDateSep = prfhQueryProfileChar(HINI_USER,
310 (PSZ)PMINIAPP_NATIONAL,
311 "sDate",
312 '/');
313 pcs->cTimeSep = prfhQueryProfileChar(HINI_USER,
314 (PSZ)PMINIAPP_NATIONAL,
315 "sTime",
316 ':');
317 pcs->cDecimal = prfhQueryProfileChar(HINI_USER,
318 (PSZ)PMINIAPP_NATIONAL,
319 "sDecimal",
320 '.');
321 pcs->cThousands = prfhQueryProfileChar(HINI_USER,
322 (PSZ)PMINIAPP_NATIONAL,
323 "sThousand",
324 ',');
325 }
326}
327
328/*
329 *@@ nlsThousandsULong:
330 * converts a ULONG into a decimal string, while
331 * inserting thousands separators into it. Specify
332 * the separator character in cThousands.
333 *
334 * Returns pszTarget so you can use it directly
335 * with sprintf and the "%s" flag.
336 *
337 * For cThousands, you should use the data in
338 * OS2.INI ("PM_National" application), which is
339 * always set according to the "Country" object.
340 * You can use nlsQueryCountrySettings to
341 * retrieve this setting.
342 *
343 * Use nlsThousandsDouble for "double" values.
344 */
345
346PSZ nlsThousandsULong(PSZ pszTarget, // out: decimal as string
347 ULONG ul, // in: decimal to convert
348 CHAR cThousands) // in: separator char (e.g. '.')
349{
350 USHORT ust, uss, usc;
351 CHAR szTemp[40];
352 sprintf(szTemp, "%lu", ul);
353
354 ust = 0;
355 usc = strlen(szTemp);
356 for (uss = 0; uss < usc; uss++)
357 {
358 if (uss)
359 if (((usc - uss) % 3) == 0)
360 {
361 pszTarget[ust] = cThousands;
362 ust++;
363 }
364 pszTarget[ust] = szTemp[uss];
365 ust++;
366 }
367 pszTarget[ust] = '\0';
368
369 return (pszTarget);
370}
371
372/*
373 * strhThousandsULong:
374 * wrapper around nlsThousandsULong for those
375 * who used the XFLDR.DLL export.
376 *
377 *added V0.9.16 (2001-10-11) [umoeller]
378 */
379
380PSZ APIENTRY strhThousandsULong(PSZ pszTarget, // out: decimal as string
381 ULONG ul, // in: decimal to convert
382 CHAR cThousands) // in: separator char (e.g. '.')
383{
384 return (nlsThousandsULong(pszTarget, ul, cThousands));
385}
386
387/*
388 *@@ nlsThousandsDouble:
389 * like nlsThousandsULong, but for a "double"
390 * value. Note that after-comma values are truncated.
391 */
392
393PSZ nlsThousandsDouble(PSZ pszTarget,
394 double dbl,
395 CHAR cThousands)
396{
397 USHORT ust, uss, usc;
398 CHAR szTemp[40];
399 sprintf(szTemp, "%.0f", floor(dbl));
400
401 ust = 0;
402 usc = strlen(szTemp);
403 for (uss = 0; uss < usc; uss++)
404 {
405 if (uss)
406 if (((usc - uss) % 3) == 0)
407 {
408 pszTarget[ust] = cThousands;
409 ust++;
410 }
411 pszTarget[ust] = szTemp[uss];
412 ust++;
413 }
414 pszTarget[ust] = '\0';
415
416 return (pszTarget);
417}
418
419/*
420 *@@ nlsVariableDouble:
421 * like nlsThousandsULong, but for a "double" value, and
422 * with a variable number of decimal places depending on the
423 * size of the quantity.
424 *
425 *@@added V0.9.6 (2000-11-12) [pr]
426 */
427
428PSZ nlsVariableDouble(PSZ pszTarget,
429 double dbl,
430 PSZ pszUnits,
431 CHAR cThousands)
432{
433 if (dbl < 100.0)
434 sprintf(pszTarget, "%.2f%s", dbl, pszUnits);
435 else
436 if (dbl < 1000.0)
437 sprintf(pszTarget, "%.1f%s", dbl, pszUnits);
438 else
439 strcat(nlsThousandsDouble(pszTarget, dbl, cThousands),
440 pszUnits);
441
442 return(pszTarget);
443}
444
445/*
446 *@@ nlsFileDate:
447 * converts file date data to a string (to pszBuf).
448 * You can pass any FDATE structure to this function,
449 * which are returned in those FILEFINDBUF* or
450 * FILESTATUS* structs by the Dos* functions.
451 *
452 * ulDateFormat is the PM setting for the date format,
453 * as set in the "Country" object, and can be queried using
454 + PrfQueryProfileInt(HINI_USER, "PM_National", "iDate", 0);
455 *
456 * meaning:
457 * -- 0 mm.dd.yyyy (English)
458 * -- 1 dd.mm.yyyy (e.g. German)
459 * -- 2 yyyy.mm.dd (Japanese, ISO)
460 * -- 3 yyyy.dd.mm
461 *
462 * cDateSep is used as a date separator (e.g. '.').
463 * This can be queried using:
464 + prfhQueryProfileChar(HINI_USER, "PM_National", "sDate", '/');
465 *
466 * Alternatively, you can query all the country settings
467 * at once using nlsQueryCountrySettings (prfh.c).
468 *
469 *@@changed V0.9.0 (99-11-07) [umoeller]: now calling nlsDateTime
470 */
471
472VOID nlsFileDate(PSZ pszBuf, // out: string returned
473 FDATE *pfDate, // in: date information
474 ULONG ulDateFormat, // in: date format (0-3)
475 CHAR cDateSep) // in: date separator (e.g. '.')
476{
477 DATETIME dt;
478 dt.day = pfDate->day;
479 dt.month = pfDate->month;
480 dt.year = pfDate->year + 1980;
481
482 nlsDateTime(pszBuf,
483 NULL, // no time
484 &dt,
485 ulDateFormat,
486 cDateSep,
487 0, 0); // no time
488}
489
490/*
491 *@@ nlsFileTime:
492 * converts file time data to a string (to pszBuf).
493 * You can pass any FTIME structure to this function,
494 * which are returned in those FILEFINDBUF* or
495 * FILESTATUS* structs by the Dos* functions.
496 *
497 * ulTimeFormat is the PM setting for the time format,
498 * as set in the "Country" object, and can be queried using
499 + PrfQueryProfileInt(HINI_USER, "PM_National", "iTime", 0);
500 * meaning:
501 * -- 0 12-hour clock
502 * -- >0 24-hour clock
503 *
504 * cDateSep is used as a time separator (e.g. ':').
505 * This can be queried using:
506 + prfhQueryProfileChar(HINI_USER, "PM_National", "sTime", ':');
507 *
508 * Alternatively, you can query all the country settings
509 * at once using nlsQueryCountrySettings (prfh.c).
510 *
511 *@@changed V0.8.5 (99-03-15) [umoeller]: fixed 12-hour crash
512 *@@changed V0.9.0 (99-11-07) [umoeller]: now calling nlsDateTime
513 */
514
515VOID nlsFileTime(PSZ pszBuf, // out: string returned
516 FTIME *pfTime, // in: time information
517 ULONG ulTimeFormat, // in: 24-hour time format (0 or 1)
518 CHAR cTimeSep) // in: time separator (e.g. ':')
519{
520 DATETIME dt;
521 dt.hours = pfTime->hours;
522 dt.minutes = pfTime->minutes;
523 dt.seconds = pfTime->twosecs * 2;
524
525 nlsDateTime(NULL, // no date
526 pszBuf,
527 &dt,
528 0, 0, // no date
529 ulTimeFormat,
530 cTimeSep);
531}
532
533/*
534 *@@ nlsDateTime:
535 * converts Control Program DATETIME info
536 * into two strings. See nlsFileDate and nlsFileTime
537 * for more detailed parameter descriptions.
538 *
539 *@@added V0.9.0 (99-11-07) [umoeller]
540 *@@changed V0.9.16 (2001-12-05) [pr]: fixed AM/PM hour bug
541 *@@changed V0.9.18 (2002-02-13) [umoeller]: fixed AM/PM hour bug fix
542 */
543
544VOID nlsDateTime(PSZ pszDate, // out: date string returned (can be NULL)
545 PSZ pszTime, // out: time string returned (can be NULL)
546 DATETIME *pDateTime, // in: date/time information
547 ULONG ulDateFormat, // in: date format (0-3); see nlsFileDate
548 CHAR cDateSep, // in: date separator (e.g. '.')
549 ULONG ulTimeFormat, // in: 24-hour time format (0 or 1); see nlsFileTime
550 CHAR cTimeSep) // in: time separator (e.g. ':')
551{
552 if (pszDate)
553 {
554 switch (ulDateFormat)
555 {
556 case 0: // mm.dd.yyyy (English)
557 sprintf(pszDate, "%02d%c%02d%c%04d",
558 pDateTime->month,
559 cDateSep,
560 pDateTime->day,
561 cDateSep,
562 pDateTime->year);
563 break;
564
565 case 1: // dd.mm.yyyy (e.g. German)
566 sprintf(pszDate, "%02d%c%02d%c%04d",
567 pDateTime->day,
568 cDateSep,
569 pDateTime->month,
570 cDateSep,
571 pDateTime->year);
572 break;
573
574 case 2: // yyyy.mm.dd (Japanese)
575 sprintf(pszDate, "%04d%c%02d%c%02d",
576 pDateTime->year,
577 cDateSep,
578 pDateTime->month,
579 cDateSep,
580 pDateTime->day);
581 break;
582
583 default: // yyyy.dd.mm
584 sprintf(pszDate, "%04d%c%02d%c%02d",
585 pDateTime->year,
586 cDateSep,
587 pDateTime->day,
588 cDateSep,
589 pDateTime->month);
590 break;
591 }
592 }
593
594 if (pszTime)
595 {
596 if (ulTimeFormat == 0)
597 {
598 // for 12-hour clock, we need additional INI data
599 CHAR szAMPM[10] = "err";
600
601 if (pDateTime->hours >= 12) // V0.9.16 (2001-12-05) [pr] if (pDateTime->hours > 12)
602 {
603 // yeah cool Paul, now we get 00:20 PM if it's 20 past noon
604 // V0.9.18 (2002-02-13) [umoeller]
605 ULONG ulHours;
606 if (!(ulHours = pDateTime->hours % 12))
607 ulHours = 12;
608
609 // >= 12h: PM.
610 PrfQueryProfileString(HINI_USER,
611 "PM_National",
612 "s2359", // key
613 "PM", // default
614 szAMPM, sizeof(szAMPM)-1);
615 sprintf(pszTime, "%02d%c%02d%c%02d %s",
616 // leave 12 == 12 (not 0)
617 ulHours,
618 cTimeSep,
619 pDateTime->minutes,
620 cTimeSep,
621 pDateTime->seconds,
622 szAMPM);
623 }
624 else
625 {
626 // < 12h: AM
627 PrfQueryProfileString(HINI_USER,
628 "PM_National",
629 "s1159", // key
630 "AM", // default
631 szAMPM, sizeof(szAMPM)-1);
632 sprintf(pszTime, "%02d%c%02d%c%02d %s",
633 pDateTime->hours,
634 cTimeSep,
635 pDateTime->minutes,
636 cTimeSep,
637 pDateTime->seconds,
638 szAMPM);
639 }
640 }
641 else
642 // 24-hour clock
643 sprintf(pszTime, "%02d%c%02d%c%02d",
644 pDateTime->hours,
645 cTimeSep,
646 pDateTime->minutes,
647 cTimeSep,
648 pDateTime->seconds);
649 }
650}
651
652/*
653 * strhDateTime:
654 * wrapper around nlsDateTime for those who used
655 * the XFLDR.DLL export.
656 */
657
658VOID APIENTRY strhDateTime(PSZ pszDate, // out: date string returned (can be NULL)
659 PSZ pszTime, // out: time string returned (can be NULL)
660 DATETIME *pDateTime, // in: date/time information
661 ULONG ulDateFormat, // in: date format (0-3); see nlsFileDate
662 CHAR cDateSep, // in: date separator (e.g. '.')
663 ULONG ulTimeFormat, // in: 24-hour time format (0 or 1); see nlsFileTime
664 CHAR cTimeSep) // in: time separator (e.g. ':')
665{
666 nlsDateTime(pszDate,
667 pszTime,
668 pDateTime,
669 ulDateFormat,
670 cDateSep,
671 ulTimeFormat,
672 cTimeSep);
673}
674
675
676/*
677 *@@ nlsUpper:
678 * quick hack for upper-casing a string.
679 *
680 * This uses DosMapCase with the default system country
681 * code and the process's codepage. WARNING: DosMapCase
682 * is a 16-bit API and therefore quite slow. Use this
683 * with care.
684 *
685 *@@added V0.9.16 (2001-10-25) [umoeller]
686 */
687
688APIRET nlsUpper(PSZ psz, // in/out: string
689 ULONG ulLength) // in: string length; if 0, we run strlen(psz)
690{
691 COUNTRYCODE cc;
692
693 if (psz)
694 {
695 if (!ulLength)
696 ulLength = strlen(psz);
697
698 if (ulLength)
699 {
700 cc.country = 0; // use system country code
701 cc.codepage = 0; // use process default codepage
702 return (DosMapCase(ulLength,
703 &cc,
704 psz));
705 }
706 }
707
708 return (ERROR_INVALID_PARAMETER);
709}
710
711
Note: See TracBrowser for help on using the repository browser.