Changeset 108 for trunk/src/helpers/tmsgfile.c
- Timestamp:
- Oct 13, 2001, 7:57:58 PM (24 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/helpers/tmsgfile.c
r102 r108 2 2 /* 3 3 *@@sourcefile tmsgfile.c: 4 * contains a "text message file" helper function, 5 * which works similar to DosGetMessage. See 6 * tmfGetMessage for details. 7 * 8 * tmfGetMessage operates on plain-text ".TMF" files. 9 * The file is "compiled" into the file's extended 10 * attributes for faster access after the first access. 11 * When the file's contents change, the EAs are recompiled 12 * automatically. 13 * 14 * This has the following advantages over DosGetMessage: 4 * replacement code for DosGetMessage for decent NLS support. 5 * 6 * This code has the following advantages over DosGetMessage: 15 7 * 16 8 * 1) No external utility is necessary to change message … … 21 13 * only numerical IDs. 22 14 * 23 * This file is entirely new with V0.9.0. 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 requires 39 * tmfOpenMessageFile to be called beforehand. 24 40 * 25 41 * Usage: All OS/2 programs. … … 28 44 * -- tmf* text message file functions 29 45 * 30 * This code uses only C standard library functions31 * which are also part of the subsystem libraries.32 * It does _not_ use the fopen etc. functions, but33 * DosOpen etc. instead.34 * You can therefore use this code with your software35 * which uses the subsystem libraries only.36 *37 * This code was kindly provided by Christian Langanke.38 * Modifications April 1999 by Ulrich Mller. Any39 * substantial changes (other than comments and40 * formatting) are marked with (*UM).41 *42 46 * Note: Version numbering in this file relates to XWorkplace version 43 47 * numbering. … … 48 52 49 53 /* 50 * Copyright (C) 1999 Christian Langanke. 51 * Copyright (C) 1999-2000 Ulrich Mller. 54 * Copyright (C) 2001 Ulrich Mller. 52 55 * This file is part of the "XWorkplace helpers" source package. 53 56 * This is free software; you can redistribute it and/or modify … … 68 71 #define INCL_DOSFILEMGR 69 72 #define INCL_DOSMISC 70 #define INCL_DOSNLS71 73 #define INCL_DOSERRORS 72 74 #include <os2.h> … … 79 81 #include "setup.h" // code generation and debugging options 80 82 83 #include "helpers\dosh.h" 81 84 #include "helpers\eah.h" 85 #include "helpers\standards.h" 86 #include "helpers\stringh.h" 87 #include "helpers\tree.h" 88 #include "helpers\xstring.h" 89 82 90 #include "helpers\tmsgfile.h" 83 91 … … 93 101 ********************************************************************/ 94 102 95 // extended attribute used for timestamp; written to .TMF file 96 #define EA_TIMESTAMP "TMF.FILEINFO" 97 // extended attribute used for compiled text file; written to .TMF file 98 #define EA_MSGTABLE "TMF.MSGTABLE" 99 100 #define NEWLINE "\n" 101 102 // start-of-msgid marker 103 #define MSG_NAME_START "\r\n<--" 104 // end-of-msgid marker 105 #define MSG_NAME_END "-->:" // fixed: the space had to be removed, because 106 // if a newline was the first character after ":", 107 // we got complete garbage (crashes) (99-10-23) [umoeller] 108 // comment marker 109 #define MSG_COMMENT_LINE "\r\n;" 110 111 // internal prototypes 112 APIRET CompileMsgTable(PSZ pszMessageFile, PBYTE * ppbTableData); 113 APIRET GetTimeStamp(PFILESTATUS3 pfs3, PSZ pszBuffer, ULONG ulBufferlen); 103 /* 104 *@@ MSGENTRY: 105 * 106 *@@added V0.9.16 (2001-10-08) [umoeller] 107 */ 108 109 typedef struct _MSGENTRY 110 { 111 TREE Tree; // ulKey points to strID.psz 112 XSTRING strID; // message ID 113 ULONG ulOfsText; // offset of start of text (in C-format buffer) 114 ULONG cbText; // length of text in msg file 115 } MSGENTRY, *PMSGENTRY; 116 117 // globals 118 static PCSZ G_pcszStartMarker = "\n<--", 119 G_pcszEndMarker = "-->:"; 114 120 115 121 /* ****************************************************************** 116 122 * 117 * Text Message File Code123 * Functions 118 124 * 119 125 ********************************************************************/ 120 126 121 127 /* 122 *@@ tmfGetMessage: 123 * just like DosGetMessage, except that this does not 124 * take a simple message number for the message in the 125 * given message file, but a PSZ message identifier 126 * (pszMessageName). 127 * 128 * This function automatically compiles the given .TMF 129 * file into the file's extended attributes, if we find 130 * that this is necessary, by calling CompileMsgTable. 131 * 132 * Note that for compatibily, this function performs the 133 * same brain-dead string processing as DosGetMessage, that 134 * is, the return string is _not_ null-terminated, and 135 * trailing newline characters are _not_ cut off. 136 * 137 * If you don't like this, call tmfGetMessageExt, which 138 * calls this function in turn. 139 * 140 * <B>Returns:</B> 141 * -- ERROR_INVALID_PARAMETER 142 * -- ERROR_BUFFER_OVERFLOW 143 * -- ERROR_MR_MID_NOT_FOUND 144 * -- ERROR_MR_INV_MSGF_FORMAT 145 * -- ERROR_BUFFER_OVERFLOW 146 * 147 * plus the error codes of DosOpen, DosRead, DosClose. 148 */ 149 150 APIRET tmfGetMessage(PCHAR* pTable, // in: pointer table for string insertion 151 ULONG cTable, // in: number of pointers in *pTable 152 PBYTE pbBuffer, // out: returned string 153 ULONG cbBuffer, // out: sizeof(*pbBuffer) 154 PCSZ pszMessageName, // in: message identifier 155 PCSZ pszFile, // in: message file (.TMF extension proposed) 156 PULONG pcbMsg) // out: bytes written to *pbBuffer 128 *@@ tmfOpenMessageFile: 129 * opens a .TMF message file for future use 130 * with tmfGetMessage. 131 * 132 * Use tmfCloseMessageFile to close the file 133 * again and free all resources. This thing 134 * can allocate quite a bit of memory. 135 * 136 * Returns: 137 * 138 * -- NO_ERROR: *ppMsgFile has received the 139 * new TMFMSGFILE structure. 140 * 141 * -- ERROR_NOT_ENOUGH_MEMORY 142 * 143 * plus any of the errors of doshLoadTextFile, 144 * such as ERROR_FILE_NOT_FOUND. 145 * 146 *@@added V0.9.16 (2001-10-08) [umoeller] 147 */ 148 149 APIRET tmfOpenMessageFile(const char *pcszMessageFile, // in: fully q'fied .TMF file name 150 PTMFMSGFILE *ppMsgFile) // out: TMFMSGFILE struct 157 151 { 158 // fixed UM 99-10-22: now initializing all vars to 0, 159 // because ulBytesRead might be returned from this func 160 APIRET rc = NO_ERROR; 161 CHAR szMessageFile[_MAX_PATH]; 162 PBYTE pbTableData = NULL; 163 PSZ pszEntry = 0; 164 PSZ pszEndOfEntry = 0; 165 ULONG ulMessagePos = 0; 166 ULONG ulMessageLen = 0; 167 168 HFILE hfile = NULLHANDLE; 169 ULONG ulFilePtr = 0; 170 ULONG ulAction = 0; 171 ULONG ulBytesToRead = 0; 172 ULONG ulBytesRead = 0; 173 174 do 152 APIRET arc; 153 PSZ pszContent = NULL; 154 155 if (!(arc = doshLoadTextFile(pcszMessageFile, 156 &pszContent))) 175 157 { 176 // check parms 177 if ((!pbBuffer) || 178 (!cbBuffer) || 179 (!pszMessageName) || 180 (!*pszMessageName) || 181 (!*pszFile)) 158 // file loaded: 159 // create a TMFMSGFILE entry 160 PTMFMSGFILE pFile; 161 if (!(pFile = NEW(TMFMSGFILE))) 182 162 { 183 rc = ERROR_INVALID_PARAMETER;184 break;163 arc = ERROR_NOT_ENOUGH_MEMORY; 164 free(pszContent); 185 165 } 186 187 if (cbBuffer < 2)188 {189 rc = ERROR_BUFFER_OVERFLOW;190 break;191 }192 193 // search file194 if ((strchr(pszFile, ':')) ||195 (strchr(pszFile, '\\')) ||196 (strchr(pszFile, '/')))197 // drive and/or path given: no search in path198 strcpy(szMessageFile, pszFile);199 166 else 200 167 { 201 // only filename, search in current dir and DPATH 202 rc = DosSearchPath(SEARCH_IGNORENETERRS 203 | SEARCH_ENVIRONMENT 204 | SEARCH_CUR_DIRECTORY, 205 "DPATH", 206 (PSZ)pszFile, 207 szMessageFile, 208 sizeof(szMessageFile)); 209 if (rc != NO_ERROR) 210 break; 211 } 212 213 // _Pmpf(("tmfGetMessage: Found %s", szMessageFile)); 214 215 // compile table if neccessary 216 rc = CompileMsgTable(szMessageFile, &pbTableData); 217 if (rc != NO_ERROR) 218 break; 219 220 // _Pmpf(("tmfGetMessage: Now searching compiled file")); 221 222 // search the name 223 pszEntry = strstr(pbTableData, pszMessageName); 224 if (!pszEntry) 225 { 226 rc = ERROR_MR_MID_NOT_FOUND; 227 break; 228 } 229 else 230 pszEntry += strlen(pszMessageName) + 1; 231 232 // isolate entry 233 pszEndOfEntry = strchr(pszEntry, '\n'); 234 if (pszEndOfEntry) 235 *pszEndOfEntry = 0; 236 237 // get numbers 238 ulMessagePos = atol(pszEntry); 239 if (ulMessagePos == 0) 240 if (!pszEntry) 168 // TMFMSGFILE created: 169 170 PCSZ pStartOfFile, 171 pStartOfMarker; 172 173 ULONG ulStartMarkerLength = strlen(G_pcszStartMarker), 174 ulEndMarkerLength = strlen(G_pcszEndMarker); 175 176 // initialize TMFMSGFILE struct 177 ZERO(pFile); 178 pFile->pszFilename = strdup(pcszMessageFile); 179 treeInit(&pFile->IDsTreeRoot); 180 181 xstrInitSet(&pFile->strContent, pszContent); 182 183 // convert to plain C format 184 xstrConvertLineFormat(&pFile->strContent, 185 CRLF2LF); 186 187 // kick out all the comments 188 while (pStartOfMarker = strstr(pFile->strContent.psz, "\n;")) 241 189 { 242 rc = ERROR_MR_INV_MSGF_FORMAT; 243 break; 190 // copy the next line over this 191 PCSZ pEOL = strhFindEOL(pStartOfMarker + 2, NULL); 192 /* printf("pStartOfMarker = %lX, pEOL = %lX\n", 193 pStartOfMarker, 194 pEOL); */ 195 xstrrpl(&pFile->strContent, 196 // ofs of first char to replace: "\n;" 197 pStartOfMarker - pFile->strContent.psz, 198 // no. of chars to replace: 199 pEOL - pStartOfMarker, 200 // string to replace chars with: 201 NULL, 202 // length of replacement string: 203 0); 244 204 } 245 205 246 pszEntry = strchr(pszEntry, ' '); 247 if (!pszEntry) 248 { 249 rc = ERROR_MR_INV_MSGF_FORMAT; 250 break; 251 } 252 ulMessageLen = atol(pszEntry); 253 254 // determine maximum read len 255 ulBytesToRead = _min(ulMessageLen, cbBuffer); 256 257 // open file and read message 258 rc = DosOpen((PSZ)pszFile, 259 &hfile, 260 &ulAction, 261 0, 0, 262 OPEN_ACTION_FAIL_IF_NEW 263 | OPEN_ACTION_OPEN_IF_EXISTS, 264 OPEN_FLAGS_FAIL_ON_ERROR 265 | OPEN_SHARE_DENYWRITE 266 | OPEN_ACCESS_READONLY, 267 NULL); 268 if (rc != NO_ERROR) 269 break; 270 271 rc = DosSetFilePtr(hfile, ulMessagePos, FILE_BEGIN, &ulFilePtr); 272 if ((rc != NO_ERROR) || (ulFilePtr != ulMessagePos)) 273 break; 274 275 rc = DosRead(hfile, 276 pbBuffer, 277 ulBytesToRead, 278 &ulBytesRead); 279 if (rc != NO_ERROR) 280 break; 281 282 // report "buffer too small" here 283 if (ulBytesToRead < ulMessageLen) 284 rc = ERROR_BUFFER_OVERFLOW; 285 286 } 287 while (FALSE); 288 289 // replace string placeholders ("%1" etc.) (*UM) 290 if (rc == NO_ERROR) // added (99-10-24) [umoeller] 291 if (cTable) 292 { 293 // create a temporary buffer for replacements 294 PSZ pszTemp = (PSZ)malloc(cbBuffer); 295 296 if (!pszTemp) 297 rc = ERROR_NOT_ENOUGH_MEMORY; 206 // free excessive memory 207 xstrShrink(&pFile->strContent); 208 209 pStartOfFile = pFile->strContent.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); 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 258 ZERO(pNew); 259 260 // copy the string ID (between start and end markers) 261 xstrInit(&pNew->strID, 0); 262 xstrcpy(&pNew->strID, 263 pStartOfMsgID, 264 ulIDLength); 265 // make ulKey point to the string ID for tree sorting 266 pNew->Tree.ulKey = (ULONG)pNew->strID.psz; 267 268 // skip leading spaces 269 while (*pStartOfText == ' ') 270 pStartOfText++; 271 272 // store start of text 273 pNew->ulOfsText = pStartOfText - pStartOfFile; 274 275 // check if there's a comment before the 276 // next item 277 /* if (pNextComment = strstr(pStartOfText, "\n;")) 278 { 279 if ( (!pStartOfNextMarker) 280 || (pNextComment < pStartOfNextMarker) 281 ) 282 pEndOfText = pNextComment; 283 } */ 284 285 if (pStartOfNextMarker) 286 // other markers left: 287 pNew->cbText = // offset of next marker 288 (pStartOfNextMarker - pStartOfFile) 289 - pNew->ulOfsText; 290 else 291 // this was the last message: 292 pNew->cbText = strlen(pStartOfText); 293 294 // store this thing 295 if (!treeInsert(&pFile->IDsTreeRoot, 296 (TREE*)pNew, 297 treeCompareStrings)) 298 // successfully inserted: 299 (pFile->cIDs)++; 300 } 301 302 // go on with next start marker (can be NULL) 303 pStartOfMarker = pStartOfNextMarker; 304 } // end while ( (pStartOfMarker) ... 305 306 // done with IDs, or error occured: 307 if (!arc) 308 // output 309 *ppMsgFile = pFile; 298 310 else 299 { 300 // two pointers for copying 301 PSZ pSource = pbBuffer, 302 pTarget = pszTemp; 303 304 CHAR szMsgNum[5] = "0"; 305 ULONG ulTableIndex = 0; 306 307 ULONG ulCount = 0, ulTargetWritten = 0; 308 309 // 1) copy the stuff we've read to the 310 // new temporary buffer 311 for (ulCount = 0; 312 ulCount < ulBytesRead; 313 ulCount++) 314 *pTarget++ = *pSource++; 315 316 // 2) now go thru the source string 317 // (which we've read above) and 318 // look for "%" characters; we do 319 // this while copying the stuff 320 // back to pbBuffer 321 322 pSource = pszTemp; 323 pTarget = pbBuffer; 324 325 for (ulCount = 0; 326 ulCount < ulBytesRead; 327 ulCount++) 328 { 329 if (*pSource == '%') 330 { 331 // copy the index (should be > 0) 332 szMsgNum[0] = *(pSource + 1); 333 // szMsgNum[1] is still 0 334 335 // Pmpf((" Found %s", szMsgNum)); 336 337 ulTableIndex = atoi(szMsgNum); 338 // _Pmpf((" --> %d", ulTableIndex)); 339 if ((ulTableIndex != 0) 340 && (ulTableIndex <= cTable) 341 ) 342 { 343 // valid index: 344 // insert stuff from table 345 PSZ *ppTableThis = (PSZ*)(pTable + (ulTableIndex - 1)); 346 347 // (pTable) if 1, (pTable+1) if 2, ... 348 PSZ pTableSource = *ppTableThis; 349 350 // copy string from table 351 // and increase the target pointer 352 while ((*pTarget++ = *pTableSource++)) 353 ulTargetWritten++; 354 355 // increase source pointer to point 356 // behind the "%x" 357 pSource += 2; 358 // decrease target again, because 359 // we've copied a null-byte 360 pTarget--; 361 362 ulCount++; 363 // next for 364 continue; 365 } 366 // else invalid index: just copy 367 } 368 369 // regular character: copy 370 *pTarget++ = *pSource++; 371 ulTargetWritten++; 372 } // end for (ulCount = 0) ... 373 374 ulBytesRead = ulTargetWritten; 375 376 free(pszTemp); // V0.9.9 (2001-03-13) [umoeller] 377 } // if pszTemp 378 } // end if (cTable) 379 380 // report bytes written (*UM) 381 if (pcbMsg) 382 *pcbMsg = ulBytesRead; 383 384 // cleanup 385 if (hfile) 386 DosClose(hfile); 387 if (pbTableData) 388 free(pbTableData); 389 390 return (rc); 391 } 392 393 /* 394 *@@ tmfGetMessageExt: 395 * just like tmfGetMessage, but this func is smart 396 * enough to return a zero-terminated string and 397 * strip trailing newlines. 398 * 399 * See tmfGetMessage for details. 400 */ 401 402 APIRET tmfGetMessageExt(PCHAR* pTable, // in: pointer table for string insertion 403 ULONG cTable, // in: number of pointers in *pTable 404 PBYTE pbBuffer, // out: returned string 405 ULONG cbBuffer, // out: sizeof(*pbBuffer) 406 PCSZ pszMessageName, // in: message identifier 407 PCSZ pszFile, // in: message file (.TMF extension proposed) 408 PULONG pcbMsg) // out: bytes written to *pbBuffer 409 { 410 APIRET arc = NO_ERROR; 411 ULONG cbReturned = 0; 412 arc = tmfGetMessage(pTable, cTable, pbBuffer, cbBuffer, pszMessageName, pszFile, 413 &cbReturned); 414 415 if (arc == NO_ERROR) 416 { 417 PSZ p = pbBuffer; 418 419 // terminate string 420 if (cbReturned < cbBuffer) 421 *(pbBuffer+cbReturned) = 0; 422 else 423 *(pbBuffer+cbBuffer-1) = 0; 424 425 // remove leading spaces (99-10-24) [umoeller] 426 while (*p == ' ') 427 p++; 428 if (p != pbBuffer) 429 { 430 // we have leading spaces: 431 strcpy(pbBuffer, p); 432 // decrease byte count 433 cbReturned -= (p - pbBuffer); 434 } 435 436 // remove trailing newlines 437 while (TRUE) 438 { 439 p = pbBuffer + strlen(pbBuffer) - 1; 440 if ( (*p == '\n') 441 || (*p == '\r') 442 ) 443 { 444 *p = '\0'; 445 } else 446 break; // while (TRUE) 447 } 448 } 449 450 if (pcbMsg) 451 *pcbMsg = cbReturned; 311 // error: 312 tmfCloseMessageFile(&pFile); 313 314 } // end else if (!(pFile = NEW(TMFMSGFILE))) 315 } // end if (!(arc = doshLoadTextFile(pcszMessageFile, 452 316 453 317 return (arc); … … 455 319 456 320 /* 457 *@@ CompileMsgTable: 458 * this gets called from tmfGetMessage. 459 * Here we check for whether the given 460 * message file has been compiled to the 461 * EAs yet or if the compilation is 462 * outdated. 463 * 464 * If we need recompilation, we do this here. 465 * Memory for the compiled message table is 466 * allocated here using malloc(), and the 467 * buffer is stored in ppbTableData. You 468 * must free() this buffer after using this 469 * function. 470 * 471 * <B>Returns:</B> 321 *@@ tmfCloseMessageFile: 322 * closes a message file opened by 323 * tmfOpenMessageFile, frees all resources, 324 * and sets *ppMsgFile to NULL for safety. 325 * 326 *@@added V0.9.16 (2001-10-08) [umoeller] 327 */ 328 329 APIRET tmfCloseMessageFile(PTMFMSGFILE *ppMsgFile) 330 { 331 if (ppMsgFile && *ppMsgFile) 332 { 333 PTMFMSGFILE pFile = *ppMsgFile; 334 ULONG cItems; 335 TREE** papNodes; 336 337 if (pFile->pszFilename) 338 free(pFile->pszFilename); 339 xstrClear(&pFile->strContent); 340 341 if (cItems = pFile->cIDs) 342 { 343 if (papNodes = treeBuildArray(pFile->IDsTreeRoot, 344 &cItems)) 345 { 346 ULONG ul; 347 for (ul = 0; ul < cItems; ul++) 348 { 349 PMSGENTRY pNodeThis = (PMSGENTRY)(papNodes[ul]); 350 351 xstrClear(&pNodeThis->strID); 352 353 free(pNodeThis); 354 } 355 356 free(papNodes); 357 } 358 } 359 360 free(pFile); 361 *ppMsgFile = NULL; 362 363 return (NO_ERROR); 364 } 365 366 return (ERROR_INVALID_PARAMETER); 367 } 368 369 /* 370 *@@ tmfGetMessage: 371 * replacement for DosGetMessage. 372 * 373 * After you have opened a .TMF file with tmfOpenMessageFile, 374 * you can pass it to this function to retrieve a message 375 * with the given string (!) ID. See tmsgfile.c for details. 376 * 377 * Note that this will invoke xstrcpy on the given XSTRING 378 * buffer. In other words, the string must be initialized 379 * (see xstrInit), but will be replaced. 380 * 381 * This does perform the same simple string replacements 382 * as DosGetMessage, that is, the string "%1" will be 383 * replaced with pTable[0], "%2" will be replaced with 384 * pTable[1], and so on. 385 * 386 * Returns: 387 * 388 * -- NO_ERROR; 389 * 472 390 * -- ERROR_INVALID_PARAMETER 473 * -- ERROR_BUFFER_OVERFLOW 474 * -- ERROR_NOT_ENOUGH_MEMORY 475 * -- ERROR_READ_FAULT 476 * -- ERROR_INVALID_DATA 477 * 478 *@@changed V0.9.1 (99-12-12) [umoeller]: fixed heap overwrites which caused irregular crashes 479 *@@changed V0.9.1 (99-12-12) [umoeller]: fixed realloc failure 480 *@@changed V0.9.1 (99-12-12) [umoeller]: file last-write date is now reset when EAs are written 481 *@@changed V0.9.1 (2000-02-01) [umoeller]: now skipping leading spaces in msg 482 *@@todo handle 64KB EA limit 483 */ 484 485 APIRET CompileMsgTable(PSZ pszMessageFile, // in: file to compile 486 PBYTE *ppbTableDataReturn) // out: compiled message table 391 * 392 * -- ERROR_MR_MID_NOT_FOUND 393 * 394 *@@added V0.9.16 (2001-10-08) [umoeller] 395 */ 396 397 APIRET tmfGetMessage(PTMFMSGFILE pMsgFile, // in: msg file opened by tmfOpenMessageFile 398 PCSZ pcszMessageName, // in: msg name to look for (case-sensitive!) 399 PXSTRING pstr, // out: message string, if found (XSTRING must be initialized) 400 PSZ *pTable, // in: replacement table or NULL 401 ULONG cTableEntries) // in: count of items in pTable or null 487 402 { 488 APIRET rc = NO_ERROR; 489 // CHAR szMessageFile[_MAX_PATH]; 490 491 FILESTATUS3 fs3MessageFile; 492 493 // fixed UM 99-10-22: initializing all vars to 0 494 ULONG ulStampLength = 0; 495 PBYTE pbFileData = NULL; 496 ULONG ulFileDataLength = 0; 497 498 CHAR szFileStampOld[18] = ""; // yyyymmddhhmmssms. 499 500 CHAR szFileStampCurrent[18] = ""; 501 502 PBYTE pbTableData = NULL; 503 ULONG cbTableDataAllocated = 0; 504 ULONG cbTableDataUsed = 0; 505 506 HFILE hfileMessageFile = NULLHANDLE; 507 ULONG ulAction = 0; 508 ULONG ulBytesRead = 0; 509 510 COUNTRYCODE cc = {0, 0}; 511 512 ULONG cbOpenTag = strlen(MSG_NAME_START); 513 ULONG cbClosingTag = strlen(MSG_NAME_END); 514 515 do 403 APIRET arc = NO_ERROR; 404 405 if (!pMsgFile) 406 arc = ERROR_INVALID_PARAMETER; 407 else 516 408 { 517 PSZ pCurrentNameStart = 0; 518 PSZ pCurrentNameEnd = 0; 519 ULONG ulCurrentMessagePos = 0; 520 ULONG ulCurrentMessageLen = 0; 521 // BOOL fError = FALSE; 522 523 // check parms 524 if ((!pszMessageFile) || 525 (!ppbTableDataReturn)) 409 // go find the message in the tree 410 PMSGENTRY pEntry; 411 if (pEntry = (PMSGENTRY)treeFind(pMsgFile->IDsTreeRoot, 412 (ULONG)pcszMessageName, 413 treeCompareStrings)) 526 414 { 527 rc = ERROR_INVALID_PARAMETER; 528 break; 529 } 530 531 // get length and timestamp of file 532 rc = DosQueryPathInfo(pszMessageFile, 533 FIL_STANDARD, 534 &fs3MessageFile, 535 sizeof(fs3MessageFile)); 536 if (rc != NO_ERROR) 537 break; 538 ulFileDataLength = fs3MessageFile.cbFile; 539 540 // determine current timestamp 541 GetTimeStamp(&fs3MessageFile, szFileStampCurrent, sizeof(szFileStampCurrent)); 542 543 // _Pmpf((" Timestamp for %s: '%s'", pszMessageFile, szFileStampCurrent)); 544 545 // determine saved timestamp 546 ulStampLength = sizeof(szFileStampOld); 547 rc = eahReadStringEA(pszMessageFile, 548 EA_TIMESTAMP, 549 szFileStampOld, 550 &ulStampLength); 551 552 // _Pmpf((" Saved timestamp for %s: '%s'", pszMessageFile, szFileStampOld)); 553 554 // compare timestamps 555 if ((rc == NO_ERROR) 556 && (ulStampLength == (strlen(szFileStampCurrent) + 1)) 557 && (!strcmp(szFileStampCurrent, szFileStampOld)) 558 ) 559 { 560 561 // read table out of EAs 562 do 415 // copy the raw string to the output buffer 416 xstrcpy(pstr, 417 pMsgFile->strContent.psz + pEntry->ulOfsText, 418 pEntry->cbText); 419 420 // now replace strings from the table 421 if (cTableEntries && pTable) 563 422 { 564 // get ea length of table 565 rc = eahReadStringEA(pszMessageFile, 566 EA_MSGTABLE, 567 NULL, &cbTableDataAllocated); 568 if (rc != ERROR_BUFFER_OVERFLOW) 569 break; 570 571 // get memory 572 if ((pbTableData = (PBYTE)malloc(cbTableDataAllocated)) == NULL) 423 CHAR szFind[34] = "%0"; 424 ULONG ul; 425 for (ul = 0; 426 ul < cTableEntries; 427 ul++) 573 428 { 574 rc = ERROR_NOT_ENOUGH_MEMORY; 575 break; 576 } 577 578 // read table 579 rc = eahReadStringEA(pszMessageFile, 580 EA_MSGTABLE, 581 pbTableData, &cbTableDataAllocated); 582 583 } 584 while (FALSE); 585 586 // if no error occurred, we are finished 587 if (rc == NO_ERROR) 588 { 589 // _Pmpf((" --> using precompiled table")); 590 break; 591 } 592 } // end if 593 594 // _Pmpf((" --> recompiling table")); 595 596 // recompilation needed: 597 // get memory for file data 598 if ((pbFileData = (PBYTE)malloc(ulFileDataLength + 1)) == NULL) 599 { 600 rc = ERROR_NOT_ENOUGH_MEMORY; 601 break; 602 } 603 *(pbFileData + ulFileDataLength) = 0; 604 605 // get memory for table data 606 cbTableDataAllocated = ulFileDataLength / 2; 607 if ((pbTableData = (PBYTE)malloc(cbTableDataAllocated)) == NULL) 608 { 609 rc = ERROR_NOT_ENOUGH_MEMORY; 610 break; 611 } 612 613 *pbTableData = 0; 614 // fixed V0.9.1 (99-12-12); 615 // this was 616 // *(pbTableData + cbTableDataAllocated) = 0; 617 // since we're using "strcat" below to write into 618 // the table data, this wasn't such a good idea 619 620 // open file and read it 621 rc = DosOpen(pszMessageFile, 622 &hfileMessageFile, 623 &ulAction, 624 0, 0, 625 OPEN_ACTION_FAIL_IF_NEW 626 | OPEN_ACTION_OPEN_IF_EXISTS, 627 OPEN_FLAGS_FAIL_ON_ERROR 628 | OPEN_SHARE_DENYWRITE 629 | OPEN_ACCESS_READWRITE, // needed for EA attachement 630 NULL); 631 if (rc != NO_ERROR) 632 break; 633 634 rc = DosRead(hfileMessageFile, 635 pbFileData, 636 ulFileDataLength, 637 &ulBytesRead); 638 if (rc != NO_ERROR) 639 break; 640 if (ulBytesRead != ulFileDataLength) 641 { 642 rc = ERROR_READ_FAULT; 643 break; 644 } 645 646 // search first message name 647 // this will automatically skip comments 648 // at the beginning 649 pCurrentNameStart = strstr(pbFileData, MSG_NAME_START); 650 651 if (!pCurrentNameStart) 652 { 653 rc = ERROR_INVALID_DATA; 654 break; 655 } 656 else 657 pCurrentNameStart += cbOpenTag; 658 // this points to message ID string after "<--" now 659 660 // is first name complete ? 661 pCurrentNameEnd = strstr(pCurrentNameStart, MSG_NAME_END); 662 if (!pCurrentNameEnd) 663 { 664 rc = ERROR_INVALID_DATA; 665 break; 666 } 667 668 // scan through all names 669 while ( (pCurrentNameStart) 670 && (*pCurrentNameStart) 671 ) 672 { 673 PSZ pNextNameStart = 0, 674 pNextCommentStart = 0, 675 pEndOfMsg = 0; 676 CHAR szEntry[500] = ""; 677 678 // search end of name, if not exist, skip end of file 679 pCurrentNameEnd = strstr(pCurrentNameStart, MSG_NAME_END); 680 // this points to the closing "-->" now 681 if (!pCurrentNameEnd) 682 break; 683 684 // search next name (ID), if none, use end of string 685 pNextNameStart = strstr(pCurrentNameEnd, MSG_NAME_START); 686 // points to next "<--" now 687 if (!pNextNameStart) 688 // not found: 689 // use terminating null byte 690 pNextNameStart = pCurrentNameStart + strlen(pCurrentNameStart); 691 else 692 // found: 693 // point to next message ID string 694 pNextNameStart += cbOpenTag; 695 696 pEndOfMsg = pNextNameStart - cbOpenTag; 697 698 // search next comment (99-11-28) [umoeller] 699 pNextCommentStart = strstr(pCurrentNameEnd, MSG_COMMENT_LINE); 700 if (pNextCommentStart) 701 // another comment found: 702 if (pNextCommentStart < pNextNameStart) 703 // if it's even before the next key, use that 704 // for the end of the current string; otherwise 705 // the comment would appear in the message too 706 pEndOfMsg = pNextCommentStart; 707 708 // now we have: 709 // -- pCurrentNameStart: points to message ID after "<--" 710 // -- pCurrentNameEnd: points to "-->" 711 // -- pNextNameStart: points to _next_ message ID after "<--" 712 // -- pNextCommentStart: points to _next_ comment 713 // -- pEndOfMsg: either pNextNameStart-cbOpenTag 714 // or pNextCommentStart, 715 // whichever comes first 716 717 // calculate table entry data 718 *pCurrentNameEnd = 0; 719 ulCurrentMessagePos = (pCurrentNameEnd - pbFileData) + cbClosingTag; 720 // this points to the first character after the <-- --> tag now; 721 // if this points to a space character, take next char 722 // (V0.9.1 (2000-02-13) [umoeller]) 723 while ( (*(pbFileData + ulCurrentMessagePos)) 724 && (*(pbFileData + ulCurrentMessagePos) == ' ') 725 ) 726 ulCurrentMessagePos++; 727 728 ulCurrentMessageLen = (pEndOfMsg - pbFileData) - ulCurrentMessagePos; 729 // pszNextNameStart - pbFileData - ulCurrentMessagePos - 1; 730 731 // determine entry 732 sprintf(szEntry, "%s %lu %lu" NEWLINE, 733 pCurrentNameStart, 734 ulCurrentMessagePos, 735 ulCurrentMessageLen); 736 737 /* _Pmpf(("Found %s at %d, length %d", 738 pCurrentNameStart, 739 ulCurrentMessagePos, 740 ulCurrentMessageLen)); */ 741 742 // need more space ? 743 if ((cbTableDataUsed + strlen(szEntry) + 1) > cbTableDataAllocated) 744 { 745 PBYTE pbTmp; 746 // _Pmpf((" Re-allocating!!")); 747 748 cbTableDataAllocated += ulFileDataLength / 2; 749 pbTmp = (PBYTE)realloc(pbTableData, cbTableDataAllocated); 750 if (!pbTmp) 751 { 752 rc = ERROR_NOT_ENOUGH_MEMORY; 753 break; 754 } 755 else 756 pbTableData = pbTmp; 757 } 758 759 // add entry 760 strcat(pbTableData, szEntry); 761 cbTableDataUsed += strlen(szEntry); 762 // added V0.9.1 (99-12-12); 763 // this was 0 all the time and never raised 764 765 // adress next entry 766 pCurrentNameStart = pNextNameStart; 767 768 } // while (pszCurrentNameStart) 769 770 // write new timestamp and table 771 // @@todo handle 64 kb limit here !!! 772 if (rc == NO_ERROR) 773 { 774 FILESTATUS3 fs3Tmp; 775 776 rc = eahWriteStringEA(hfileMessageFile, EA_TIMESTAMP, szFileStampCurrent); 777 rc = eahWriteStringEA(hfileMessageFile, EA_MSGTABLE, pbTableData); 778 779 if (rc == NO_ERROR) 780 { 781 // fixed V0.9.1 (99-12-12): we need to reset the file date to 782 // the old date, 783 // because writing EAs updates the "last write date" also; 784 // without this, we'd recompile every single time 785 rc = DosQueryFileInfo(hfileMessageFile, 786 FIL_STANDARD, 787 &fs3Tmp, 788 sizeof(fs3Tmp)); 789 if (rc == NO_ERROR) 790 { 791 // restore original "last write" date 792 // _Pmpf(("Resetting file date")); 793 fs3Tmp.fdateLastWrite = fs3MessageFile.fdateLastWrite; 794 fs3Tmp.ftimeLastWrite = fs3MessageFile.ftimeLastWrite; 795 DosSetFileInfo(hfileMessageFile, 796 FIL_STANDARD, 797 &fs3Tmp, 798 sizeof(fs3Tmp)); 429 ULONG ulOfs = 0; 430 PSZ pszReplThis = pTable[ul]; 431 432 _ultoa(ul + 1, szFind + 1, 10); 433 while (xstrFindReplaceC(pstr, 434 &ulOfs, 435 szFind, 436 pszReplThis)) 437 ; 799 438 } 800 439 } 801 440 } 802 } while (FALSE); 803 804 805 if (rc == NO_ERROR) 806 { 807 // hand over result 808 *ppbTableDataReturn = pbTableData; 809 810 // make text uppercase 811 rc = DosMapCase(cbTableDataAllocated, &cc, pbTableData); 441 else 442 arc = ERROR_MR_MID_NOT_FOUND; 812 443 } 444 445 return (arc); 446 } 447 448 /* test case */ 449 450 #ifdef __TMFDEBUG__ 451 452 int main(int argc, char *argv[]) 453 { 454 APIRET arc; 455 PTMFMSGFILE pMsgFile; 456 457 if (argc < 3) 458 printf("tmsgfile <file> <msgid>\n"); 813 459 else 814 460 { 815 // error occured: 816 if (pbTableData) 817 free(pbTableData); 461 if (!(arc = tmfOpenMessageFile(argv[1], &pMsgFile))) 462 { 463 XSTRING str; 464 xstrInit(&str, 0); 465 if (!(arc = tmfGetMessage(pMsgFile, 466 argv[2], 467 &str, 468 NULL, 469 0))) 470 { 471 printf("String:\n%s", str.psz); 472 } 473 else 474 printf("tmfGetMessage returned %d\n", arc); 475 476 xstrClear(&str); 477 478 tmfCloseMessageFile(&pMsgFile); 479 } 480 else 481 printf("tmfOpenMessageFile returned %d\n", arc); 818 482 } 819 820 // cleanup821 if (pbFileData)822 free(pbFileData);823 if (hfileMessageFile)824 DosClose(hfileMessageFile);825 826 return rc;827 483 } 828 484 829 /* 830 * GetTimeStamp: 831 * this returns the "last write date" timestamp 832 * of the file specified in pfs3. 833 * 834 * <B>Usage:</B> called from CompileMsgTable 835 * to check whether the compilation is 836 * outdated. 837 * 838 * Returns: 839 * -- NO_ERROR 840 * -- ERROR_INVALID_PARAMETER 841 * -- ERROR_BUFFER_OVERFLOW: pszBuffer too small for timestamp 842 */ 843 844 APIRET GetTimeStamp(PFILESTATUS3 pfs3, // in: file to check 845 PSZ pszBuffer, // out: timestamp 846 ULONG ulBufferlen) // in: sizeof(*pszBuffer) 847 { 848 849 APIRET rc = NO_ERROR; 850 CHAR szTimeStamp[15]; 851 static PSZ pszFormatTimestamp = "%4u%02u%02u%02u%02u%02u%"; 852 853 do 854 { 855 // check parms 856 if ((!pfs3) || 857 (!pszBuffer)) 858 { 859 rc = ERROR_INVALID_PARAMETER; 860 break; 861 } 862 863 // create stamp 864 sprintf(szTimeStamp, 865 pszFormatTimestamp, 866 pfs3->fdateLastWrite.year + 1980, 867 pfs3->fdateLastWrite.month, 868 pfs3->fdateLastWrite.day, 869 pfs3->ftimeLastWrite.hours, 870 pfs3->ftimeLastWrite.minutes, 871 pfs3->ftimeLastWrite.twosecs * 2); 872 873 // check bufferlen 874 if (strlen(szTimeStamp) + 1 > ulBufferlen) 875 { 876 rc = ERROR_BUFFER_OVERFLOW; 877 break; 878 } 879 880 // hand over result 881 strcpy(pszBuffer, szTimeStamp); 882 } 883 while (FALSE); 884 885 return rc; 886 } 887 485 #endif
Note:
See TracChangeset
for help on using the changeset viewer.