source: branches/branch-1-0/src/helpers/tmsgfile.c@ 297

Last change on this file since 297 was 284, checked in by pr, 20 years ago

Kernel bug workaround

  • 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 tmsgfile.c:
4 * replacement code for DosGetMessage for decent NLS support.
5 *
6 * This code has the following advantages over DosGetMessage:
7 *
8 * 1) No external utility is necessary to change message
9 * files. Simply edit the text, and on the next call,
10 * the file is recompiled at run-time.
11 *
12 * 2) To identify messages, any string can be used, not
13 * only numerical IDs.
14 *
15 * The .TMF file must have the following format:
16 *
17 * 1) Any message must start on the beginning of a line
18 * and look like this:
19 *
20 + <--MSGID-->: message text
21 *
22 * "MSGID" can be any string and will serve as the
23 * message identifier for tmfGetMessage.
24 * Leading spaces after "-->:" will be ignored.
25 *
26 * 2) The message text may span across several lines.
27 * If so, the line breaks are returned as plain
28 * newline (\n) characters by tmfGetMessage.
29 *
30 * 3) Comments in the file are supported if they start
31 * with a semicolon (";") at the beginning of a line.
32 * Any following text is ignored until the next
33 * message ID.
34 *
35 * This file was originally added V0.9.0. The original
36 * code was contributed by Christian Langanke, but this
37 * has been completely rewritten with V0.9.16 to use my
38 * fast string functions now. Also, tmfGetMessage now
39 * requires tmfOpenMessageFile to be called beforehand
40 * and keeps all messages in memory for speed.
41 *
42 * Usage: All OS/2 programs.
43 *
44 * Function prefixes:
45 * -- tmf* text message file functions
46 *
47 * Note: Version numbering in this file relates to XWorkplace version
48 * numbering.
49 *
50 *@@header "helpers\tmsgfile.h"
51 *@@added V0.9.0 [umoeller]
52 */
53
54/*
55 * Copyright (C) 2001-2002 Ulrich M”ller.
56 * This file is part of the "XWorkplace helpers" source package.
57 * This is free software; you can redistribute it and/or modify
58 * it under the terms of the GNU General Public License as published
59 * by the Free Software Foundation, in version 2 as it comes in the
60 * "COPYING" file of the XWorkplace main distribution.
61 * This program is distributed in the hope that it will be useful,
62 * but WITHOUT ANY WARRANTY; without even the implied warranty of
63 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
64 * GNU General Public License for more details.
65 */
66
67#define OS2EMX_PLAIN_CHAR
68 // this is needed for "os2emx.h"; if this is defined,
69 // emx will define PSZ as _signed_ char, otherwise
70 // as unsigned char
71
72#define INCL_DOSFILEMGR
73#define INCL_DOSMISC
74#define INCL_DOSERRORS
75#include <os2.h>
76
77#include <stdio.h>
78#include <stdlib.h>
79#include <string.h>
80#include <stdarg.h>
81
82#include "setup.h" // code generation and debugging options
83
84#include "helpers\datetime.h"
85#include "helpers\dosh.h"
86#include "helpers\eah.h"
87#include "helpers\standards.h"
88#include "helpers\stringh.h"
89#include "helpers\tree.h"
90#include "helpers\xstring.h"
91
92#include "helpers\tmsgfile.h"
93
94/*
95 *@@category: Helpers\Control program helpers\Text message files (TMF)
96 * see tmsgfile.c.
97 */
98
99/* ******************************************************************
100 *
101 * Declarations
102 *
103 ********************************************************************/
104
105/*
106 *@@ MSGENTRY:
107 * private representation of a message in a text
108 * message file. Each tree entry in
109 * TMFMSGFILE.IDsTreeRoot points to one of these
110 * structures.
111 *
112 *@@added V0.9.16 (2001-10-08) [umoeller]
113 */
114
115typedef struct _MSGENTRY
116{
117 TREE Tree; // ulKey points to strID.psz
118 XSTRING strID; // message ID
119 ULONG ulOfsText; // offset of start of message text in file
120 ULONG cbText; // length of text in msg file
121} MSGENTRY, *PMSGENTRY;
122
123// globals
124STATIC PCSZ G_pcszStartMarker = "\n<--",
125 G_pcszEndMarker = "-->:";
126
127/* ******************************************************************
128 *
129 * Functions
130 *
131 ********************************************************************/
132
133/*
134 *@@ LoadAndCompile:
135 * loads and compiles the message file into the
136 * given TMFMSGFILE struct.
137 *
138 * This has been extracted from tmfOpenMessageFile
139 * to allow for recompiling on the fly if the
140 * message file's last-write date/time changed.
141 *
142 * Preconditions:
143 *
144 * -- pFile->pszFilename must have been set.
145 *
146 * All other fields get initialized here.
147 *
148 *@@added V0.9.18 (2002-03-24) [umoeller]
149 *@@changed V0.9.20 (2002-07-19) [umoeller]: optimized, no longer holding all msgs im mem
150 */
151
152APIRET LoadAndCompile(PTMFMSGFILE pTmf, // in: TMF struct to set up
153 PXFILE pFile) // in: opened XFILE for msg file
154{
155 APIRET arc;
156
157 PSZ pszContent = NULL;
158 ULONG cbRead;
159
160 if (!(arc = doshReadText(pFile,
161 &pszContent,
162 &cbRead))) // bytes read including null char
163 {
164 // file loaded:
165
166 PCSZ pStartOfFile,
167 pStartOfMarker;
168
169 ULONG ulStartMarkerLength = strlen(G_pcszStartMarker),
170 ulEndMarkerLength = strlen(G_pcszEndMarker);
171
172 XSTRING strContents;
173
174 // initialize TMFMSGFILE struct
175 treeInit(&pTmf->IDsTreeRoot, NULL);
176
177 xstrInit(&strContents,
178 cbRead); // including null byte
179 xstrcpy(&strContents,
180 pszContent,
181 cbRead - 1); // not including null byte
182
183 // convert to plain C format
184 /* xstrConvertLineFormat(&pTmf->strContent,
185 CRLF2LF);
186 */
187
188 // kick out all the comments
189 /*
190 while (pStartOfMarker = strstr(pTmf->strContent.psz, "\n;"))
191 {
192 // copy the next line over this
193 PCSZ pEOL = strhFindEOL(pStartOfMarker + 2, NULL);
194 xstrrpl(&pTmf->strContent,
195 // ofs of first char to replace: "\n;"
196 pStartOfMarker - pTmf->strContent.psz,
197 // no. of chars to replace:
198 pEOL - pStartOfMarker,
199 // string to replace chars with:
200 NULL,
201 // length of replacement string:
202 0);
203 }
204 */
205
206 // free excessive memory
207 // xstrShrink(&pTmf->strContent);
208
209 pStartOfFile = strContents.psz;
210
211 // go build a tree of all message IDs...
212
213 // find first start message marker
214 pStartOfMarker = strstr(pStartOfFile,
215 G_pcszStartMarker); // start-of-line marker
216 while ( (pStartOfMarker)
217 && (!arc)
218 )
219 {
220 // start marker found:
221 PCSZ pStartOfMsgID = pStartOfMarker + ulStartMarkerLength;
222 // search next start marker
223 PCSZ pStartOfNextMarker = strstr(pStartOfMsgID + 1,
224 G_pcszStartMarker); // "\n<--"
225 // and the end-marker
226 PCSZ pEndOfMarker = strstr(pStartOfMsgID + 1,
227 G_pcszEndMarker); // "-->:"
228
229 PMSGENTRY pNew;
230
231 // sanity checks...
232
233 if ( (pStartOfNextMarker)
234 && (pStartOfNextMarker < pEndOfMarker)
235 )
236 {
237 // next start marker before end marker:
238 // that doesn't look correct, skip this entry
239 pStartOfMarker = pStartOfNextMarker;
240 continue;
241 }
242
243 if (!pEndOfMarker)
244 // no end marker found:
245 // that's invalid too, and there can't be any
246 // message left in the file then...
247 break;
248
249 // alright, this ID looks correct now
250 if (!(pNew = NEW(MSGENTRY)))
251 arc = ERROR_NOT_ENOUGH_MEMORY;
252 else
253 {
254 // length of the ID
255 ULONG ulIDLength = pEndOfMarker - pStartOfMsgID;
256 PCSZ pStartOfText = pEndOfMarker + ulEndMarkerLength,
257 pNextComment;
258
259 ZERO(pNew);
260
261 // copy the string ID (between start and end markers)
262 xstrInit(&pNew->strID, 0);
263 xstrcpy(&pNew->strID,
264 pStartOfMsgID,
265 ulIDLength);
266 // make ulKey point to the string ID for tree sorting
267 pNew->Tree.ulKey = (ULONG)pNew->strID.psz;
268
269 // skip leading spaces
270 while (*pStartOfText == ' ')
271 pStartOfText++;
272
273 // store offset of start of text
274 pNew->ulOfsText = pStartOfText - pStartOfFile;
275
276 // check if there's a comment before the
277 // next item
278 if (pNextComment = strstr(pStartOfText, "\n;"))
279 {
280 if ( (!pStartOfNextMarker)
281 || (pNextComment < pStartOfNextMarker)
282 )
283 pNew->cbText = // offset of comment marker
284 (pNextComment - pStartOfFile)
285 - pNew->ulOfsText;
286 else
287 // we have a next marker AND
288 // the comment comes after it
289 pNew->cbText = // offset of next marker
290 (pStartOfNextMarker - pStartOfFile)
291 - pNew->ulOfsText;
292 }
293 else
294 if (pStartOfNextMarker)
295 // other markers left:
296 pNew->cbText = // offset of next marker
297 (pStartOfNextMarker - pStartOfFile)
298 - pNew->ulOfsText;
299 else
300 // this was the last message:
301 pNew->cbText = strlen(pStartOfText);
302
303 // remove trailing newlines
304 while ( (pNew->cbText)
305 && ( (pStartOfText[pNew->cbText - 1] == '\n')
306 || (pStartOfText[pNew->cbText - 1] == '\r')
307 )
308 )
309 (pNew->cbText)--;
310
311 // store this thing
312 if (!treeInsert(&pTmf->IDsTreeRoot,
313 NULL,
314 (TREE*)pNew,
315 treeCompareStrings))
316 // successfully inserted:
317 (pTmf->cIDs)++;
318 }
319
320 // go on with next start marker (can be NULL)
321 pStartOfMarker = pStartOfNextMarker;
322 } // end while ( (pStartOfMarker) ...
323
324 free(pszContent);
325
326 } // end else if (!(pTmf = NEW(TMFMSGFILE)))
327
328 return arc;
329}
330
331/*
332 *@@ tmfOpenMessageFile:
333 * opens a .TMF message file for future use
334 * with tmfGetMessage.
335 *
336 * Use tmfCloseMessageFile to close the file
337 * again and free all resources. This thing
338 * can allocate quite a bit of memory.
339 *
340 * Returns:
341 *
342 * -- NO_ERROR: *ppMsgFile has received the
343 * new TMFMSGFILE structure.
344 *
345 * -- ERROR_NOT_ENOUGH_MEMORY
346 *
347 * plus any of the errors of doshLoadTextFile,
348 * such as ERROR_FILE_NOT_FOUND.
349 *
350 *@@added V0.9.16 (2001-10-08) [umoeller]
351 *@@changed V0.9.20 (2002-07-19) [umoeller]: optimized, no longer holding all msgs im mem
352 */
353
354APIRET tmfOpenMessageFile(const char *pcszMessageFile, // in: fully q'fied .TMF file name
355 PTMFMSGFILE *ppMsgFile) // out: TMFMSGFILE struct
356{
357 APIRET arc;
358
359 ULONG cbFile = 0; // V1.0.7 (2005-05-21) [pr]: Workaround for kernel bug
360 PXFILE pFile;
361 if (!(arc = doshOpen(pcszMessageFile,
362 XOPEN_READ_EXISTING,
363 &cbFile,
364 &pFile)))
365 {
366 // create a TMFMSGFILE entry
367 PTMFMSGFILE pTmf;
368 if (!(pTmf = NEW(TMFMSGFILE)))
369 arc = ERROR_NOT_ENOUGH_MEMORY;
370 else
371 {
372 ZERO(pTmf);
373 pTmf->pszFilename = strdup(pcszMessageFile);
374
375 // TMFMSGFILE created:
376 if (!(arc = LoadAndCompile(pTmf, pFile)))
377 {
378 // set timestamp to that of the file
379 FILESTATUS3 fs3;
380 if (!(arc = DosQueryFileInfo(pFile->hf,
381 FIL_STANDARD,
382 &fs3,
383 sizeof(fs3))))
384 {
385 dtCreateFileTimeStamp(pTmf->szTimestamp,
386 &fs3.fdateLastWrite,
387 &fs3.ftimeLastWrite);
388
389 // output
390 *ppMsgFile = pTmf;
391 }
392 }
393
394 if (arc)
395 // error:
396 tmfCloseMessageFile(&pTmf);
397 }
398
399 doshClose(&pFile);
400 }
401
402 return arc;
403}
404
405/*
406 *@@ FreeInternalMem:
407 * cleans out the internal message file compilation
408 * and the content string. Used by both tmfCloseMessageFile
409 * and tmfGetMessage to allow for recompiles when the
410 * last-write date/time changed.
411 *
412 *@@added V0.9.18 (2002-03-24) [umoeller]
413 */
414
415STATIC VOID FreeInternalMem(PTMFMSGFILE pTmf)
416{
417 LONG cItems;
418 TREE** papNodes;
419
420 if (cItems = pTmf->cIDs)
421 {
422 if (papNodes = treeBuildArray(pTmf->IDsTreeRoot,
423 &cItems))
424 {
425 ULONG ul;
426 for (ul = 0; ul < cItems; ul++)
427 {
428 PMSGENTRY pNodeThis = (PMSGENTRY)(papNodes[ul]);
429
430 xstrClear(&pNodeThis->strID);
431
432 free(pNodeThis);
433 }
434
435 free(papNodes);
436 }
437 }
438}
439
440/*
441 *@@ tmfCloseMessageFile:
442 * closes a message file opened by
443 * tmfOpenMessageFile, frees all resources,
444 * and sets *ppMsgFile to NULL for safety.
445 *
446 *@@added V0.9.16 (2001-10-08) [umoeller]
447 */
448
449APIRET tmfCloseMessageFile(PTMFMSGFILE *ppMsgFile)
450{
451 if (ppMsgFile && *ppMsgFile)
452 {
453 PTMFMSGFILE pTmf = *ppMsgFile;
454
455 if (pTmf->pszFilename)
456 free(pTmf->pszFilename);
457
458 FreeInternalMem(pTmf);
459
460 free(pTmf);
461 *ppMsgFile = NULL;
462
463 return NO_ERROR;
464 }
465
466 return (ERROR_INVALID_PARAMETER);
467}
468
469/*
470 *@@ tmfGetMessage:
471 * replacement for DosGetMessage.
472 *
473 * After you have opened a .TMF file with tmfOpenMessageFile,
474 * you can pass it to this function to retrieve a message
475 * with the given string (!) ID. See tmsgfile.c for details.
476 *
477 * Note that this will invoke xstrcpy on the given XSTRING
478 * buffer. In other words, the string must be initialized
479 * (see xstrInit), but will be replaced.
480 *
481 * This does perform the same brain-dead string replacements
482 * as DosGetMessage, that is, the string "%1" will be
483 * replaced with pTable[0], "%2" will be replaced with
484 * pTable[1], and so on.
485 *
486 * Returns:
487 *
488 * -- NO_ERROR;
489 *
490 * -- ERROR_INVALID_PARAMETER
491 *
492 * -- ERROR_MR_MID_NOT_FOUND
493 *
494 *@@added V0.9.16 (2001-10-08) [umoeller]
495 *@@changed V0.9.18 (2002-03-24) [umoeller]: now recompiling if last write date changed
496 *@@changed V0.9.20 (2002-07-19) [umoeller]: optimized, no longer holding all msgs im mem
497 */
498
499APIRET tmfGetMessage(PTMFMSGFILE pMsgFile, // in: msg file opened by tmfOpenMessageFile
500 PCSZ pcszMessageName, // in: msg name to look for (case-sensitive!)
501 PXSTRING pstr, // out: message string, if found (XSTRING must be initialized)
502 PCSZ *pTable, // in: replacement table or NULL
503 ULONG cTableEntries) // in: count of items in pTable or null
504{
505 APIRET arc = NO_ERROR;
506
507 if ( (!pMsgFile)
508 || (!pMsgFile->pszFilename)
509 )
510 arc = ERROR_INVALID_PARAMETER;
511 else
512 {
513 // open the file again V0.9.20 (2002-07-19) [umoeller]
514 ULONG cbFile = 0; // V1.0.7 (2005-05-21) [pr]: Workaround for kernel bug
515 PXFILE pFile;
516 if (!(arc = doshOpen(pMsgFile->pszFilename,
517 XOPEN_READ_EXISTING,
518 &cbFile,
519 &pFile)))
520 {
521 // check if last-write date/time changed compared
522 // to the last time we opened the thing...
523 // V0.9.18 (2002-03-24) [umoeller]
524 FILESTATUS3 fs3;
525 if (!(arc = DosQueryFileInfo(pFile->hf,
526 FIL_STANDARD,
527 &fs3,
528 sizeof(fs3))))
529 {
530 CHAR szTemp[30];
531 dtCreateFileTimeStamp(szTemp,
532 &fs3.fdateLastWrite,
533 &fs3.ftimeLastWrite);
534 if (strcmp(szTemp, pMsgFile->szTimestamp))
535 {
536 // last write date changed:
537 _Pmpf((__FUNCTION__ ": timestamp changed, recompiling"));
538 FreeInternalMem(pMsgFile);
539
540 if (!(arc = LoadAndCompile(pMsgFile, pFile)))
541 strcpy(pMsgFile->szTimestamp, szTemp);
542 }
543 }
544
545 if (!arc)
546 {
547 // go find the message in the tree
548 PMSGENTRY pEntry;
549 if (!(pEntry = (PMSGENTRY)treeFind(pMsgFile->IDsTreeRoot,
550 (ULONG)pcszMessageName,
551 treeCompareStrings)))
552 arc = ERROR_MR_MID_NOT_FOUND;
553 else
554 {
555 PSZ pszMsg;
556 ULONG cbRead = pEntry->cbText;
557
558 if (!(pszMsg = (PSZ)malloc(cbRead + 1)))
559 arc = ERROR_NOT_ENOUGH_MEMORY;
560 else if (!(arc = doshReadAt(pFile,
561 pEntry->ulOfsText,
562 &cbRead,
563 pszMsg,
564 DRFL_NOCACHE | DRFL_FAILIFLESS)))
565 {
566 // null-terminate
567 pszMsg[cbRead] = '\0';
568 xstrset2(pstr,
569 pszMsg,
570 cbRead);
571
572 // kick out \r\n
573 xstrConvertLineFormat(pstr,
574 CRLF2LF);
575
576 // now replace strings from the table
577 if (cTableEntries && pTable)
578 {
579 CHAR szFind[10] = "%0";
580 ULONG ul;
581 for (ul = 0;
582 ul < cTableEntries;
583 ul++)
584 {
585 ULONG ulOfs = 0;
586
587 _ultoa(ul + 1, szFind + 1, 10);
588 while (xstrFindReplaceC(pstr,
589 &ulOfs,
590 szFind,
591 pTable[ul]))
592 ;
593 }
594 }
595 }
596 }
597 }
598
599 doshClose(&pFile);
600 }
601 }
602
603 return arc;
604}
605
606/* test case */
607
608#ifdef __TMFDEBUG__
609
610int main(int argc, char *argv[])
611{
612 APIRET arc;
613 PTMFMSGFILE pMsgFile;
614
615 if (argc < 3)
616 printf("tmsgfile <file> <msgid>\n");
617 else
618 {
619 if (!(arc = tmfOpenMessageFile(argv[1], &pMsgFile)))
620 {
621 XSTRING str;
622 xstrInit(&str, 0);
623 if (!(arc = tmfGetMessage(pMsgFile,
624 argv[2],
625 &str,
626 NULL,
627 0)))
628 {
629 printf("String:\n%s", str.psz);
630 }
631 else
632 printf("tmfGetMessage returned %d\n", arc);
633
634 xstrClear(&str);
635
636 tmfCloseMessageFile(&pMsgFile);
637 }
638 else
639 printf("tmfOpenMessageFile returned %d\n", arc);
640 }
641}
642
643#endif
Note: See TracBrowser for help on using the repository browser.