home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 10 Tools
/
10-Tools.zip
/
xwphescr.zip
/
XWPH0208.ZIP
/
src
/
helpers
/
tmsgfile.c
< prev
next >
Wrap
C/C++ Source or Header
|
2002-08-08
|
22KB
|
644 lines
/*
*@@sourcefile tmsgfile.c:
* replacement code for DosGetMessage for decent NLS support.
*
* This code has the following advantages over DosGetMessage:
*
* 1) No external utility is necessary to change message
* files. Simply edit the text, and on the next call,
* the file is recompiled at run-time.
*
* 2) To identify messages, any string can be used, not
* only numerical IDs.
*
* The .TMF file must have the following format:
*
* 1) Any message must start on the beginning of a line
* and look like this:
*
+ <--MSGID-->: message text
*
* "MSGID" can be any string and will serve as the
* message identifier for tmfGetMessage.
* Leading spaces after "-->:" will be ignored.
*
* 2) The message text may span across several lines.
* If so, the line breaks are returned as plain
* newline (\n) characters by tmfGetMessage.
*
* 3) Comments in the file are supported if they start
* with a semicolon (";") at the beginning of a line.
* Any following text is ignored until the next
* message ID.
*
* This file was originally added V0.9.0. The original
* code was contributed by Christian Langanke, but this
* has been completely rewritten with V0.9.16 to use my
* fast string functions now. Also, tmfGetMessage now
* requires tmfOpenMessageFile to be called beforehand
* and keeps all messages in memory for speed.
*
* Usage: All OS/2 programs.
*
* Function prefixes:
* -- tmf* text message file functions
*
* Note: Version numbering in this file relates to XWorkplace version
* numbering.
*
*@@header "helpers\tmsgfile.h"
*@@added V0.9.0 [umoeller]
*/
/*
* Copyright (C) 2001-2002 Ulrich Möller.
* This file is part of the "XWorkplace helpers" source package.
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, in version 2 as it comes in the
* "COPYING" file of the XWorkplace main distribution.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define OS2EMX_PLAIN_CHAR
// this is needed for "os2emx.h"; if this is defined,
// emx will define PSZ as _signed_ char, otherwise
// as unsigned char
#define INCL_DOSFILEMGR
#define INCL_DOSMISC
#define INCL_DOSERRORS
#include <os2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "setup.h" // code generation and debugging options
#include "helpers\datetime.h"
#include "helpers\dosh.h"
#include "helpers\eah.h"
#include "helpers\standards.h"
#include "helpers\stringh.h"
#include "helpers\tree.h"
#include "helpers\xstring.h"
#include "helpers\tmsgfile.h"
/*
*@@category: Helpers\Control program helpers\Text message files (TMF)
* see tmsgfile.c.
*/
/* ******************************************************************
*
* Declarations
*
********************************************************************/
/*
*@@ MSGENTRY:
* private representation of a message in a text
* message file. Each tree entry in
* TMFMSGFILE.IDsTreeRoot points to one of these
* structures.
*
*@@added V0.9.16 (2001-10-08) [umoeller]
*/
typedef struct _MSGENTRY
{
TREE Tree; // ulKey points to strID.psz
XSTRING strID; // message ID
ULONG ulOfsText; // offset of start of message text in file
ULONG cbText; // length of text in msg file
} MSGENTRY, *PMSGENTRY;
// globals
static PCSZ G_pcszStartMarker = "\n<--",
G_pcszEndMarker = "-->:";
/* ******************************************************************
*
* Functions
*
********************************************************************/
/*
*@@ LoadAndCompile:
* loads and compiles the message file into the
* given TMFMSGFILE struct.
*
* This has been extracted from tmfOpenMessageFile
* to allow for recompiling on the fly if the
* message file's last-write date/time changed.
*
* Preconditions:
*
* -- pFile->pszFilename must have been set.
*
* All other fields get initialized here.
*
*@@added V0.9.18 (2002-03-24) [umoeller]
*@@changed V0.9.20 (2002-07-19) [umoeller]: optimized, no longer holding all msgs im mem
*/
APIRET LoadAndCompile(PTMFMSGFILE pTmf, // in: TMF struct to set up
PXFILE pFile) // in: opened XFILE for msg file
{
APIRET arc;
PSZ pszContent = NULL;
ULONG cbRead;
if (!(arc = doshReadText(pFile,
&pszContent,
&cbRead))) // bytes read including null char
{
// file loaded:
PCSZ pStartOfFile,
pStartOfMarker;
ULONG ulStartMarkerLength = strlen(G_pcszStartMarker),
ulEndMarkerLength = strlen(G_pcszEndMarker);
XSTRING strContents;
// initialize TMFMSGFILE struct
treeInit(&pTmf->IDsTreeRoot, NULL);
xstrInit(&strContents,
cbRead); // including null byte
xstrcpy(&strContents,
pszContent,
cbRead - 1); // not including null byte
// convert to plain C format
/* xstrConvertLineFormat(&pTmf->strContent,
CRLF2LF);
*/
// kick out all the comments
/*
while (pStartOfMarker = strstr(pTmf->strContent.psz, "\n;"))
{
// copy the next line over this
PCSZ pEOL = strhFindEOL(pStartOfMarker + 2, NULL);
xstrrpl(&pTmf->strContent,
// ofs of first char to replace: "\n;"
pStartOfMarker - pTmf->strContent.psz,
// no. of chars to replace:
pEOL - pStartOfMarker,
// string to replace chars with:
NULL,
// length of replacement string:
0);
}
*/
// free excessive memory
// xstrShrink(&pTmf->strContent);
pStartOfFile = strContents.psz;
// go build a tree of all message IDs...
// find first start message marker
pStartOfMarker = strstr(pStartOfFile,
G_pcszStartMarker); // start-of-line marker
while ( (pStartOfMarker)
&& (!arc)
)
{
// start marker found:
PCSZ pStartOfMsgID = pStartOfMarker + ulStartMarkerLength;
// search next start marker
PCSZ pStartOfNextMarker = strstr(pStartOfMsgID + 1,
G_pcszStartMarker); // "\n<--"
// and the end-marker
PCSZ pEndOfMarker = strstr(pStartOfMsgID + 1,
G_pcszEndMarker); // "-->:"
PMSGENTRY pNew;
// sanity checks...
if ( (pStartOfNextMarker)
&& (pStartOfNextMarker < pEndOfMarker)
)
{
// next start marker before end marker:
// that doesn't look correct, skip this entry
pStartOfMarker = pStartOfNextMarker;
continue;
}
if (!pEndOfMarker)
// no end marker found:
// that's invalid too, and there can't be any
// message left in the file then...
break;
// alright, this ID looks correct now
if (!(pNew = NEW(MSGENTRY)))
arc = ERROR_NOT_ENOUGH_MEMORY;
else
{
// length of the ID
ULONG ulIDLength = pEndOfMarker - pStartOfMsgID;
PCSZ pStartOfText = pEndOfMarker + ulEndMarkerLength,
pNextComment;
ZERO(pNew);
// copy the string ID (between start and end markers)
xstrInit(&pNew->strID, 0);
xstrcpy(&pNew->strID,
pStartOfMsgID,
ulIDLength);
// make ulKey point to the string ID for tree sorting
pNew->Tree.ulKey = (ULONG)pNew->strID.psz;
// skip leading spaces
while (*pStartOfText == ' ')
pStartOfText++;
// store offset of start of text
pNew->ulOfsText = pStartOfText - pStartOfFile;
// check if there's a comment before the
// next item
if (pNextComment = strstr(pStartOfText, "\n;"))
{
if ( (!pStartOfNextMarker)
|| (pNextComment < pStartOfNextMarker)
)
pNew->cbText = // offset of comment marker
(pNextComment - pStartOfFile)
- pNew->ulOfsText;
else
// we have a next marker AND
// the comment comes after it
pNew->cbText = // offset of next marker
(pStartOfNextMarker - pStartOfFile)
- pNew->ulOfsText;
}
else
if (pStartOfNextMarker)
// other markers left:
pNew->cbText = // offset of next marker
(pStartOfNextMarker - pStartOfFile)
- pNew->ulOfsText;
else
// this was the last message:
pNew->cbText = strlen(pStartOfText);
// remove trailing newlines
while ( (pNew->cbText)
&& ( (pStartOfText[pNew->cbText - 1] == '\n')
|| (pStartOfText[pNew->cbText - 1] == '\r')
)
)
(pNew->cbText)--;
// store this thing
if (!treeInsert(&pTmf->IDsTreeRoot,
NULL,
(TREE*)pNew,
treeCompareStrings))
// successfully inserted:
(pTmf->cIDs)++;
}
// go on with next start marker (can be NULL)
pStartOfMarker = pStartOfNextMarker;
} // end while ( (pStartOfMarker) ...
free(pszContent);
} // end else if (!(pTmf = NEW(TMFMSGFILE)))
return arc;
}
/*
*@@ tmfOpenMessageFile:
* opens a .TMF message file for future use
* with tmfGetMessage.
*
* Use tmfCloseMessageFile to close the file
* again and free all resources. This thing
* can allocate quite a bit of memory.
*
* Returns:
*
* -- NO_ERROR: *ppMsgFile has received the
* new TMFMSGFILE structure.
*
* -- ERROR_NOT_ENOUGH_MEMORY
*
* plus any of the errors of doshLoadTextFile,
* such as ERROR_FILE_NOT_FOUND.
*
*@@added V0.9.16 (2001-10-08) [umoeller]
*@@changed V0.9.20 (2002-07-19) [umoeller]: optimized, no longer holding all msgs im mem
*/
APIRET tmfOpenMessageFile(const char *pcszMessageFile, // in: fully q'fied .TMF file name
PTMFMSGFILE *ppMsgFile) // out: TMFMSGFILE struct
{
APIRET arc;
ULONG cbFile;
PXFILE pFile;
if (!(arc = doshOpen(pcszMessageFile,
XOPEN_READ_EXISTING,
&cbFile,
&pFile)))
{
// create a TMFMSGFILE entry
PTMFMSGFILE pTmf;
if (!(pTmf = NEW(TMFMSGFILE)))
arc = ERROR_NOT_ENOUGH_MEMORY;
else
{
ZERO(pTmf);
pTmf->pszFilename = strdup(pcszMessageFile);
// TMFMSGFILE created:
if (!(arc = LoadAndCompile(pTmf, pFile)))
{
// set timestamp to that of the file
FILESTATUS3 fs3;
if (!(arc = DosQueryFileInfo(pFile->hf,
FIL_STANDARD,
&fs3,
sizeof(fs3))))
{
dtCreateFileTimeStamp(pTmf->szTimestamp,
&fs3.fdateLastWrite,
&fs3.ftimeLastWrite);
// output
*ppMsgFile = pTmf;
}
}
if (arc)
// error:
tmfCloseMessageFile(&pTmf);
}
doshClose(&pFile);
}
return arc;
}
/*
*@@ FreeInternalMem:
* cleans out the internal message file compilation
* and the content string. Used by both tmfCloseMessageFile
* and tmfGetMessage to allow for recompiles when the
* last-write date/time changed.
*
*@@added V0.9.18 (2002-03-24) [umoeller]
*/
static VOID FreeInternalMem(PTMFMSGFILE pTmf)
{
LONG cItems;
TREE** papNodes;
if (cItems = pTmf->cIDs)
{
if (papNodes = treeBuildArray(pTmf->IDsTreeRoot,
&cItems))
{
ULONG ul;
for (ul = 0; ul < cItems; ul++)
{
PMSGENTRY pNodeThis = (PMSGENTRY)(papNodes[ul]);
xstrClear(&pNodeThis->strID);
free(pNodeThis);
}
free(papNodes);
}
}
}
/*
*@@ tmfCloseMessageFile:
* closes a message file opened by
* tmfOpenMessageFile, frees all resources,
* and sets *ppMsgFile to NULL for safety.
*
*@@added V0.9.16 (2001-10-08) [umoeller]
*/
APIRET tmfCloseMessageFile(PTMFMSGFILE *ppMsgFile)
{
if (ppMsgFile && *ppMsgFile)
{
PTMFMSGFILE pTmf = *ppMsgFile;
if (pTmf->pszFilename)
free(pTmf->pszFilename);
FreeInternalMem(pTmf);
free(pTmf);
*ppMsgFile = NULL;
return NO_ERROR;
}
return (ERROR_INVALID_PARAMETER);
}
/*
*@@ tmfGetMessage:
* replacement for DosGetMessage.
*
* After you have opened a .TMF file with tmfOpenMessageFile,
* you can pass it to this function to retrieve a message
* with the given string (!) ID. See tmsgfile.c for details.
*
* Note that this will invoke xstrcpy on the given XSTRING
* buffer. In other words, the string must be initialized
* (see xstrInit), but will be replaced.
*
* This does perform the same brain-dead string replacements
* as DosGetMessage, that is, the string "%1" will be
* replaced with pTable[0], "%2" will be replaced with
* pTable[1], and so on.
*
* Returns:
*
* -- NO_ERROR;
*
* -- ERROR_INVALID_PARAMETER
*
* -- ERROR_MR_MID_NOT_FOUND
*
*@@added V0.9.16 (2001-10-08) [umoeller]
*@@changed V0.9.18 (2002-03-24) [umoeller]: now recompiling if last write date changed
*@@changed V0.9.20 (2002-07-19) [umoeller]: optimized, no longer holding all msgs im mem
*/
APIRET tmfGetMessage(PTMFMSGFILE pMsgFile, // in: msg file opened by tmfOpenMessageFile
PCSZ pcszMessageName, // in: msg name to look for (case-sensitive!)
PXSTRING pstr, // out: message string, if found (XSTRING must be initialized)
PCSZ *pTable, // in: replacement table or NULL
ULONG cTableEntries) // in: count of items in pTable or null
{
APIRET arc = NO_ERROR;
if ( (!pMsgFile)
|| (!pMsgFile->pszFilename)
)
arc = ERROR_INVALID_PARAMETER;
else
{
// open the file again V0.9.20 (2002-07-19) [umoeller]
ULONG cbFile;
PXFILE pFile;
if (!(arc = doshOpen(pMsgFile->pszFilename,
XOPEN_READ_EXISTING,
&cbFile,
&pFile)))
{
// check if last-write date/time changed compared
// to the last time we opened the thing...
// V0.9.18 (2002-03-24) [umoeller]
FILESTATUS3 fs3;
if (!(arc = DosQueryFileInfo(pFile->hf,
FIL_STANDARD,
&fs3,
sizeof(fs3))))
{
CHAR szTemp[30];
dtCreateFileTimeStamp(szTemp,
&fs3.fdateLastWrite,
&fs3.ftimeLastWrite);
if (strcmp(szTemp, pMsgFile->szTimestamp))
{
// last write date changed:
_Pmpf((__FUNCTION__ ": timestamp changed, recompiling"));
FreeInternalMem(pMsgFile);
if (!(arc = LoadAndCompile(pMsgFile, pFile)))
strcpy(pMsgFile->szTimestamp, szTemp);
}
}
if (!arc)
{
// go find the message in the tree
PMSGENTRY pEntry;
if (!(pEntry = (PMSGENTRY)treeFind(pMsgFile->IDsTreeRoot,
(ULONG)pcszMessageName,
treeCompareStrings)))
arc = ERROR_MR_MID_NOT_FOUND;
else
{
PSZ pszMsg;
ULONG cbRead = pEntry->cbText;
if (!(pszMsg = (PSZ)malloc(cbRead + 1)))
arc = ERROR_NOT_ENOUGH_MEMORY;
else if (!(arc = doshReadAt(pFile,
pEntry->ulOfsText,
&cbRead,
pszMsg,
DRFL_NOCACHE | DRFL_FAILIFLESS)))
{
// null-terminate
pszMsg[cbRead] = '\0';
xstrset2(pstr,
pszMsg,
cbRead);
// kick out \r\n
xstrConvertLineFormat(pstr,
CRLF2LF);
// now replace strings from the table
if (cTableEntries && pTable)
{
CHAR szFind[10] = "%0";
ULONG ul;
for (ul = 0;
ul < cTableEntries;
ul++)
{
ULONG ulOfs = 0;
_ultoa(ul + 1, szFind + 1, 10);
while (xstrFindReplaceC(pstr,
&ulOfs,
szFind,
pTable[ul]))
;
}
}
}
}
}
doshClose(&pFile);
}
}
return arc;
}
/* test case */
#ifdef __TMFDEBUG__
int main(int argc, char *argv[])
{
APIRET arc;
PTMFMSGFILE pMsgFile;
if (argc < 3)
printf("tmsgfile <file> <msgid>\n");
else
{
if (!(arc = tmfOpenMessageFile(argv[1], &pMsgFile)))
{
XSTRING str;
xstrInit(&str, 0);
if (!(arc = tmfGetMessage(pMsgFile,
argv[2],
&str,
NULL,
0)))
{
printf("String:\n%s", str.psz);
}
else
printf("tmfGetMessage returned %d\n", arc);
xstrClear(&str);
tmfCloseMessageFile(&pMsgFile);
}
else
printf("tmfOpenMessageFile returned %d\n", arc);
}
}
#endif