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

Last change on this file since 154 was 151, checked in by umoeller, 23 years ago

Misc fixes

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 17.8 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 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 *
108 *@@added V0.9.16 (2001-10-08) [umoeller]
109 */
110
111typedef struct _MSGENTRY
112{
113 TREE Tree; // ulKey points to strID.psz
114 XSTRING strID; // message ID
115 ULONG ulOfsText; // offset of start of text (in C-format buffer)
116 ULONG cbText; // length of text in msg file
117} MSGENTRY, *PMSGENTRY;
118
119// globals
120static PCSZ G_pcszStartMarker = "\n<--",
121 G_pcszEndMarker = "-->:";
122
123/* ******************************************************************
124 *
125 * Functions
126 *
127 ********************************************************************/
128
129/*
130 *@@ LoadAndCompile:
131 * loads and compiles the message file into the
132 * given TMFMSGFILE struct.
133 *
134 * This has been extracted from tmfOpenMessageFile
135 * to allow for recompiling on the fly if the
136 * message file's last-write date/time changed.
137 *
138 * Preconditions:
139 *
140 * -- pFile->pszFilename must have been set.
141 *
142 * All other fields get initialized here.
143 *
144 *@@added V0.9.18 (2002-03-24) [umoeller]
145 */
146
147APIRET LoadAndCompile(PTMFMSGFILE pFile)
148{
149 APIRET arc;
150
151 PSZ pszContent = NULL;
152 if (!(arc = doshLoadTextFile(pFile->pszFilename,
153 &pszContent,
154 NULL)))
155 {
156 // file loaded:
157
158 PCSZ pStartOfFile,
159 pStartOfMarker;
160
161 ULONG ulStartMarkerLength = strlen(G_pcszStartMarker),
162 ulEndMarkerLength = strlen(G_pcszEndMarker);
163
164 // initialize TMFMSGFILE struct
165 treeInit(&pFile->IDsTreeRoot, NULL);
166
167 xstrInitSet(&pFile->strContent, pszContent);
168
169 // convert to plain C format
170 xstrConvertLineFormat(&pFile->strContent,
171 CRLF2LF);
172
173 // kick out all the comments
174 while (pStartOfMarker = strstr(pFile->strContent.psz, "\n;"))
175 {
176 // copy the next line over this
177 PCSZ pEOL = strhFindEOL(pStartOfMarker + 2, NULL);
178 xstrrpl(&pFile->strContent,
179 // ofs of first char to replace: "\n;"
180 pStartOfMarker - pFile->strContent.psz,
181 // no. of chars to replace:
182 pEOL - pStartOfMarker,
183 // string to replace chars with:
184 NULL,
185 // length of replacement string:
186 0);
187 }
188
189 // free excessive memory
190 xstrShrink(&pFile->strContent);
191
192 pStartOfFile = pFile->strContent.psz;
193
194 // go build a tree of all message IDs...
195
196 // find first start message marker
197 pStartOfMarker = strstr(pStartOfFile,
198 G_pcszStartMarker); // start-of-line marker
199 while ( (pStartOfMarker)
200 && (!arc)
201 )
202 {
203 // start marker found:
204 PCSZ pStartOfMsgID = pStartOfMarker + ulStartMarkerLength;
205 // search next start marker
206 PCSZ pStartOfNextMarker = strstr(pStartOfMsgID + 1,
207 G_pcszStartMarker);
208 // and the end-marker
209 PCSZ pEndOfMarker = strstr(pStartOfMsgID + 1,
210 G_pcszEndMarker);
211
212 PMSGENTRY pNew;
213
214 // sanity checks...
215
216 if ( (pStartOfNextMarker)
217 && (pStartOfNextMarker < pEndOfMarker)
218 )
219 {
220 // next start marker before end marker:
221 // that doesn't look correct, skip this entry
222 pStartOfMarker = pStartOfNextMarker;
223 continue;
224 }
225
226 if (!pEndOfMarker)
227 // no end marker found:
228 // that's invalid too, and there can't be any
229 // message left in the file then...
230 break;
231
232 // alright, this ID looks correct now
233 if (!(pNew = NEW(MSGENTRY)))
234 arc = ERROR_NOT_ENOUGH_MEMORY;
235 else
236 {
237 // length of the ID
238 ULONG ulIDLength = pEndOfMarker - pStartOfMsgID;
239 PCSZ pStartOfText = pEndOfMarker + ulEndMarkerLength;
240
241 ZERO(pNew);
242
243 // copy the string ID (between start and end markers)
244 xstrInit(&pNew->strID, 0);
245 xstrcpy(&pNew->strID,
246 pStartOfMsgID,
247 ulIDLength);
248 // make ulKey point to the string ID for tree sorting
249 pNew->Tree.ulKey = (ULONG)pNew->strID.psz;
250
251 // skip leading spaces
252 while (*pStartOfText == ' ')
253 pStartOfText++;
254
255 // store start of text
256 pNew->ulOfsText = pStartOfText - pStartOfFile;
257
258 // check if there's a comment before the
259 // next item
260 /* if (pNextComment = strstr(pStartOfText, "\n;"))
261 {
262 if ( (!pStartOfNextMarker)
263 || (pNextComment < pStartOfNextMarker)
264 )
265 pEndOfText = pNextComment;
266 } */
267
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
278 while ( (pNew->cbText)
279 && (pStartOfText[pNew->cbText-1] == '\n')
280 )
281 (pNew->cbText)--;
282
283 // store this thing
284 if (!treeInsert(&pFile->IDsTreeRoot,
285 NULL,
286 (TREE*)pNew,
287 treeCompareStrings))
288 // successfully inserted:
289 (pFile->cIDs)++;
290 }
291
292 // go on with next start marker (can be NULL)
293 pStartOfMarker = pStartOfNextMarker;
294 } // end while ( (pStartOfMarker) ...
295 } // end else if (!(pFile = NEW(TMFMSGFILE)))
296
297 return (arc);
298}
299
300/*
301 *@@ tmfOpenMessageFile:
302 * opens a .TMF message file for future use
303 * with tmfGetMessage.
304 *
305 * Use tmfCloseMessageFile to close the file
306 * again and free all resources. This thing
307 * can allocate quite a bit of memory.
308 *
309 * Returns:
310 *
311 * -- NO_ERROR: *ppMsgFile has received the
312 * new TMFMSGFILE structure.
313 *
314 * -- ERROR_NOT_ENOUGH_MEMORY
315 *
316 * plus any of the errors of doshLoadTextFile,
317 * such as ERROR_FILE_NOT_FOUND.
318 *
319 *@@added V0.9.16 (2001-10-08) [umoeller]
320 */
321
322APIRET tmfOpenMessageFile(const char *pcszMessageFile, // in: fully q'fied .TMF file name
323 PTMFMSGFILE *ppMsgFile) // out: TMFMSGFILE struct
324{
325 APIRET arc;
326
327 FILESTATUS3 fs3;
328 if (!(arc = DosQueryPathInfo((PSZ)pcszMessageFile,
329 FIL_STANDARD,
330 &fs3,
331 sizeof(fs3))))
332 {
333 // create a TMFMSGFILE entry
334 PTMFMSGFILE pFile;
335 if (!(pFile = NEW(TMFMSGFILE)))
336 arc = ERROR_NOT_ENOUGH_MEMORY;
337 else
338 {
339 ZERO(pFile);
340 pFile->pszFilename = strdup(pcszMessageFile);
341
342 // TMFMSGFILE created:
343 if (!(arc = LoadAndCompile(pFile)))
344 {
345 // set timestamp to that of the file
346 dtCreateFileTimeStamp(pFile->szTimestamp,
347 &fs3.fdateLastWrite,
348 &fs3.ftimeLastWrite);
349
350 // output
351 *ppMsgFile = pFile;
352 }
353 else
354 // error:
355 tmfCloseMessageFile(&pFile);
356 }
357 }
358
359 return (arc);
360}
361
362/*
363 *@@ FreeInternalMem:
364 * cleans out the internal message file compilation
365 * and the content string. Used by both tmfCloseMessageFile
366 * and tmfGetMessage to allow for recompiles when the
367 * last-write date/time changed.
368 *
369 *@@added V0.9.18 (2002-03-24) [umoeller]
370 */
371
372static VOID FreeInternalMem(PTMFMSGFILE pFile)
373{
374 LONG cItems;
375 TREE** papNodes;
376
377 xstrClear(&pFile->strContent);
378
379 if (cItems = pFile->cIDs)
380 {
381 if (papNodes = treeBuildArray(pFile->IDsTreeRoot,
382 &cItems))
383 {
384 ULONG ul;
385 for (ul = 0; ul < cItems; ul++)
386 {
387 PMSGENTRY pNodeThis = (PMSGENTRY)(papNodes[ul]);
388
389 xstrClear(&pNodeThis->strID);
390
391 free(pNodeThis);
392 }
393
394 free(papNodes);
395 }
396 }
397}
398
399/*
400 *@@ tmfCloseMessageFile:
401 * closes a message file opened by
402 * tmfOpenMessageFile, frees all resources,
403 * and sets *ppMsgFile to NULL for safety.
404 *
405 *@@added V0.9.16 (2001-10-08) [umoeller]
406 */
407
408APIRET tmfCloseMessageFile(PTMFMSGFILE *ppMsgFile)
409{
410 if (ppMsgFile && *ppMsgFile)
411 {
412 PTMFMSGFILE pFile = *ppMsgFile;
413
414 if (pFile->pszFilename)
415 free(pFile->pszFilename);
416
417 FreeInternalMem(pFile);
418
419 free(pFile);
420 *ppMsgFile = NULL;
421
422 return (NO_ERROR);
423 }
424
425 return (ERROR_INVALID_PARAMETER);
426}
427
428/*
429 *@@ tmfGetMessage:
430 * replacement for DosGetMessage.
431 *
432 * After you have opened a .TMF file with tmfOpenMessageFile,
433 * you can pass it to this function to retrieve a message
434 * with the given string (!) ID. See tmsgfile.c for details.
435 *
436 * Note that this will invoke xstrcpy on the given XSTRING
437 * buffer. In other words, the string must be initialized
438 * (see xstrInit), but will be replaced.
439 *
440 * This does perform the same brain-dead string replacements
441 * as DosGetMessage, that is, the string "%1" will be
442 * replaced with pTable[0], "%2" will be replaced with
443 * pTable[1], and so on.
444 *
445 * Returns:
446 *
447 * -- NO_ERROR;
448 *
449 * -- ERROR_INVALID_PARAMETER
450 *
451 * -- ERROR_MR_MID_NOT_FOUND
452 *
453 *@@added V0.9.16 (2001-10-08) [umoeller]
454 *@@changed V0.9.18 (2002-03-24) [umoeller]: now recompiling if last write date changed
455 */
456
457APIRET tmfGetMessage(PTMFMSGFILE pMsgFile, // in: msg file opened by tmfOpenMessageFile
458 PCSZ pcszMessageName, // in: msg name to look for (case-sensitive!)
459 PXSTRING pstr, // out: message string, if found (XSTRING must be initialized)
460 PCSZ *pTable, // in: replacement table or NULL
461 ULONG cTableEntries) // in: count of items in pTable or null
462{
463 APIRET arc = NO_ERROR;
464
465 if (!pMsgFile)
466 arc = ERROR_INVALID_PARAMETER;
467 else
468 {
469 // check if last-write date/time changed compared
470 // to the last time we opened the thing...
471 // V0.9.18 (2002-03-24) [umoeller]
472 FILESTATUS3 fs3;
473 if (!(arc = DosQueryPathInfo(pMsgFile->pszFilename,
474 FIL_STANDARD,
475 &fs3,
476 sizeof(fs3))))
477 {
478 CHAR szTemp[30];
479 dtCreateFileTimeStamp(szTemp,
480 &fs3.fdateLastWrite,
481 &fs3.ftimeLastWrite);
482 if (strcmp(szTemp, pMsgFile->szTimestamp))
483 {
484 // last write date changed:
485 _Pmpf((__FUNCTION__ ": timestamp changed, recompiling"));
486 FreeInternalMem(pMsgFile);
487 if (!(arc = LoadAndCompile(pMsgFile)))
488 strcpy(pMsgFile->szTimestamp, szTemp);
489 }
490 }
491
492 if (!arc)
493 {
494 // go find the message in the tree
495 PMSGENTRY pEntry;
496 if (pEntry = (PMSGENTRY)treeFind(pMsgFile->IDsTreeRoot,
497 (ULONG)pcszMessageName,
498 treeCompareStrings))
499 {
500 // copy the raw string to the output buffer
501 xstrcpy(pstr,
502 pMsgFile->strContent.psz + pEntry->ulOfsText,
503 pEntry->cbText);
504
505 // now replace strings from the table
506 if (cTableEntries && pTable)
507 {
508 CHAR szFind[10] = "%0";
509 ULONG ul;
510 for (ul = 0;
511 ul < cTableEntries;
512 ul++)
513 {
514 ULONG ulOfs = 0;
515
516 _ultoa(ul + 1, szFind + 1, 10);
517 while (xstrFindReplaceC(pstr,
518 &ulOfs,
519 szFind,
520 pTable[ul]))
521 ;
522 }
523 }
524 }
525 else
526 arc = ERROR_MR_MID_NOT_FOUND;
527 }
528 }
529
530 return (arc);
531}
532
533/* test case */
534
535#ifdef __TMFDEBUG__
536
537int main(int argc, char *argv[])
538{
539 APIRET arc;
540 PTMFMSGFILE pMsgFile;
541
542 if (argc < 3)
543 printf("tmsgfile <file> <msgid>\n");
544 else
545 {
546 if (!(arc = tmfOpenMessageFile(argv[1], &pMsgFile)))
547 {
548 XSTRING str;
549 xstrInit(&str, 0);
550 if (!(arc = tmfGetMessage(pMsgFile,
551 argv[2],
552 &str,
553 NULL,
554 0)))
555 {
556 printf("String:\n%s", str.psz);
557 }
558 else
559 printf("tmfGetMessage returned %d\n", arc);
560
561 xstrClear(&str);
562
563 tmfCloseMessageFile(&pMsgFile);
564 }
565 else
566 printf("tmfOpenMessageFile returned %d\n", arc);
567 }
568}
569
570#endif
Note: See TracBrowser for help on using the repository browser.