unit wMessages2;

{ I wrote this unit in 1994, with some adds in 1995.
  All comments where in french. I let them, and changed only the
  comments at the beginning, before the IMPLEMENTATION part.

  To translate the beginning ...

  utilisation de lignes de textes indexé par un numero en début de ligne,
  avec la création d'un fichier d'index 'filename.ind', si il n'a pas
  déjà été créé, ou si sa date de création est antérieur à celle du
  fichier texte.
  Le fichier texte est écrit sous la forme :
  ; commentaire
  numéro texte   ; commentaires
  numéro texte   ; commentaires
  ...
  le numéro est compris entre 1 et 4294967290.
  le numéro peut être égale à 0 mais il ne peut être accédé
  que par GetMsg(4294967295), qui lit le message suivant le dernier lu.
  Suppose les numéro classés par ordre croissant, mais pas forcément consécutifs.
  Après le numéro un caractère est sauté, puis le texte est pris, même
    si il commence par des blancs.
  Pour entrer un dièse #, il faut le doubler. exemple : ##
  Si le dernier caractère de la ligne est un '\' il est enlevé, donc
    on peut avoir ainsi des blancs en fin de texte.
  Pour avoir un '\' en fin de ligne, il faut le doubler. exemple : \\
  Maximum de 9000 messages indexés.
  Il faut fermer un fichier pour en ouvrir un autre, si plusieurs fichier
  textes sont à traiter.
  Si dans GetMsg, un numéro de message n'est pas trouvé, retourne '????' + numéro
  Si erreur, car fichier non ouvert, retourn '???!'

  Adjonction du 29 avril 1994
  modifié    le 30 juin  1994
  modifié    le 21 août  1994
  modifié    le 20 avril 1995
  """"""""""""""""""""""""""""
  On peut ouvrir plusieurs fichiers de messages en même temps, la recherche
  de message se fait dans l'ordre inverse des ouvertures.
  ---------------------------------------------------------------- }

// To tell FreePascal that the application is a Graphical User Interface
// i.e. is running in windows, not in text mode.
{$APPTYPE GUI}

interface
{"""""""}

type
  word32 = Cardinal;

const
  msgNext           = word32(-1); // = 4294967295 = 4'294'967'295
  msgNextNum        = word32(-2); // = 4294967294 = 4'294'967'294

  gglstrMsgPathName : ShortString = '';
  // Indicate the path of the application's .msg filename

  gglstrMsgDirectory : ShortString = '';
  // If a message file could not be opened, try to open it in this directory

  ctestrLineEND = '...END';
  // This string will be used very often to indicate the end of a series
  // of description lines.

{ indique quelle numéro de message à provoqué l'erreur }
var
  gglwMessages2ErrorPosition        : word32;

  { dernier numéro accédé, surtout utile pour savoir si un GetMsg(msgNext)
    arrive sur un numéro autre que 0 }
  gglwMessages2LastNum              : word32;

  gglfMessages2LastFound : boolean;
  // Is TRUE is last  GetMsg(...  found a string,
  // FALSE other wise.

function OpenMsgPlaced( FilenameIn:ShortString; wPos:Word32;
{=====================} wFlags:word32; var wError:Word32):pointer;
{ Ouvre un fichier de messages, placé en position nPos parmis les messages déjà ouverts.
  FilenameIn = nom du fichier texte.
  nPos <= 0 => placé en fin, cas normal.
  nPos  = 1 => placé juste avant le dernier
  nPos  = 2 => placé avant avant dernier
  nPos grand => placé en début de liste.
  La recherche du message dans les fichiers se fera par ordre inverse
  de la liste, i.e. du dernier au début.

  wFlags = 0 => Ouverture en lecture uniquement. (Par défaut c'est plus sur)
  wFlags = 1 => Ouverture en lecture / écriture.

  OUTPUT :
  wError = 0 si pas d'erreurs
         = 1 si erreur d'ouverture en lecture
         = 2 si erreur de creation du fichier d'index
         = 3 si erreur d'ouverture en lecture dans OpenMsg
         =10 si erreur, les numéros d'index ne sont pas par ordre croissant
             Dans ce cas, gglwMessages2ErrorPosition = numéro du message
             qui n'est pas dans l'ordre croissant.

  OUTPUT :
  Un pointeur qui devra être retourné à  CloseMsgPlace pour la fermeture du fichier.
  =NIL si il y a eu une erreur.

  Exemple de placement en fin.
  avant
  NIL <- pLastMsg
  après         pLastMsg=pMsg
  NIL <- (pPrevius, pNext) -> NIL
  après                               pLastMsg=pMsg
  NIL <- (pPrevius, pNext) <-> (pPrevius, pNext) -> NIL
-------------------------------------------------------------------------}

procedure OpenMsg(FilenameIn:ShortString; wFlags:word32; var wError:Word32);
//===========================================================================
{ ouvre un fichier text dont les lignes sont indexées, pour les obtenir
  en fonction de leur index, à l'aide de GetMsg.
  INPUT :
  FilenameIn = nom du fichier texte.

  wFlags = 0 => Ouverture en lecture uniquement. (Par défaut c'est plus sur)
  wFlags = 1 => Ouverture en lecture / écriture.

  OUTPUT :
  wError = 0 si pas d'erreurs
         = 1 si erreur d'ouverture en lecture
         = 2 si erreur de creation du fichier d'index
         = 3 si erreur d'ouverture en lecture dans OpenMsg
         =10 si erreur, les numéros d'index ne sont pas par ordre croissant
             Dans ce cas, gglwMessages2ErrorPosition = numéro du message
             qui n'est pas dans l'ordre croissant.
-------------------------------------------------------------------------}

procedure CloseMsgPlaced(pMsgClose:pointer; var wError:Word32);
//==============================================================
{ Ferme le message pointé par pMsgClose, et libère la mémoire allouée,
  qui est de 4 bytes par lignes indexée.

  Si pMsgClose = NIL, ne fait rien

  OUTPUT :
  wError = 0  => OK
  wError = 1  => pas de fichier de messages a fermer.
  wError = 2  => pMsgClose ne pointe pas sur un fichier de message ouvert.

  avant          pMsg                 pLastMsg
  NIL <- (pPrevius, pNext) <-> (pPrevius, pNext) -> NIL
  après         pLastMsg
  NIL <- (pPrevius, pNext) -> NIL
  après
  NIL <- pLastMsg
-------------------------------------------------------------------------}

procedure CloseMsg;
{=================}
{ ferme le dernier fichier ouvert par OpenMsg, et libère la mémoire allouée,
  qui est de 4 bytes par lignes indexée
-------------------------------------------------------------------------}

procedure CloseAllMsg;
{====================}
{ ferme tous les fichiers ouvert par OpenMsg, et libère la mémoire allouée,
  qui est de 4 bytes par lignes indexée
-------------------------------------------------------------------------}

procedure GetMsgPos(var ptr:pointer; var wPos:word32);
//====================================================
{ Retourne un pointeur sur le fichier utilisé actuellement + la position dans ce fichier.
  peut être utile, pour se repositionner avec SetMsgPos.
-------------------------------------------------------------------------}

procedure SetMsgPos(ptr:pointer; wPos:word32; var wError:Word32);
//================================================================
{ permet de se repositionner sur dans le fichier dont les coordonnées ont été lues avec
  GetMsgPos.
  Il est prévu de faire ensuite un GetMsg(msgNext);
  wError = 1 si le pointeur ptr n'es pas valide, = 0 si OK.
-------------------------------------------------------------------------}

function GetMsgNextNum:ShortString;
//=================================
// Return the message corresponding to the first number following the last used

function GetMsg(wNumRef:word32):ShortString;
//==========================================
{ Retourne le texte indexé par wNumRef dans un des fichiers ouvert par OpenMsg
  La recherche se fait dans l'ordre inverse d'ouverture.
  si pas de messages trouve, retourne : '????' + wNumRef
  si fichier non ouvert, retourne : '???!'
  si wNumRef = msgNext alors retourne le message suivant.
  si wNumRef = msgNextNum alors retourne le prochain message ayant un numéro
               différent de zéro.
-------------------------------------------------------------------------}

function ExistMsgNumber(wNumRef:word32):boolean;
//==============================================
// Return TRUE if the message number exist in the  .msg  file
// And GetMsg(msgNext) will return the next line.

function GetMsgSub(wNumRef, wPlace:word32):ShortString;
//=====================================================
{ Retourne le message qui se trouve en  wPlace  position après la ligne
  indexée par wNumRef.
  Si wPlace = 0, alors GetMsgSub = GetMsg.
  Si wPlace = 1, alors GetMsgSub = la ligne suivante.

  C'est utile pour les messages ayant plusieurs lignes indexées par  0
-------------------------------------------------------------------------}

function IsInLastOpened:boolean;
//==============================
{ retourne TRUE, si le dernier message lu était dans le dernier fichier
  de message ouvert }

procedure OpenAllMessagesFiles(strPathName:ShortString; var wError:Word32);
//==========================================================================
{ Ouvre le fichier pour la lecture des messages indexé --- }
{ Essaye dans le directory courrant, puis dans le directory ou se
  trouve le .EXE, puis dans le parent de .EXE, puis dans
  le répertoire glstrMsgSearchPath.
  FilenameIn = nom du fichier de messages, avec extension (.msg)
  et généralement sans répertoire de recherche, qui peut être défini dans
  glstrMsgSearchPath.
  Les messages numéro  1 à 10 donne une liste de fichiers à ouvrir
  Les messages numéro 11 à 20 donne une autre liste de fichiers à ouvrir.

  La recherche d'un message se ferra dans l'ordre suivant :
  1) Dans l'ordre inverse des fichiers donné aux messages numéro 11 à 20
  2) Le fichier des messages de l'application  (=FilenameIn)
  3) Dans l'ordre inverse des fichiers donné aux messages numéro  1 à 10.

  exemple de données dans le fichier de messages FilenameIn

  1 errFr.msg  ; les messages erreurs
  2 stdFr.msg  ; les messages standards
  3 ...END

  11 clientfr.msg ; les messages clients
  12 autre.msg
  13 ...END

  La recherche se fera dans l'ordre :
  1) autre.msg
  2) clientfr.msg
  3) Le fichier .msg du prog.
  4) stdFr.msg
  5) errFr.msg

  Si il n'y a pas de numéro commun à deux fichiers (ex. stdFr.msg et
  errFr.msg), leur ordre de placement n'est pas important
-------------------------------------------------------------------------}

//******************* Not well tested. ***************************

procedure OpenMsgRW(FilenameIn:ShortString; var wError:Word32);
{============================================================}
{ Ouverture en lecture / écriture.
  avant
  NIL <- pLastMsg
  après         pLastMsg=pMsg
  NIL <- (pPrevius, pNext) -> NIL
  après                               pLastMsg=pMsg
  NIL <- (pPrevius, pNext) <-> (pPrevius, pNext) -> NIL
-------------------------------------------------------------------------}

procedure AddLineMsg(wNumRef:word32; strNew:ShortString; var wError:Word32);
{===========================================================================}
{ Test si possible, et si oui,
  remplace le  ...END qui termine le message wNumRef, par le
  string strNew, et rajoute un  ...END  à la suite.
  Ceci sans modifier la taille du fichier.
  Pour que ce soit possible, il faut que après le  ...END  il y ait
  des lignes quelconques, suivit d'un autre  ...END.
  c.f. tMessage.pas  et  tMessage.msg.

  OUTPUT :
  wError =
   0 si OK,
   1 si Message pas trouvé.
   2 si ...END  final pas trouvé

  10 si pas de place pour rajouter le string  strNew
  20 si il y a eu une erreur IOResult.
-------------------------------------------------------------------------}

procedure ReplaceLineMsg(wNumRef,wPlace:word32; strNew:ShortString; var wError:Word32);
{======================================================================================}
{ Se positionne à la ligne référencée par wNumRef,
  Si wPlace = 1, alors reste à la ligne référencée par wNumRef
  Si wPlace = 1, alors ligne qui suit la ligne référencée par wNumRef.

  Remplace par strNew la ligne atteinte, suivit de
    ' ;-----------------' avec une longueur exacte pour conserver
    le nombre de caractères de la ligne.
  Au retour, se positionne sur la ligne qui suit.
  En cas d'erreur, ne fait rien.

  OUTPUT :
  wError =
   0 si OK,
   1 si Message pas trouvé.
   2 si si la fin du fichier atteind

  10 si length(strNew) > longueur de la ligne à remplacer
  20 si il y a eu une erreur IOResult.
-------------------------------------------------------------------------}

procedure AppendLine(wMsgRef, wMsgNum:Word32;  strFilePath, strS:ShortString; var wError:Word32);
//================================================================================================
// Append a line to the file  strFilePath, (usually a .msg file)
// The appened line begins with the number wMsgNum
// followed by one space, followed by the strS string.
// if the file is opened as a  .msg  file,
// If (wMsgRef <> 0)  then it is  ASSUMED  that
// the file  strFilePath  has been opened as a  .msg  file, AND
// That the message number  wMsgRef  exist in this file.
// Thus the file will be closed, to be reopened in read/write mode,
// the string will be append,
// and it will be reopened in read only mode.

implementation
{""""""""""""}

uses wErrors0, wDebug0, DOS; // for the GetFTime, ...

{$GOTO ON}  // Accept goto in the code.

Type PathStr = ShortString;
     Dirstr  = ShortString;
     Namestr = string[80];
     Extstr  = string[40];

const MaxMsg      = 9000;
      cteReturn   =   13;
      cteComment  = ord('#');

      cteErrorOpen   =  1;
      cteErrorCreate =  2;
//      cteErrorOpen2  =  3;
      cteErrorOrder  = 10;

      { const. pour la lecture de caract. dans le fichier des messages }
const BufSizeMax  =  500;

      { variables globales pour la lectures de caracteres dans les messages }
var   bCar        : byte;
      glwPos      : word32; { position actuelle dans le fichier des messages }
      abBuffer    : array[1..BufSizeMax] of byte;
      nPosBuf     : integer;
      nBufSize    : integer; { buffer pour lire le fichier des messages }

type
  ArrayOfword32    = array[1..MaxMsg] of word32;
  PTypeRecMsg      = ^TypeRecMsg;
  TypeRecMsg       = record
     pPrevius,
     pNext         : PTypeRecMsg;
     fileIn        : file; { mais c'est un fichier texte }
     pawNumMsg     : ^ArrayOfword32; { numéros de références }
     pawPosMsg     : ^ArrayOfword32; { Position associées }
     wNbMsg        : word32;  { nombre de lignes indexées }
     wInd          : word32;
     wLastPos      : word32; { position du début du dernier msg lu }
     end; { record }

{ Les informations sur les fichiers de messages sont chainés dans une liste }
const
  pLastMsg         : PTypeRecMsg = NIL;
{ fichier de message actuellement actif }
  pDataMsg         : PTypeRecMsg = NIL;

procedure ReadNextCar;
{====================}
{ utilise les variables globales : nPosBuf, bCar, abBuffer, nBufSize, fileIn,
  et la constante globale BufSizeMax.
  OUTPUT :
  bCar      = prochain caractere de fileIn
  nPosBuf   = position dans le buffer de fileIn
  glwPos    = position dans fileIn
  nBufSize  = 0 si il n'y a plus de car. a lire
-------------------------------------------------------------------------}
begin { - ReadNextCar - }
with pDataMsg^ do begin
inc(glwPos);
nPosBuf:=nPosBuf+1;
bCar:=abBuffer[nPosBuf];
if nPosBuf >= nBufSize then begin
   BlockRead(fileIn, abBuffer, BufSizeMax, nBufSize);
   nPosBuf:=0;
   end;
end; { with pDataMsg^ }
end;  { - ReadNextCar - }

function GetIndexNumber:word32;
//=============================
{ retourne le numéro d'index du messages qui vient.
  On suppose être au début de ce numéro.
  Lit le caractère qui suit le numéro.
  retourne msgNext si on a atteind la fin de fichier.
-------------------------------------------------------------------------}
var   wNum  : word32;
begin { - GetIndexNumber - }
wNum:=0;
repeat
   if (nBufSize = 0) then begin { - fin de fichier - }
      GetIndexNumber:=msgNext;
      exit;
      end;

   // Overflow can occure is the parenthesis "Word32(...) is omitted.
   wNum:=wNum * 10 + word32(bCar - ord('0'));
   ReadNextCar;
   until (bCar < ord('0')) or (bCar > ord('9'));

{ - début du texte qui suit avec blanc au début si désiré - }
if (nBufSize = 0) then wNum:=msgNext
else                   ReadNextCar;

GetIndexNumber:=wNum;
end;  { - GetIndexNumber - }

procedure IndexCreate(var FilenameIn, FilenameOut:ShortString; var wError:Word32);
//=================================================================================
{ création de l'index du fichier Filename.
  OUTPUT :
  wError = 0 si pas d'erreurs
         = 1 si erreur d'ouverture en lecture
         = 2 si erreur de creation du fichier d'index
         = 3 si erreur d'ouverture en lecture (ne devrait jamais arriver)
         =10 si erreur, les numéros d'index ne sont pas par ordre croissant
  fichier .ind d'index de Filename
  ------------------------------------------------------------}
label SaveIndex;
label EndIndexCreate;
var   fileOut     : text;  { fichier d'index }
//      pawNumMsg   : ^ArrayOfword32;  { numéro de références }
//      palPosMsg   : ^ArrayOfLong;  { position du début de texte }
      wNum        : word32;  { numéro de référence }
      wNumLast    : word32;  { dernier num de référence }
//      wNbMsg      : word32;  { nombre de lignes indexées }
      wII         : word32;
      lTimeIn     : longint;
      lTimeOut    : longint;
      lLastSize   : longint;
      lFileSize   : longint;

begin { - IndexCreate - }
with pDataMsg^ do begin
wNumLast:=0;
wError:=0;
wNbMsg:=0;

assign(fileIn, FilenameIn);
{$i-}
FileMode:=0; { read only }
reset(fileIn, 1);
FileMode:=2; { read / write }
lFileSize:=FileSize(FileIn);
{$i+}
if ioresult <> 0 then begin { - erreur de lecture - }
   wError :=cteErrorOpen;
   exit;
   end;

{ - test si le fichier d'index est plus récent que le fichier à indexer - }
assign(fileOut, FilenameOut);
{$i-}
FileMode:=0; { read only }
reset(fileOut);
FileMode:=2; { read / write }
{$i+}
if ioresult = 0 then begin
   GetFTime(FileIn, lTimeIn);
   GetFTime(FileOut, lTimeOut);

   {$i-}
   readln(FileOut, lLastSize);
   {$i+}
   if (IOResult <> 0) then lLastSize:=0;

   close(fileOut);

   if (lTimeOut > lTimeIn) and (lFileSize = lLastSize) then begin
      { - fichier d'index est plus récent que le fichier texte - }
      { - et taille du fichier texte n'a pas changé - }
      close(fileIn);
      exit;
      end;
   end;
{ else, file doesn't exist, create it }

{ --- creation de l'index --- }
{ *************************** }
glwPos:=0;
nPosBuf:=0;
BlockRead(fileIn, abBuffer, BufSizeMax, nBufSize);
new(pawNumMsg);
new(pawPosMsg);

repeat
   { --- saute les blancs de début de ligne, ainsi que les tabulateurs,
         line feed, return --- }
   repeat
      if (nBufSize = 0) then goto SaveIndex;
      ReadNextCar; // inc(glwPos);  is done here
      until (bCar > ord(' '));

   { --- si numéro d'index de ligne trouvé, mémorise le, ainsi que la
         position de la ligne --- }
   if (bCar >= ord('0')) and (bCar <= ord('9')) then begin
      wNum:=GetIndexNumber;
      if wNum = msgNext then goto SaveIndex;

      if (wNum > 0) then begin
         { - sauve l'index et la position - }
         wNbMsg:=wNbMsg + 1;
         pawNumMsg^[wNbMsg]:=wNum;
         pawPosMsg^[wNbMsg]:=glwPos-1;

         if wNum <= wNumLast then begin { - index pas par ordre croissant - }
            wError:=cteErrorOrder;
            gglwMessages2ErrorPosition:=wNum;
            close(fileIn);
            goto EndIndexCreate;
            end;

         wNumLast:=wNum;
         end; { - if wNum > 0 then begin - }
      end; { - if (bCar >= ord('0')) and ... - }

   { --- recherche du début de la ligne suivante --- }
   repeat
      if (nBufSize = 0) then goto SaveIndex;
      ReadNextCar;
      until (bCar = cteReturn);

   until FALSE;

{ --- on a terminé de créer l'indexe, sauve le dans un fichier --- }
{ ---------------------------------------------------------------- }
SaveIndex:;
close(fileIn);

assign(fileOut, FilenameOut);
{$i-}
rewrite(fileOut);
{$i+}
if ioresult <> 0 then begin { - erreur de création - }
   wError:=cteErrorCreate;
   end
else begin
   writeln(FileOut, lFileSize:9); { taille du fichier source }
   writeln(fileOut, wNbMsg:9);    { nombre d'entrée de messages }
   for wII:=1 to wNbMsg do begin
      writeln(fileOut, pawNumMsg^[wII]:5, ' ', pawPosMsg^[wII]:9);
      end;

   close(fileOut);
   end;

EndIndexCreate:;
dispose(pawPosMsg);
dispose(pawNumMsg);

end; { with pDataMsg^ }
end; { - IndexCreate - }

procedure OpenMsgGen(FilenameIn:ShortString; wFlags:word32; var wError:Word32);
//==============================================================================
{ Ouverture du fichier de textes, avec création d'index si nécéssaire
  wFlags = 0 => Ouverture en lecture uniquement. (Par défaut c'est plus sur)
  wFlags = 1 => Ouverture en lecture / écriture.
-------------------------------------------------------------------------}
var   FilenameIndex  : PathStr;
      fileIndex      : text;
      wII            : word32;
//      nPosExt        : integer;
      strDir         : DirStr;
      strName        : NameStr;
      strExt         : ExtStr;
      lFileSize      : longint;

begin { - OpenMsgGen - }
with pDataMsg^ do begin
{ --- définit le nom du fichier d'index, et créé l'index si nécéssaire --- }
wNbMsg:=0;

FSplit(FilenameIn, strDir, strName, strExt);

FilenameIndex:=strDir + strName + '.ind';

IndexCreate(FilenameIn, FilenameIndex, wError);
if (wError <> 0) then exit; { -- erreur, c.f. IndexCreate -- }

{ --- lecture dans un tableau globale de la table d'index --- }
assign(fileIndex, FilenameIndex);
if (wFlags <> 1) then FileMode:=0 { read only }
else                  FileMode:=2; { read / write }
reset(fileIndex);  { il vient d'être créé, donc pas d'erreur }
readln(fileIndex, lFileSize); { Lecture de taille, non utilisé ici }
readln(fileIndex, wNbMsg); { - nombre de ligne du fichier - }

GetMem(pawNumMsg, wNbMsg * SizeOf(word32));
GetMem(pawPosMsg, wNbMsg * SizeOf(word32));
for wII:=1 to wNbMsg do readln(fileIndex, pawNumMsg^[wII], pawPosMsg^[wII]);

close(fileIndex);

{ --- ouverture du ficher texte contenant les messages, et laisse ouvert --- }
assign(fileIn, FilenameIn);
reset(fileIn, 1);
FileMode:=2; { read / write }

wLastPos:=pawPosMsg^[1];
end; { with pDataMsg^ }
end;  { - OpenMsgGen - }

procedure CloseMsgGen;
{====================}
{ Fermeture du fichier de textes, et libération de mémoire
-------------------------------------------------------------------------}
var
  nErr : integer;
begin { - CloseMsgGen - }
with pDataMsg^ do begin
{$i-}
close(fileIn);
{$i+}

nErr:=IOResult;
nErr := nErr; // to remove a Note

if (wNbMsg > 0) then begin
   FreeMem(pawPosMsg, wNbMsg * SizeOf(word32));
   FreeMem(pawNumMsg, wNbMsg * SizeOf(word32));
   end;
end; { with pDataMsg^ }
end;  { - CloseMsgGen - }

function GetMsgGen(wNumRef:word32):ShortString;
//=============================================
{ retourne le texte correspondant au numéro de référence associé
-------------------------------------------------------------------------}
var   strText     : ShortString;
      wIndMin     : word32;
      wIndMax     : word32;
      wNum        : word32;
      fExitLoop   : boolean;
      fBidon      : boolean; { on ne veut que tester si le fichier est
                               ouvert, en accédant à EOF(fileIn) }

begin { - GetMsgGen - }
gglfMessages2LastFound:=FALSE;  // not found by default

with pDataMsg^ do begin
{ --- test si le fichier est ouvert --- }
{$i-} fBidon:=EOF(fileIn); {$i+}
if ioresult <> 0 then begin { - le fichier n'est pas ouvert - }
   GetMsgGen:='???!';
   exit;
   end;

if (wNumRef = msgNext) then begin
   { --- recherche de la ligne suivante --- }
   seek(fileIn, wLastPos);
   glwPos:=wLastPos -1;
   nPosBuf:=0;
   BlockRead(fileIn, abBuffer, BufSizeMax, nBufSize);
   fExitLoop:=FALSE;

   { --- recherche du message suivant --- }
   repeat
      { --- recherche du début de la ligne suivante --- }
      repeat
         if (nBufSize = 0) then begin
            GetMsgGen:='????4294967295';
            exit;
            end;
         ReadNextCar;
         until (bCar = cteReturn);

      { --- saute les blancs de début de ligne, ainsi que les tabulateurs,
            line feed, return --- }
      repeat
         if (nBufSize = 0) then begin
            GetMsgGen:='????4294967295';
            exit;
            end;
         ReadNextCar;
         until (bCar > ord(' '));

      { --- si numéro d'index de ligne trouvé, glwPos=position de la ligne --- }
      if (bCar >= ord('0')) and (bCar <= ord('9')) then begin
         wNum:=GetIndexNumber;
         if wNum = msgNext then begin
            GetMsgGen:='????4294967295';
            exit;
            end;

         wLastPos:=glwPos;
         fExitLoop:=TRUE;
         end;
      until fExitLoop;
   end
else if (wNumRef = msgNextNum) then begin
   // The number has already been found in GetMsgNextNum
   // The corresponding index has been saved in  gglwMessages2LastNum
   wInd:=gglwMessages2LastNum;
   wLastPos :=pawPosMsg^[gglwMessages2LastNum];
   wNum     :=pawNumMsg^[gglwMessages2LastNum];
   end
else begin { --- (wNumRef <> msgNext) --- }
   wIndMin:=1;
   wIndMax:=wNbMsg;

   repeat
      wInd:=(wIndMin + wIndMax) div 2;
      wNum:=pawNumMsg^[wInd];

      if (wNum < wNumRef) then wIndMin:=wInd +1;
      if (wNum > wNumRef) then wIndMax:=wInd -1;
      until (wNum = wNumRef) or (wIndMin > wIndMax);

   { --- numéro trouvé ? --- }
   if (wNum <> wNumRef) then begin { - numéro non trouvé - }
      str(wNumRef, strText);
      strText:='????' + strText;
      GetMsgGen:=strText;
      exit;
      end;

   wLastPos:=pawPosMsg^[wInd];
   end;

{ --- copy le texte de fileIn, correspondant au numéro de réf. --- }
seek(fileIn, wLastPos);
nPosBuf:=0;
BlockRead(fileIn, abBuffer, BufSizeMax, nBufSize);

strText:='';
repeat
   nPosBuf:=nPosBuf +1;
   bCar:=abBuffer[nPosBuf];

   { #  is followed by a comment,
     ## is the '#' character. }
   if (bCar = cteComment) then begin
      nPosBuf:=nPosBuf +1;
      bCar:=abBuffer[nPosBuf];
      if (bCar <> cteComment) then bCar:=cteReturn;
      end;

   if (bCar >= ord(' ')) then strText:=strText + chr(bCar);
   until (bCar < ord(' ')) or (nPosBuf >= nBufSize);

{ --- élimine les blancs en fin --- }
while strText[length(strText)] = ' ' do strText[0]:=chr(ord(strText[0]) -1);
if strText[length(strText)] = '\' then  strText[0]:=chr(ord(strText[0]) -1);

gglwMessages2LastNum:=wNum;
GetMsgGen:=strText;
gglfMessages2LastFound:=TRUE;
end; { with pDataMsg^ }
end;  { - GetMsgGen - }

{************************************************************************}
{------------------------------------------------------------------------}
{************************************************************************}

function OpenMsgPlaced( FilenameIn:ShortString; wPos:Word32;
{=====================} wFlags:word32; var wError:Word32):pointer;
{ Ouvre un fichier de messages, placé en position nPos parmis les messages déjà ouverts.
  nPos <= 0 => placé en fin, cas normal.
  nPos  = 1 => placé juste avant le dernier
  nPos  = 2 => placé avant avant dernier
  nPos grand => placé en début de liste.
  La recherche du message dans les fichiers se fera par ordre inverse
  de la liste, i.e. du dernier au début.

  wFlags = 0 => Ouverture en lecture uniquement. (Par défaut c'est plus sur)
  wFlags = 1 => Ouverture en lecture / écriture.

  OUTPUT :
  Un pointeur qui devra être retourné à  CloseMsgPlace pour la fermeture du fichier.
  =NIL si il y a eu une erreur.

  Exemple de placement en fin.
  avant
  NIL <- pLastMsg
  après         pLastMsg=pMsg
  NIL <- (pPrevius, pNext) -> NIL
  après                               pLastMsg=pMsg
  NIL <- (pPrevius, pNext) <-> (pPrevius, pNext) -> NIL
-------------------------------------------------------------------------}
var
  pMsg,    { pointeur sur nouveau fichier }
  pAfter,  { pointeur sur position suivant celle d'insertion }
  pBefore  { pointeur sur position précédent celle d'insertion }
            : PTypeRecMsg;
  nn        : integer;

begin { - OpenMsgPlaced - }
{ création de données pour un nouveau fichier de message }
new(pMsg);

{ ouverture du fichier }
pDataMsg :=pMsg;
OpenMsgGen(FilenameIn, wFlags, wError);

if (wError <> 0) then begin
   dispose(pMsg);
   pDataMsg :=NIL;
   OpenMsgPlaced:=NIL;
   exit;
   end;

{ Recherche de la position d'insertion }
pAfter  :=NIL;
pBefore :=pLastMsg;
nn:=0;

while (pBefore <> NIL) and ( nn < wPos) do begin
   inc(nn);
   pAfter  :=pBefore;
   pBefore :=pBefore^.pPrevius;
   end;

pMsg^.pPrevius  :=pBefore;
pMsg^.pNext     :=pAfter;
if (pBefore <> NIL) then pBefore^.pNext   :=pMsg;
if (pAfter  <> NIL) then pAfter^.pPrevius :=pMsg;

{ - si insertion en fin, l'indique dans pLastMsg }
if (pAfter  = NIL) then pLastMsg :=pMsg;

OpenMsgPlaced :=pMsg;
end;  { - OpenMsgPlaced - }

procedure OpenMsg(FilenameIn:ShortString; wFlags:word32; var wError:Word32);
{===========================================================================}
{ avant
  NIL <- pLastMsg
  après         pLastMsg=pMsg
  NIL <- (pPrevius, pNext) -> NIL
  après                               pLastMsg=pMsg
  NIL <- (pPrevius, pNext) <-> (pPrevius, pNext) -> NIL
-------------------------------------------------------------------------}
begin { - OpenMsg - }
OpenMsgPlaced(FilenameIn, 0, wFlags, wError);
end;  { - OpenMsg - }

procedure CloseMsgPlaced(pMsgClose:pointer; var wError:Word32);
{===============================================================}
{ Ferme le message pointé par pMsgClose.
  Si pMsgClose = NIL, ferme le dernier fichier de messages.
  wError = 0  => OK
  wError = 1  => pas de fichier de messages a fermer.
  wError = 2  => pMsgClose ne pointe pas sur un fichier de message ouvert.

  avant          pMsg                 pLastMsg
  NIL <- (pPrevius, pNext) <-> (pPrevius, pNext) -> NIL
  après         pLastMsg
  NIL <- (pPrevius, pNext) -> NIL
  après
  NIL <- pLastMsg
-------------------------------------------------------------------------}
var
  pMsg,    { pointeur de recherche }
  pAfter,  { pointeur sur position suivant celle d'insertion }
  pBefore  { pointeur sur position précédent celle d'insertion }
            : PTypeRecMsg;

begin { - CloseMsgPlaced - }
wError:=1;
if (pLastMsg = NIL) then exit;  { pas de message possible à fermer }

{ par défaut, ferme le dernier message }
if (pMsgClose = NIL) then pMsgClose := pLastMsg;

{ - cherche si ptr est un pointeur valide - }
pMsg:=pLastMsg;

while (pMsg <> NIL) and (pMsg <> pMsgClose) do pMsg :=pMsg^.pPrevius;

if (pMsg = NIL) then begin
   { message n'existe pas }
   wError:=2;
   exit;
   end;

{ - ferme le fichier pointé par pMsg - }
pBefore :=pMsg^.pPrevius;
pAfter  :=pMsg^.pNext;

pDataMsg :=pMsg;
CloseMsgGen;
dispose(pMsg);

if (pBefore <> NIL) then pBefore^.pNext   :=pAfter;
if (pAfter  <> NIL) then pAfter^.pPrevius :=pBefore;
if (pAfter  =  NIL) then pLastMsg         :=pBefore;
pDataMsg:=pLastMsg;
wError:=0;
end;  { - CloseMsgPlaced - }

procedure CloseMsg;
{=================}
{ avant          pMsg                 pLastMsg
  NIL <- (pPrevius, pNext) <-> (pPrevius, pNext) -> NIL
  après         pLastMsg
  NIL <- (pPrevius, pNext) -> NIL
  après
  NIL <- pLastMsg
-------------------------------------------------------------------------}
var
  wError           : Word32;

begin { - CloseMsg - }
CloseMsgPlaced(NIL, wError);
end;  { - CloseMsg - }

procedure CloseAllMsg;
{====================}
{ ferme tous les fichiers ouvert par OpenMsg, et libère la mémoire allouée,
  qui est de 4 bytes par lignes indexée
-------------------------------------------------------------------------}
begin { - CloseAllMsg - }
while (pLastMsg <> NIL) do CloseMsg;
end;  { - CloseAllMsg - }

function GetMsgNextNum:ShortString;
//=================================
// Return the message corresponding to the first number following the last used
var
  wIndMin,
  wIndMax,
  wNum,
  wNumSearch, // Searched number
  wNumFound,  // Best number found
  wIndFound   // Corresponding index to the best number found
              : word32;
  pDataMsgFound // Message fle in which the nearest number has been found
              : pointer;
begin
if (not gglfMessages2LastFound) or (gglwMessages2LastNum = 0) then begin
   gglfMessages2LastFound:=FALSE;
   GetMsgNextNum:='???!';
   exit;
   end;

// Search among all message files, the smallest index number
// bigger or equal to :
wNumSearch:=gglwMessages2LastNum+1;

// Nothing found yet
pDataMsgFound :=NIL;
wIndFound     :=1;
wNumFound:=word32(-1); // the biggest number

// In all message files, look for the smallest number
// which is bigger than gglwMessages2LastNum
pDataMsg :=pLastMsg;

repeat
   with pDataMsg^ do begin
      wNum:=0;
      wIndMin:=1;
      wIndMax:=wNbMsg;

      // Search in  pDataMsg^  the lowest index >= wNumSearch
      if (pawNumMsg^[wNbMsg] >= wNumSearch) then repeat
         wInd:=(wIndMin + wIndMax) div 2;
         wNum:=pawNumMsg^[wInd];

         if (wNum < wNumSearch) then wIndMin:=wInd +1;
         if (wNum > wNumSearch) then wIndMax:=wInd -1;
         until (wNum = wNumSearch) or (wIndMin > wIndMax);

      if (wNum = wNumSearch) then begin
         // found the next number
         // save the index in  gglwMessages2LastNum  to be used by  GetMsgGen
         gglwMessages2LastNum:=wInd;
         GetMsgNextNum:=GetMsgGen(msgNextNum);
         exit;
         end;

      // take the number just above wNumSearch
      if (wNum < wNumSearch) and (wInd < wNbMsg) then begin
         inc(wInd);
         wNum:=pawNumMsg^[wInd];
         end;

      if (wNum > wNumSearch) and (wNum < wNumFound) then begin
         // A better number has been found
         wNumFound :=wNum;
         wIndFound :=wInd;
         pDataMsgFound:=pDataMsg;
         end;
       end; // with pDataMsg^ do begin

   // Continue searching in the messages files
   pDataMsg :=pDataMsg^.pPrevius;
   until (pDataMsg = NIL);

if (pDataMsgFound = NIL) then begin
   // No next message number found.
   gglfMessages2LastFound:=FALSE;
   GetMsgNextNum:='???!';
   exit;
   end;

// Message found
pDataMsg:=pDataMsgFound;

// save the index in  gglwMessages2LastNum  to be used by  GetMsgGen
gglwMessages2LastNum:=wIndFound;
GetMsgNextNum:=GetMsgGen(msgNextNum);
end;  // GetMsgNextNum

function GetMsg(wNumRef:word32):ShortString;
{==========================================}
var
  strS             : ShortString;

begin { - GetMsg - }
gglfMessages2LastFound:=FALSE;
if (wNumRef = msgNext) then begin
   { On garde le fichier dans lequel on a recherché en dernier }
   if (pDataMsg <> NIL) then GetMsg:=GetMsgGen(wNumRef)
   else GetMsg:='???!';
   exit;
   end;

if (wNumRef = msgNextNum) then begin
   GetMsg:=GetMsgNextNum;
   exit;
   end;

{ Lit dans le dernier fichier ouvert }
pDataMsg :=pLastMsg;

{ Par défaut, erreur indiquant que pas de fichier ouvert }
strS:='???!';
if (pDataMsg <> NIL) then begin
   strS:=GetMsgGen(wNumRef);

   while (pDataMsg^.pPrevius <> NIL) and (not gglfMessages2LastFound) do begin
      pDataMsg :=pDataMsg^.pPrevius;
      strS:=GetMsgGen(wNumRef);
      end;
   end;

GetMsg:=strS;
end;  { - GetMsg - }

function ExistMsgNumber(wNumRef:word32):boolean;
//==============================================
begin
ExistMsgNumber:=(Copy(GetMsg(wNumRef),1,3) <> '???');
end;  // ExistMsgNumber
{
//function AztGetMsg(wNumRef:word32):PChar;
//=====================================
// retourn un PChar au lieu d'un string standard
begin
glstrS:=GetMsg(wNumRef);
glstrS[length(glstrS)+1]:=#0;
AztGetMsg:=@glstrS[1];
end;  // AztGetMsg
}
procedure GetMsgPos(var ptr:pointer; var wPos:word32);
{=====================================================}
{ Retourne un pointeur sur le fichier utilisé actuellement + la position dans ce fichier.
  peut être utile, pour se repositionner avec SetMsgPos.
-------------------------------------------------------------------------}
begin { - GetMsgPos - }
ptr  :=pDataMsg;
if (pDataMsg <> NIL) then wPos :=pDataMsg^.wLastPos
else                      wPos :=0;
end;  { - GetMsgPos - }

procedure SetMsgPos(ptr:pointer; wPos:word32; var wError:Word32);
{=================================================================}
{ permet de se repositionner sur dans le fichier dont les coordonnées ont été lues avec
  GetMsgPos.
  Il est prévu de faire ensuite un GetMsg(msgNext);
  wError = 1 si le pointeur ptr n'es pas valide, = 0 si OK.
-------------------------------------------------------------------------}
var
  ptrLoc           : PTypeRecMsg;

begin { - SetMsgPos - }
{ - cherche si ptr est un pointeur valide - }
ptrLoc:=pLastMsg;

while (ptrLoc <> NIL) and (ptrLoc <> ptr) do ptrLoc:=ptrLoc^.pPrevius;

if (ptrLoc = NIL) then begin
   wError:=1;
   exit;
   end;

pDataMsg  :=ptr;
pDataMsg^.wLastPos:=wPos;
wError:=0;
end;  { - SetMsgPos - }

function GetMsgSub(wNumRef, wPlace:word32):ShortString;
//=====================================================
{ Retourne le message qui se trouve en  wPlace  position après la ligne
  indexée par wNumRef.
  Si wPlace = 0, alors GetMsgSub = GetMsg.
  Si wPlace = 1, alors GetMsgSub = la ligne suivante.

  C'est utile pour les messages ayant plusieurs lignes indexées par  0
-------------------------------------------------------------------------}
var
  strS             : string[190];
  nn               : integer;

begin { - GetMsgSub - }
strS :=GetMsg(wNumRef);
nn   :=1;

while (nn <= wPlace) do begin strS:=GetMsg(msgNext); inc(nn); end;
GetMsgSub:=strS;
end;  { - GetMsgSub - }

function IsInLastOpened:boolean;
{============================}
{ retourne TRUE, si le dernier message lu était dans le dernier fichier
  de message ouvert }
begin { - IsInLastOpened - }
IsInLastOpened:=(pLastMsg = pDataMsg);
end; { - IsInLastOpened - }

procedure ErrorReport(wError:Word32; strName:ShortString);
//=========================================================
{ Message d'erreur dans une message box standard, Si problème
  d'ouverture de fichier de message.
---------------------------------------------------------------------------}
begin { - ErrorReport - }
if (wError = 0) then exit;

ErrorWrite(1, 'in wMessage2.OpenAllMessagesFiles');

if (wError < 10) then begin
   ErrorComment(1,
      'Error trying to open the message file : ' +  strName);
    ErrorComment(1,
      'The file probably doesn''t exist. Some texte will be missing.');
   end
else begin
    ErrorComment(1,
      'Index are not in ascending order at :' + IntToStr(gglwMessages2ErrorPosition, 0));
   ErrorComment(1,
      'In the message file : ' +  strName);
   end;

end;  { - ErrorReport - }

procedure OpenAllMessagesFiles(strPathName:ShortString; var wError:Word32);
//==========================================================================
{ Ouvre le fichier pour la lecture des messages indexé --- }
{ Essaye dans le directory courrant, puis dans le directory ou se
  trouve le .EXE, puis dans le parent de .EXE, puis dans
  le répertoire glstrMsgSearchPath.
  FilenameIn = nom du fichier de messages, avec extension (.msg)
  et généralement sans répertoire de recherche, qui peut être défini dans
  glstrMsgSearchPath.
  Les messages numéro  1 à 10 donne une liste de fichiers à ouvrir
  Les messages numéro 11 à 20 donne une autre liste de fichiers à ouvrir.

  La recherche d'un message se ferra dans l'ordre suivant :
  1) Dans l'ordre inverse des fichiers donné aux messages numéro 11 à 20
  2) Le fichier des messages de l'application  (=FilenameIn)
  3) Dans l'ordre inverse des fichiers donné aux messages numéro  1 à 10.

  exemple de données dans le fichier de messages FilenameIn

  1 errFr.msg  ; les messages erreurs
  2 stdFr.msg  ; les messages standards
  3 ...END

  11 clientfr.msg ; les messages clients
  12 autre.msg
  13 ...END

  La recherche se fera dans l'ordre :
  1) autre.msg
  2) clientfr.msg
  3) Le fichier .msg du prog.
  4) stdFr.msg
  5) errFr.msg

  Si il n'y a pas de numéro commun à deux fichiers (ex. stdFr.msg et
  errFr.msg), leur ordre de placement n'est pas important
-------------------------------------------------------------------------}

var
  nSecurity   : integer;
  ptrL        : pointer;
  wPosL       : word32;

begin { - OpenAllMessagesFiles - }
// First opens the main application message file.
OpenMsg(strPathName, 0, wError); // 0=> opens in read-only mode

if (wError <> 0) then exit; // Faild to open the main message file

// Then opens all message files give at number 11 of the main message file.
// These messages will be used only if the message doesn't exit in
// the main message file, or in a message file at a level over it.
nSecurity:=10; // it is assumed that no more than 10 files will be opened.
strPathName:=GetMsg(11); // at number 11, other message file to open can be specified
while (strPathName <> '...END') and (Copy(strPathName,1,3) <> '???') and
      (nSecurity > 0) do begin
   // Memorise where we are in the Main .msg file
   GetMsgPos(ptrL, wPosL);

   strPathName:=gglstrMsgDirectory + strPathName;
   OpenMsgPlaced(strPathName, 1, 0, wError); // 1, 0 => UNDER & opens in read-only mode

   { Test d'erreur.
     Si il y a erreur, elle n'est pas fatale, mais on l'indique }
   if (wError = 10) then begin
      ErrorReport(wError, strPathName);
      end;

   dec(nSecurity);

   // Get back where we where in the Main .msg file
   SetMsgPos(ptrL, wPosL, wError);
   strPathName:=GetMsg(msgNext);
   end; { - while - }

// Then opens all message files given at number 12 of the main message file.
// These messages will override the messages defines in previous message
// files. i.e. a message number is first searched in one of these message files.
// This is what I call a LEVEL OVER the main message file.
nSecurity:=10; // it is assumed that no more than 10 files will be opened.
strPathName:=GetMsg(12);
while (strPathName <> '...END') and (Copy(strPathName,1,3) <> '???') and
      (nSecurity > 0) do begin
   // Memorise where we are in the Main .msg file
   GetMsgPos(ptrL, wPosL);

   strPathName:=gglstrMsgDirectory + strPathName;
   OpenMsgPlaced(strPathName, 0, 0, wError); // 0, 0 => Over & opens in read-only mode

   { Test d'erreur.
     Si il y a erreur, elle n'est pas fatale, mais on l'indique }
   if (wError = 10) then begin
      ErrorReport(wError, strPathName);
      end;

   dec(nSecurity);

   // Get back where we where in the Main .msg file
   SetMsgPos(ptrL, wPosL, wError);
   strPathName:=GetMsg(msgNext);
   end; { - while - }

wError:=0; // do not report errors, already done.
end;  { - OpenAllMessagesFiles - }

procedure OpenMsgRW(FilenameIn:ShortString; var wError:Word32);
{============================================================}
{ Ouverture en lecture / écriture.
  avant
  NIL <- pLastMsg
  après         pLastMsg=pMsg
  NIL <- (pPrevius, pNext) -> NIL
  après                               pLastMsg=pMsg
  NIL <- (pPrevius, pNext) <-> (pPrevius, pNext) -> NIL
-------------------------------------------------------------------------}
begin { - OpenMsgRW - }
OpenMsgPlaced(FilenameIn, 0, 0, wError); // 0, 1 => OVER & in read-write mode
end;  { - OpenMsgRW - }

procedure AddLineMsg(wNumRef:word32; strNew:ShortString; var wError:Word32);
{===========================================================================}
{ Test si possible, et si oui,
  remplace le  ...END qui termine le message wNumRef, par le
  string strNew, et rajoute un  ...END  à la suite.
  Ceci sans modifier la taille du fichier.
  Pour que ce soit possible, il faut que après le  ...END  il y ait
  des lignes quelconques, suivit d'un autre  ...END.
  c.f. tMessage.pas  et  tMessage.msg.

  OUTPUT :
  wError =
   0 si OK,
   1 si Message pas trouvé.
   2 si ...END  final pas trouvé

  10 si pas de place pour rajouter le string  strNew
  20 si il y a eu une erreur IOResult.
-------------------------------------------------------------------------}
var
   strS            : ShortString;
   nPlace,
   nLenMin,
   nn              : integer;
   lMem,
   lMemPosBEG,
   lMemPosEND
// ,lMemPosAfter
                   : word32;
   ch1,ch2         : char;

begin { - AddLineMsg - }
{ cherche le message, et sa fin }
strS:=GetMsg(wNumRef);

if (Copy(strS,1, 3) = '???') or (pDataMsg = NIL) then begin
   wError:=1;
   exit;
   end;

{ Mémorise la position du début du message }
lMemPosBEG:=pDataMsg^.wLastPos;

{ cherche la fin du message }
nn:=0;
while (strS <> '...END') and (nn < 200) do begin
   inc(nn);
   strS:=GetMsg(msgNext);
   end;

if (strS <> '...END') then begin
   wError:=2;
   exit;
   end;

{ on a trouvé la fin, Essai d'ajouter la ligne strNew. }
{------------------------------------------------------}
{ Mémorise la position du  '...END' }
lMemPosEND:=pDataMsg^.wLastPos;

{ Se positionne après le ...END }
strS:=GetMsg(msgNext);
// lMemPosAfter:=pDataMsg^.wLastPos;

{ Test si il y a de la place pour ajouter le strNew }
{ Longueur minimal nécéssaire pour ajouter le  strNew }
nLenMin:=length(strNew) + 9;

nn:=0;
nPlace:=0;
while (strS <> '...END') and (nn < 40) and (nPlace <= nLenMin) do begin
   nPlace:=nPlace + length(strS);
   inc(nn);
   strS:=GetMsg(msgNext);
   end;

{ erreur, pas de place pour ajouter le  strNew }
if (nPlace <= nLenMin) then begin
   wError:=10;
   exit;
   end;

{$i-}
{ ici on est assuré d'avoir de la place }
Seek(pDataMsg^.fileIn, lMemPosEND);

{ Remplace le '...END' par  strNew + le  '...END' + le début de la suite }
{ et s'arrange pour que la ligne suivante commence par  '    0 -' }
strS:=strNew + #13 + #10 + '    0 ...END' + #13 + #10 + '    0 -';
BlockWrite(pDataMsg^.fileIn, strS[1], length(strS));

{ test que l'on a pas coupé un  CR  LF  en deux.
  Si c'est le cas, le remplace par un - }
lMem:=FilePos(pDataMsg^.FileIn);
BlockRead(pDataMsg^.fileIn, ch1, 1);
BlockRead(pDataMsg^.fileIn, ch2, 1);

if ((ch1 = #10) and (ch2 <> #13)) or
   ((ch1 = #13) and (ch2 <> #10)) then begin
   { remplace le #10 ou #13 isolé par un '-' }
   seek(pDataMsg^.FileIn, lMem);
   ch1:='-';
   BlockWrite(pDataMsg^.fileIn, ch1, 1);
   end;

{ Se positionne sur le début du message }
with pDataMsg^ do begin
   wLastPos:=lMemPosBeg;
   seek(fileIn, wLastPos);
   nPosBuf:=0;
   BlockRead(FileIn, abBuffer, BufSizeMax, nBufSize);
   end;

{$i+}
if (IOResult <> 0) then begin
   wError:=20;
   exit;
   end;

wError:=0;
end;  { - AddLineMsg - }

procedure ReplaceLineMsg(wNumRef,wPlace:word32; strNew:ShortString; var wError:Word32);
{======================================================================================}
{ Se positionne à la ligne référencée par wNumRef,
  Si wPlace  = 0, alors reste à la ligne référencée par wNumRef
  Si wPlace  = 1, alors ligne qui suit la ligne référencée par wNumRef.

  Remplace par strNew la ligne atteinte, suivit de
    ' ;-----------------' avec une longueur exacte pour conserver
    le nombre de caractères de la ligne.
  Au retour, se positionne sur la ligne qui suit.
  En cas d'erreur, ne fait rien.

  OUTPUT :
  wError =
   0 si OK,
   1 si Message pas trouvé.
   2 si si la fin du fichier atteind

  10 si length(strNew) > longueur de la ligne à remplacer
  20 si il y a eu une erreur IOResult.
-------------------------------------------------------------------------}
var
  strS            : ShortString;
  nLen,
  nn              : integer;
  lMemPosBEG      : longint;

begin { - ReplaceLineMsg - }
// Get the message, to set  pDataMsg  to the right pointer
// And to be positionned at the right place in the .msg file
strS:=GetMsg(wNumRef);

// Switch into Read / Write mode
filemode:=2;
reset(pDataMsg^.fileIn, 1);

{ cherche le message, et sa fin }
//strS:=GetMsg(wNumRef);

if (Copy(strS,1, 3) = '???') or (pDataMsg = NIL) then begin
   wError:=1;
   filemode:=0; // read only mode
   reset(pDataMsg^.fileIn, 1);
   exit;
   end;

{ Saute wSkip lignes }
for nn:=1 to wPlace do begin
   strS:=GetMsg(msgNext);

   if (Copy(strS,1, 3) = '???') then begin
      wError:=2;
      filemode:=0; // read only mode
      reset(pDataMsg^.fileIn, 1);
      exit;
      end;
   end;

{ Mémorise la position du début du message }
lMemPosBEG:=pDataMsg^.wLastPos;

{ Lecture de la ligne }
{$i-}
Seek(pDataMsg^.fileIn, lMemPosBEG);
BlockRead(pDataMsg^.fileIn, strS[1], 250, nLen);

{ Cherche la fin de ligne. }
nn:=1;
while (nn < nLen) and (strS[nn] <> #13) do inc(nn);

if (nn >= nLen) then begin
   { pas de fin de ligne trouvée }
   wError:=2;
   filemode:=0; // read only mode
   reset(pDataMsg^.fileIn, 1);
   exit;
   end;

if (IOResult <> 0) then begin
   wError:=20;
   filemode:=0; // read only mode
   reset(pDataMsg^.fileIn, 1);
   exit;
   end;

{ longueur de ligne à remplacer }
nLen:=nn-1;
strS[0]:=char(nn-1);

{ Test si la ligne à remplacer est assez longue }
if (nLen < length(strNew)) then begin
   { nouvelle ligne trop longue }
   wError:=10;
   filemode:=0; // read only mode
   reset(pDataMsg^.fileIn, 1);
   exit;
   end;

{ complet la nouvelle ligne, pour garder exactement la même longuer de ligne}
if (length(strNew) < nLen) then strNew:=strNew + ' ';
if (length(strNew) < nLen) then strNew:=strNew + char(cteComment);
while (length(strNew) < nLen) do strNew:=strNew + '-';

Seek(pDataMsg^.fileIn, lMemPosBEG);
BlockWrite(pDataMsg^.fileIn, strNew[1], length(strNew));

// Switch back to the read only mode.
filemode:=0;
reset(pDataMsg^.fileIn, 1);

{ On se positionne sur la ligne suivante, i.e.
  un GetMsg(msgNext) retourne la ligne suivante. }
Seek(pDataMsg^.fileIn, lMemPosBEG);

{$i+}
if (IOResult <> 0) then begin
   wError:=20;
   exit;
   end;

wError:=0;
end;  { - ReplaceLineMsg - }

procedure AppendLine(wMsgRef, wMsgNum:Word32;  strFilePath, strS:ShortString; var wError:Word32);
//================================================================================================
// Append a line to the file  strFilePath, (usually a .msg file)
// The appened line begins with the number wMsgNum
// followed by one space, followed by the strS string.
// if the file is opened as a  .msg  file,
// If (wMsgRef <> 0)  then it is  ASSUMED  that
// the file  strFilePath  has been opened as a  .msg  file, AND
// That the message number  wMsgRef  exist in this file.
// Thus the file will be closed, to be reopened in read/write mode,
// the string will be append,
// and it will be reopened in read only mode.
var
  fOut        : text;
  fDoReOpen   : boolean;
begin
{$i-}
fDoReOpen:=FALSE;
if (wMsgRef <> 0) then begin
   // Test if the message number  wMsgRef  exist.
   // If yes, it is  ASSUMED  that it is in the file strFilePath.
   // Thus we close it first.
   GetMsg(wMsgRef);  // just to set  pDataMsg  to point the right record.
   fDoReOpen:= (Copy(strS,1,3) <> '???');
   if fDoReOpen then Close(pDataMsg^.fileIn);
   IOResult;  // call IOResult to set the I/O errors to zero
   end;

// Opens the file to append a line of text.
FileMode:=2; { read / write }
Assign(fOut, strFilePath);
Append(fOut);

// Append the string
writeln(fOut, wMsgNum, ' ', strS);

Close(fOut);
wError:=IOResult; // reset any errors, but ignore them.

// If the file was opened as a message file, reopen it in read only mode.
if (fDoReOpen) then begin
   FileMode:=0; // Read Only
   Reset(pDataMsg^.fileIn, 1);
   FileMode:=2; { read / write }
   if (wError = 0) then wError:=IOResult;
   end;
end;  // AppendLine

end. // unit wMessages2;

