source: trunk/src/helpers/tmsgfile.c@ 327

Last change on this file since 327 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: 20.0 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 *@@changed V1.0.1 (2002-12-16) [pr]: Ctrl-Z fix
151 */
152
153APIRET LoadAndCompile(PTMFMSGFILE pTmf, // in: TMF struct to set up
154 PXFILE pFile) // in: opened XFILE for msg file
155{
156 APIRET arc;
157
158 PSZ pszContent = NULL;
159 ULONG cbRead;
160
161 if (!(arc = doshReadText(pFile,
162 &pszContent,
163 &cbRead))) // bytes read including null char
164 {
165 // file loaded:
166
167 PCSZ pStartOfFile,
168 pStartOfMarker;
169
170 ULONG ulStartMarkerLength = strlen(G_pcszStartMarker),
171 ulEndMarkerLength = strlen(G_pcszEndMarker);
172
173 XSTRING strContents;
174
175 // initialize TMFMSGFILE struct
176 treeInit(&pTmf->IDsTreeRoot, NULL);
177
178 xstrInit(&strContents,
179 cbRead); // including null byte
180 xstrcpy(&strContents,
181 pszContent,
182 cbRead - 1); // not including null byte
183
184 // go build a tree of all message IDs...
185 pStartOfFile = strContents.psz;
186
187 // find first start message marker
188 pStartOfMarker = strstr(pStartOfFile,
189 G_pcszStartMarker); // start-of-line marker
190 while ( (pStartOfMarker)
191 && (!arc)
192 )
193 {
194 // start marker found:
195 PCSZ pStartOfMsgID = pStartOfMarker + ulStartMarkerLength;
196 // search next start marker
197 PCSZ pStartOfNextMarker = strstr(pStartOfMsgID + 1,
198 G_pcszStartMarker); // "\n<--"
199 // and the end-marker
200 PCSZ pEndOfMarker = strstr(pStartOfMsgID + 1,
201 G_pcszEndMarker); // "-->:"
202
203 PMSGENTRY pNew;
204
205 // sanity checks...
206
207 if ( (pStartOfNextMarker)
208 && (pStartOfNextMarker < pEndOfMarker)
209 )
210 {
211 // next start marker before end marker:
212 // that doesn't look correct, skip this entry
213 pStartOfMarker = pStartOfNextMarker;
214 continue;
215 }
216
217 if (!pEndOfMarker)
218 // no end marker found:
219 // that's invalid too, and there can't be any
220 // message left in the file then...
221 break;
222
223 // alright, this ID looks correct now
224 if (!(pNew = NEW(MSGENTRY)))
225 arc = ERROR_NOT_ENOUGH_MEMORY;
226 else
227 {
228 // length of the ID
229 ULONG ulIDLength = pEndOfMarker - pStartOfMsgID;
230 PCSZ pStartOfText = pEndOfMarker + ulEndMarkerLength,
231 pNextComment;
232
233 ZERO(pNew);
234
235 // copy the string ID (between start and end markers)
236 xstrInit(&pNew->strID, 0);
237 xstrcpy(&pNew->strID,
238 pStartOfMsgID,
239 ulIDLength);
240 // make ulKey point to the string ID for tree sorting
241 pNew->Tree.ulKey = (ULONG)pNew->strID.psz;
242
243 // skip leading spaces
244 while (*pStartOfText == ' ')
245 pStartOfText++;
246
247 // store offset of start of text
248 pNew->ulOfsText = pStartOfText - pStartOfFile;
249
250 // check if there's a comment before the
251 // next item
252 if (pNextComment = strstr(pStartOfText, "\n;"))
253 {
254 if ( (!pStartOfNextMarker)
255 || (pNextComment < pStartOfNextMarker)
256 )
257 pNew->cbText = // offset of comment marker
258 (pNextComment - pStartOfFile)
259 - pNew->ulOfsText;
260 else
261 // we have a next marker AND
262 // the comment comes after it
263 pNew->cbText = // offset of next marker
264 (pStartOfNextMarker - pStartOfFile)
265 - pNew->ulOfsText;
266 }
267 else
268 if (pStartOfNextMarker)
269 // other markers left:
270 pNew->cbText = // offset of next marker
271 (pStartOfNextMarker - pStartOfFile)
272 - pNew->ulOfsText;
273 else
274 // this was the last message:
275 pNew->cbText = strlen(pStartOfText);
276
277 // remove trailing newlines and Ctrl-Z
278 while ( (pNew->cbText)
279 && ( (pStartOfText[pNew->cbText - 1] == '\n')
280 || (pStartOfText[pNew->cbText - 1] == '\r')
281 || (pStartOfText[pNew->cbText - 1] == '\x1a') // V1.0.1 (2002-12-16) [pr]
282 )
283 )
284 (pNew->cbText)--;
285
286 // store this thing
287 if (!treeInsert(&pTmf->IDsTreeRoot,
288 NULL,
289 (TREE*)pNew,
290 treeCompareStrings))
291 // successfully inserted:
292 (pTmf->cIDs)++;
293 }
294
295 // go on with next start marker (can be NULL)
296 pStartOfMarker = pStartOfNextMarker;
297 } // end while ( (pStartOfMarker) ...
298
299 free(pszContent);
300
301 } // end else if (!(pTmf = NEW(TMFMSGFILE)))
302
303 return arc;
304}
305
306/*
307 *@@ tmfOpenMessageFile:
308 * opens a .TMF message file for future use
309 * with tmfGetMessage.
310 *
311 * Use tmfCloseMessageFile to close the file
312 * again and free all resources.
313 *
314 * Returns:
315 *
316 * -- NO_ERROR: *ppMsgFile has received the
317 * new TMFMSGFILE structure.
318 *
319 * -- ERROR_NOT_ENOUGH_MEMORY
320 *
321 * plus any of the errors of doshLoadTextFile,
322 * such as ERROR_FILE_NOT_FOUND.
323 *
324 *@@added V0.9.16 (2001-10-08) [umoeller]
325 *@@changed V0.9.20 (2002-07-19) [umoeller]: optimized, no longer holding all msgs im mem
326 */
327
328APIRET tmfOpenMessageFile(const char *pcszMessageFile, // in: fully q'fied .TMF file name
329 PTMFMSGFILE *ppMsgFile) // out: TMFMSGFILE struct
330{
331 APIRET arc;
332
333 ULONG cbFile = 0; // V1.0.7 (2005-05-21) [pr]: Workaround for kernel bug
334 PXFILE pFile;
335 if (!(arc = doshOpen(pcszMessageFile,
336 XOPEN_READ_EXISTING,
337 &cbFile,
338 &pFile)))
339 {
340 // create a TMFMSGFILE entry
341 PTMFMSGFILE pTmf;
342 if (!(pTmf = NEW(TMFMSGFILE)))
343 arc = ERROR_NOT_ENOUGH_MEMORY;
344 else
345 {
346 ZERO(pTmf);
347 pTmf->pszFilename = strdup(pcszMessageFile);
348
349 // TMFMSGFILE created:
350 if (!(arc = LoadAndCompile(pTmf, pFile)))
351 {
352 // set timestamp to that of the file
353 FILESTATUS3 fs3;
354 if (!(arc = DosQueryFileInfo(pFile->hf,
355 FIL_STANDARD,
356 &fs3,
357 sizeof(fs3))))
358 {
359 dtCreateFileTimeStamp(pTmf->szTimestamp,
360 &fs3.fdateLastWrite,
361 &fs3.ftimeLastWrite);
362
363 // output
364 *ppMsgFile = pTmf;
365 }
366 }
367
368 if (arc)
369 // error:
370 tmfCloseMessageFile(&pTmf);
371 }
372
373 doshClose(&pFile);
374 }
375
376 return arc;
377}
378
379/*
380 *@@ FreeInternalMem:
381 * cleans out the internal message file compilation
382 * and the content string. Used by both tmfCloseMessageFile
383 * and tmfGetMessage to allow for recompiles when the
384 * last-write date/time changed.
385 *
386 *@@added V0.9.18 (2002-03-24) [umoeller]
387 */
388
389STATIC VOID FreeInternalMem(PTMFMSGFILE pTmf)
390{
391 LONG cItems;
392 TREE** papNodes;
393
394 if ( (cItems = pTmf->cIDs)
395 && (papNodes = treeBuildArray(pTmf->IDsTreeRoot,
396 &cItems))
397 )
398 {
399 ULONG ul;
400 for (ul = 0; ul < cItems; ul++)
401 {
402 PMSGENTRY pNodeThis = (PMSGENTRY)(papNodes[ul]);
403
404 xstrClear(&pNodeThis->strID);
405
406 free(pNodeThis);
407 }
408
409 free(papNodes);
410 }
411}
412
413/*
414 *@@ tmfCloseMessageFile:
415 * closes a message file opened by
416 * tmfOpenMessageFile, frees all resources,
417 * and sets *ppMsgFile to NULL for safety.
418 *
419 *@@added V0.9.16 (2001-10-08) [umoeller]
420 */
421
422APIRET tmfCloseMessageFile(PTMFMSGFILE *ppMsgFile)
423{
424 if (ppMsgFile && *ppMsgFile)
425 {
426 PTMFMSGFILE pTmf = *ppMsgFile;
427
428 if (pTmf->pszFilename)
429 free(pTmf->pszFilename);
430
431 FreeInternalMem(pTmf);
432
433 free(pTmf);
434 *ppMsgFile = NULL;
435
436 return NO_ERROR;
437 }
438
439 return ERROR_INVALID_PARAMETER;
440}
441
442/*
443 *@@ tmfGetMessage:
444 * replacement for DosGetMessage.
445 *
446 * After you have opened a .TMF file with tmfOpenMessageFile,
447 * you can pass it to this function to retrieve a message
448 * with the given string (!) ID. See tmsgfile.c for details.
449 *
450 * Note that this will invoke xstrcpy on the given XSTRING
451 * buffer. In other words, the string must be initialized
452 * (see xstrInit), but will be replaced.
453 *
454 * This does perform the same brain-dead string replacements
455 * as DosGetMessage, that is, the string "%1" will be
456 * replaced with pTable[0], "%2" will be replaced with
457 * pTable[1], and so on.
458 *
459 * Returns:
460 *
461 * -- NO_ERROR;
462 *
463 * -- ERROR_INVALID_PARAMETER
464 *
465 * -- ERROR_MR_MID_NOT_FOUND
466 *
467 *@@added V0.9.16 (2001-10-08) [umoeller]
468 *@@changed V0.9.18 (2002-03-24) [umoeller]: now recompiling if last write date changed
469 *@@changed V0.9.20 (2002-07-19) [umoeller]: optimized, no longer holding all msgs im mem
470 */
471
472APIRET tmfGetMessage(PTMFMSGFILE pMsgFile, // in: msg file opened by tmfOpenMessageFile
473 PCSZ pcszMessageName, // in: msg name to look for (case-sensitive!)
474 PXSTRING pstr, // out: message string, if found (XSTRING must be initialized)
475 PCSZ *pTable, // in: replacement table or NULL
476 ULONG cTableEntries) // in: count of items in pTable or null
477{
478 APIRET arc;
479 ULONG cbFile = 0; // V1.0.7 (2005-05-21) [pr]: Workaround for kernel bug
480 PXFILE pFile;
481
482 if ( (!pMsgFile)
483 || (!pMsgFile->pszFilename)
484 )
485 return ERROR_INVALID_PARAMETER;
486
487 // open the file again V0.9.20 (2002-07-19) [umoeller]
488 if (!(arc = doshOpen(pMsgFile->pszFilename,
489 XOPEN_READ_EXISTING,
490 &cbFile,
491 &pFile)))
492 {
493 // check if last-write date/time changed compared
494 // to the last time we opened the thing...
495 // V0.9.18 (2002-03-24) [umoeller]
496 FILESTATUS3 fs3;
497 if (!(arc = DosQueryFileInfo(pFile->hf,
498 FIL_STANDARD,
499 &fs3,
500 sizeof(fs3))))
501 {
502 CHAR szTemp[30];
503 dtCreateFileTimeStamp(szTemp,
504 &fs3.fdateLastWrite,
505 &fs3.ftimeLastWrite);
506 if (strcmp(szTemp, pMsgFile->szTimestamp))
507 {
508 // last write date changed:
509 _Pmpf((__FUNCTION__ ": timestamp changed, recompiling"));
510 FreeInternalMem(pMsgFile);
511
512 if (!(arc = LoadAndCompile(pMsgFile, pFile)))
513 strcpy(pMsgFile->szTimestamp, szTemp);
514 }
515 }
516
517 if (!arc)
518 {
519 // go find the message in the tree
520 PMSGENTRY pEntry;
521 if (!(pEntry = (PMSGENTRY)treeFind(pMsgFile->IDsTreeRoot,
522 (ULONG)pcszMessageName,
523 treeCompareStrings)))
524 arc = ERROR_MR_MID_NOT_FOUND;
525 else
526 {
527 PSZ pszMsg;
528 ULONG cbRead = pEntry->cbText;
529
530 if (!(pszMsg = (PSZ)malloc(cbRead + 1)))
531 arc = ERROR_NOT_ENOUGH_MEMORY;
532 else if (!(arc = doshReadAt(pFile,
533 pEntry->ulOfsText,
534 &cbRead,
535 pszMsg,
536 DRFL_NOCACHE | DRFL_FAILIFLESS)))
537 {
538 // null-terminate
539 pszMsg[cbRead] = '\0';
540 xstrset2(pstr,
541 pszMsg,
542 cbRead);
543
544 // kick out \r\n
545 xstrConvertLineFormat(pstr,
546 CRLF2LF);
547
548 // now replace strings from the table
549 if (cTableEntries && pTable)
550 {
551 CHAR szFind[10] = "%0";
552 ULONG ul;
553 for (ul = 0;
554 ul < cTableEntries;
555 ul++)
556 {
557 ULONG ulOfs = 0;
558
559 _ultoa(ul + 1, szFind + 1, 10);
560 while (xstrFindReplaceC(pstr,
561 &ulOfs,
562 szFind,
563 pTable[ul]))
564 ;
565 }
566 }
567 }
568 }
569 }
570
571 doshClose(&pFile);
572 }
573
574 return arc;
575}
576
577/* test case */
578
579#ifdef __TMFDEBUG__
580
581int main(int argc, char *argv[])
582{
583 APIRET arc;
584 PTMFMSGFILE pMsgFile;
585
586 if (argc < 3)
587 printf("tmsgfile <file> <msgid>\n");
588 else
589 {
590 if (!(arc = tmfOpenMessageFile(argv[1], &pMsgFile)))
591 {
592 XSTRING str;
593 xstrInit(&str, 0);
594 if (!(arc = tmfGetMessage(pMsgFile,
595 argv[2],
596 &str,
597 NULL,
598 0)))
599 {
600 printf("String:\n%s", str.psz);
601 }
602 else
603 printf("tmfGetMessage returned %d\n", arc);
604
605 xstrClear(&str);
606
607 tmfCloseMessageFile(&pMsgFile);
608 }
609 else
610 printf("tmfOpenMessageFile returned %d\n", arc);
611 }
612}
613
614#endif
Note: See TracBrowser for help on using the repository browser.