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 >
C/C++ Source or Header  |  2002-08-08  |  22KB  |  644 lines

  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.  
  115. typedef 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
  124. static 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.  
  152. APIRET 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.  
  354. APIRET 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;
  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.  
  415. static 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.  
  449. APIRET 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.  
  499. APIRET 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;
  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.  
  610. int 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
  644.