/***************************************************************************
 *                                                                         *
 *   KWDSRCH.CPP                                                           *
 *                                                                         *
 *   Copyright (c) 1998 Galacticomm, Inc.         All Rights Reserved.     *
 *                                                                         *
 *   Active HTML File Library keyword search engine implementation.        *
 *                                                                         *
 *                                           - J. Alvrus    12/02/1998     *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "majorbbs.h"
#include "galfilh.h"
#include <cstring.h>               // change if use different string impl.
#include "filtdef.h"
#include "libqueue.h"
#include "kwdsrch.h"

#define FILREV "$Revision: 4 $"

#define RET(rc) return(m_LastRet=(rc))

MARKSOURCE(kwdsrch)

flKeywordSearch::flKeywordSearch() :
     m_nWords(0),
     m_isBrute(false),
     m_First(true),
     m_LastRet(FLKWS_NONE),
     m_libList(NULL)
{
     ::memset(m_WordArr,0,sizeof(m_WordArr));
     *m_Primitive='\0';
     *m_WordList='\0';
     *m_LibPrimary='\0';
     *m_LibCurrent='\0';
     *m_File='\0';
     *m_Keyword='\0';
}

flKeywordSearch::~flKeywordSearch()
{
}

flkwStatus                         //   returns status code
flKeywordSearch::Init(             // initialize this search
CHAR const * lib,                  //   library name or NULL/"" for all
CHAR const * start,                //   value of start parameter or NULL/""
CHAR const * kwds,                 //   keyword string supplied by user
filLibMap* libmap)                 //   joined library map
{
     flkwStatus rc;

     m_libList=libmap;
     // ignore trivial case
     if (*kwds == '\0') {
          RET(FLKWS_OK);
     }

     // parse keyword string
     rc=ParseKeywords(kwds);
     if (rc != FLKWS_OK) {
          RET(rc);
     }

     // initialize search type
     m_isBrute=(::longsrch
             || (::strcspn(m_Primitive,"|^!(") != ::strlen(m_Primitive)));

     // check for useable keywords
     if (!m_isBrute) {
          dfaSetBlk(::flkdat);
          for (INT i=0 ; i < m_nWords; ++i) {
               struct key2 TempKey;
               ::memset(&TempKey,0,sizeof(struct key2));
               ::stlcpy(TempKey.keyword,m_WordArr[i],FLKEYSIZ);
               if (!dfaAcqGE(&TempKey,&TempKey,COMPKFL)
                || !::sameto(m_WordArr[i],TempKey.keyword)) {
                    m_Misc=m_WordArr[i];
                    dfaRstBlk();
                    RET(FLKWS_NOWORD);
               }
          }
          dfaRstBlk();
     }

     // initialize library
     if (!NULSTR(lib)) {
          ::stlcpy(m_LibPrimary,lib,FLNAMESZ);
     }

     // initialize position based on start parameter
     if (NULSTR(start)) {          // starting search
          if (m_isBrute) {
               ::stlcpy(m_LibCurrent,m_LibPrimary,FLNAMESZ);
          }
          else {
               // keyword lookup is indexed on first keyword
               ::stlcpy(m_Keyword,m_WordList,FLKEYSIZ);
          }
     }
     else {                        // continuing search

          // validate type
          if ((m_isBrute && start[0] != 'l')
           || (!m_isBrute && start[0] != 'k')) {
               RET(FLKWS_BADSTART);
          }

          // extract primary library name
          CHAR const * pBeg=start+1;
          CHAR TempLib[FLNAMESZ];
          if (!ExtractPart(&pBeg,TempLib,FLNAMESZ)           // part too big
           || pBeg == NULL                               // or no more parts
           || !::sameas(TempLib,m_LibPrimary)) { // or doesn't match primary
               RET(FLKWS_BADSTART);
          }

          // extract key values
          // (brute force search = lib/file, else kwd/lib/file)
          if (!m_isBrute) {

               // extract current keyword
               if (!ExtractPart(&pBeg,m_Keyword,FLKEYSIZ) // part is too big
                || pBeg == NULL                // or there are no more parts
                || *m_Keyword == '\0'             // or the keyword is blank
                || !::sameto(m_WordList,m_Keyword)) {    // or doesn't match
                    RET(FLKWS_BADSTART);
               }
          }

          // extract current library
          if (!ExtractPart(&pBeg,m_LibCurrent,FLNAMESZ)      // part too big
           || pBeg == NULL                               // or no more parts
           || *m_LibCurrent == '\0'                  // or the name is empty
           || ::libfind(m_LibCurrent) == NULL) {     // or lib doesn't exist
               RET(FLKWS_BADSTART);
          }

          // extract current file
          if (!ExtractPart(&pBeg,m_File,FLFILENM)         // part is too big
           || pBeg != NULL                        // or there are more parts
           || *m_File == '\0') {                     // or the name is empty
               RET(FLKWS_BADSTART);
          }
     }

     RET(FLKWS_OK);
}

flkwStatus                         //   returns status code
flKeywordSearch::Find(             // find next file in search
INT direction,                     //   direction to search
struct flfile * buf)               //   buffer to receive file info
{
     if (m_nWords == 0) {
          RET(FLKWS_DONE);
     }
     if (!m_First) {
          direction&=~DIR_EQ;
     }
     flkwStatus rc=(m_isBrute ? FindBrute(direction,buf)
                              : FindKeyword(direction,buf));
     m_First=false;
     RET(rc);
}

string                             //   returns state string or "" if invalid
flKeywordSearch::GetState()        // extract state string (for start param)
{
     string RetStr;
     if (m_LastRet == FLKWS_FILE) {
          if (m_isBrute) {
               RetStr+='l';
          }
          else {
               RetStr+='k';
          }
          RetStr+=m_LibPrimary;
          RetStr+=';';
          if (!m_isBrute) {
               RetStr+=m_Keyword;
               RetStr+=';';
          }
          RetStr+=m_LibCurrent;
          RetStr+=';';
          RetStr+=m_File;
     }
     return(RetStr);
}

/***************************************************************************
 *                                                                         *
 *   Private Member Functions                                              *
 *                                                                         *
 ***************************************************************************/

flkwStatus                         //   returns status code
flKeywordSearch::FindKeyword(      // find next file in keyword search
INT direction,                     //   direction to search
struct flfile * buf)               //   buffer to receive file info
{
     // form data file key
     struct key2 key;
     ::stlcpy(key.keyword,m_Keyword,FLKEYSIZ);
     ::stlcpy(key.filname,m_File,FLKEYSIZ);
     ::stlcpy(key.libkey,m_LibCurrent,FLKEYSIZ);

     // read from keyword data file
     if (!ReadKeywordRec(&key,direction)          // end of file
      || !::sameto(m_WordList,key.keyword)) {     // end of sequence
          return(FLKWS_DONE);
     }

     flkwStatus rc=FLKWS_OK;

     // get library
     struct fllib * pLib=::libfind(key.libkey);
     if (pLib != NULL) {
          // get file record
          struct flfile tmpbuf;
          struct key1 FileKey;
          ::stlcpy(FileKey.libname,key.libkey,FLNAMESZ);
          ::stlcpy(FileKey.filname,key.filname,FLFILENM);
          if (ReadFileRec(&tmpbuf,&FileKey,DIR_EQ)) {
               // check for match with remaining keywords
               if (CheckKeywords(&tmpbuf,1)) {
                    ::memcpy(buf,&tmpbuf,sizeof(struct flfile));
                    rc=FLKWS_FILE;
               }
          }
     }

     // update current position
     ::stlcpy(m_Keyword,key.keyword,FLKEYSIZ);
     ::stlcpy(m_File,key.filname,FLKEYSIZ);
     ::stlcpy(m_LibCurrent,key.libkey,FLKEYSIZ);

     return(rc);
}

flkwStatus                         //   returns status code
flKeywordSearch::FindBrute(        // find next file in brute force search
INT direction,                     //   direction to search
struct flfile * buf)               //   buffer to receive file info
{
     // form data file key
     struct key1 key;
     ::stlcpy(key.libname,m_LibCurrent,FLNAMESZ);
     ::stlcpy(key.filname,m_File,FLKEYSIZ);

     // read from keyword data file
     struct flfile tmpbuf;
     if (!ReadFileRec(&tmpbuf,&key,direction)) {
          return(FLKWS_DONE);
     }

     // check if library is within bounds
     if (*m_LibPrimary != '\0'
      && !::sameas(tmpbuf.libname,m_LibCurrent)) {
          if (m_libList->getNextJoinedLib(direction,m_LibCurrent)) {
               if (direction&DIR_GT) {
                    *m_File='\0';
               }
               else {
                    ASSERT(direction&DIR_LE);
                    ::memset(m_File,'\xFF',FLFILENM);
               }
               return(FLKWS_OK);
          }
          else {
               return(FLKWS_DONE);
          }
     }

     // check keywords
     flkwStatus rc=FLKWS_OK;
     if (CheckKeywords(&tmpbuf,0)) {
          ::memcpy(buf,&tmpbuf,sizeof(struct flfile));
          rc=FLKWS_FILE;
     }

     // update current position
     ::stlcpy(m_File,tmpbuf.filname,FLKEYSIZ);
     ::stlcpy(m_LibCurrent,tmpbuf.libname,FLKEYSIZ);

     return(rc);
}

bool                               //   returns true if keywords match
flKeywordSearch::CheckKeywords(    // check all keywords
struct flfile const * pFileInfo,   //   file info
INT iStart)                        //   starting at which keyword
{
     ASSERT(pFileInfo != NULL);
     ASSERT(iStart >= 0);

     // handle trivial case (one word, already matched)
     if (iStart == 1 && m_nWords == 1) {
          return(true);
     }

     // parse file info into dargv
     if (!::makwdlst(pFileInfo->desc,pFileInfo->filname)) {
          return(false);           // should only fail if record empty
     }

     // form Boolean string ("0|(1&0)") from keyword primitive ("?|(?&?)")
     CHAR BoolStr[FLKEYLST];
     ::stlcpy(BoolStr,m_Primitive,FLKEYLST);
     for (INT i=0 ; i < m_nWords ; ++i) {

          // see if current word matches any in file info
          bool isMatch=true;       // assume words < iStart already matched
          if (i >= iStart) {
               isMatch=CheckOneKeyword(m_WordArr[i]);
          }

          // apply to match string
          ReplaceStringFirst(BoolStr,FLKEYLST,"?",isMatch ? "1" : "0");
     }

     // convert match string into final answer
     return(EvaluateBoolean(BoolStr));
}

static INT                         //   > 0 = target > test, etc.
KeywordMatchComp(                  // binary search util comparison func
VOID const * target,               //   target object
VOID const * array,                //   array object
ULONG index)                       //   index of array element to test
{
     CHAR const * str=static_cast<CHAR const *>(target);
     CHAR const * const * arr=static_cast<CHAR const * const *>(array);
     return(stricmp(str,arr[index]));
}

bool                               //   returns result of evaluation
flKeywordSearch::CheckOneKeyword(  // find out if a keyword matches a file
CHAR const * Target)               //   keyword to check
{                                  // (assumes sorted keyword list in dargv)
     // binary search for target in file's keyword list
     INT cmp;
     INT iMatch=::binFindNear(&cmp,Target,::dargv,::dargc,::KeywordMatchComp);

     // compare target to nearest word
     return(::sameto(Target,::dargv[iMatch]));
}

bool                               //   returns result of evaluation
flKeywordSearch::EvaluateBoolean(  // evaluate a Boolean expression string
CHAR const * BoolStr)              //   Boolean expression string
{
     CHAR sCur[FLKEYLST];          // current expression string
     CHAR sPrev[FLKEYLST];         // expression before last processing round

     ::stlcpy(sCur,BoolStr,FLKEYLST);
     while (::strlen(sCur) > 1) {

          // save current string
          ::stlcpy(sPrev,sCur,FLKEYLST);

          // evaluate boolean expressions
          do {
               do {
                    do {
                         do {
                              do {
                              } while (ReplaceStringFirst(sCur,FLKEYLST,"(1)","1")
                                    || ReplaceStringFirst(sCur,FLKEYLST,"(0)","0"));
                         } while (ReplaceStringFirst(sCur,FLKEYLST,"!1","0")
                               || ReplaceStringFirst(sCur,FLKEYLST,"!0","1"));
                    } while (ReplaceStringFirst(sCur,FLKEYLST,"1&1","1")
                          || ReplaceStringFirst(sCur,FLKEYLST,"1&0","0")
                          || ReplaceStringFirst(sCur,FLKEYLST,"0&1","0")
                          || ReplaceStringFirst(sCur,FLKEYLST,"0&0","0"));
               } while (ReplaceStringFirst(sCur,FLKEYLST,"1^1","0")
                     || ReplaceStringFirst(sCur,FLKEYLST,"1^0","1")
                     || ReplaceStringFirst(sCur,FLKEYLST,"0^1","1")
                     || ReplaceStringFirst(sCur,FLKEYLST,"0^0","0"));
          } while (ReplaceStringFirst(sCur,FLKEYLST,"1|1","1")
                || ReplaceStringFirst(sCur,FLKEYLST,"1|0","1")
                || ReplaceStringFirst(sCur,FLKEYLST,"0|1","1")
                || ReplaceStringFirst(sCur,FLKEYLST,"0|0","0"));

          // check for invalid Boolean primitive
          if ((::strchr(sCur,'1') == NULL && ::strchr(sCur,'0') == NULL)
           || ::sameas(sPrev,sCur)) {
               return(false);
          }
     }
     ASSERT(::strlen(sCur) == 1);
     return(sCur[0] == '1');
}

flkwStatus                         //   returns status code
flKeywordSearch::ParseKeywords(    // parse user-supplied keyword string
CHAR const * kwds)                 //   keyword string supplied by user
{
     CHAR buf[2*FLKEYLST];         // temp buffer for initial massaging

     if (::strlen(kwds) >= 2*FLKEYLST) {
          return(FLKWS_LENGTH);
     }
     ::stlcpy(buf,kwds,sizeof(buf));

     // remove any question marks
     RemoveCharAll(buf,'?');

     // replace words (AND,OR,NOT) with symbols (&,|,!)
     ReplaceTokenAll(buf,sizeof(buf),"and","&");
     ReplaceTokenAll(buf,sizeof(buf),"or","|");
     ReplaceTokenAll(buf,sizeof(buf),"xor","^");
     ReplaceTokenAll(buf,sizeof(buf),"not","!");

     // create word list
     CHAR TempList[2*FLKEYLST];
     ::stlcpy(TempList,buf,sizeof(TempList));
     // (replace symbols with spaces)
     ::strrpl(TempList,'&',' ');
     ::strrpl(TempList,'|',' ');
     ::strrpl(TempList,'!',' ');
     ::strrpl(TempList,'^',' ');
     ::strrpl(TempList,'(',' ');
     ::strrpl(TempList,')',' ');
     // (remove all multiple spaces -> "word word word ")
     ::strmove(TempList,::unpad(::skpwht(TempList)));
     ::stlcat(TempList," ",sizeof(TempList));
     for (CHAR * cp=TempList ; cp != NULL ; ) {
          if ((cp=::strchr(cp,' ')) != NULL) {
               ++cp;
               CHAR * pNonWhite=::skpwht(cp);
               if (pNonWhite != cp) {
                    ::strmove(cp,pNonWhite);
               }
          }
     }
     // (truncate words to maximum length)
     for (CHAR * pBeg=TempList ; *pBeg != '\0' ; ) {
          CHAR * pEnd=::strchr(pBeg,' ');
          ASSERT(pEnd != NULL);
          if ((pEnd-pBeg) > (FLKEYSIZ-1)) {
               ::strmove(pBeg+(FLKEYSIZ-1),pEnd);
          }
          pBeg=pEnd+1;
     }
     // (check length)
     if (::strlen(TempList) >= FLKEYLST) {
          return(FLKWS_LENGTH);
     }
     // (copy into permanent storage)
     ::stlcpy(m_WordList,TempList,FLKEYLST);
     // (replace spaces with nuls -> "word\0word\0word\0\0")
     for (CHAR * cp=m_WordList ; (cp=::strchr(cp,' ')) != NULL ; ++cp) {
          *cp='\0';
     }
     // (set up array of pointers to keywords)
     for (CHAR * cp=m_WordList ; *cp != '\0' ; cp+=::strlen(cp)+1) {
          m_WordArr[m_nWords++]=cp;
     }

     // create Boolean primitive
     // (replace words with "?")
     for (CHAR * cp=m_WordList ; *cp != '\0' ; cp+=::strlen(cp)+1) {
          ReplaceTokenFirst(buf,sizeof(buf),cp,"?");
     }
     // (remove all spaces)
     RemoveCharAll(buf,' ');
     // (remove unnecessary parens)
     while (ReplaceStringFirst(buf,sizeof(buf),"(?)","?")) {
     }
     // (allow for implied ANDs)
     while (ReplaceStringFirst(buf,sizeof(buf),"??","?&?")) {
     }
     while (ReplaceStringFirst(buf,sizeof(buf),"?!?","?&!?")) {
     }
     // (check syntax)
     flkwStatus rc=ValidateSyntax(buf);
     if (rc != FLKWS_OK) {
          return(rc);
     }
     // (check length)
     if (::strlen(buf) >= FLKEYLST) {
          return(FLKWS_LENGTH);
     }
     ::stlcpy(m_Primitive,buf,FLKEYLST);

     return(FLKWS_OK);
}

flkwStatus                         //   returns status code
flKeywordSearch::ValidateSyntax(   // check the syntax of a Boolean primitive
CHAR const * s)                    //   primitive string to validate
{
     CHAR c;                       // current character
     CHAR lastc='(';               // last character, determines next
     INT nParens=0;                // level of nested parentheses

     while ((c=*s) != '\0') {
          switch (lastc) {
          case '(':                // expect start of a statement
          case '&':
          case '!':
               switch (c) {
               case '(':
                    ++nParens;
               case '!':
               case '?':
                    break;
               default:
                    return(FLKWS_SYNTAX);
               }
               break;
          case '?':                // expect end of a statement
          case ')':
               switch (c) {
               case ')':
                    --nParens;
                    break;
               case '&':
               case '|':
               case '^':
                    c='&';
                    break;
               default:
                    return(FLKWS_SYNTAX);
               }
               break;
          }
          lastc=c;
          ++s;
     }
     if (lastc != '?' && lastc != ')') {
          return(FLKWS_SYNTAX);
     }
     if (nParens != 0) {
          return(FLKWS_PARENS);
     }
     return(FLKWS_OK);
}

bool                               //   returns true if replaced a token
flKeywordSearch::ReplaceTokenFirst( // replace first of one token with another
CHAR * buf,                        //   buffer containing string to modify
size_t bufSiz,                     //   size of buffer
CHAR const * tokOld,               //   old token to be replaced
CHAR const * tokNew)               //   new token to replace with
{
     size_t sizOld=::strlen(tokOld); // size of old token
     size_t sizNew=::strlen(tokNew); // size of new token
     INT sizDif=(sizNew-sizOld);   // difference in sizes
     CHAR * cp=FindToken(buf,tokOld);
     if (cp != NULL) {
          if ((::strlen(buf)+sizDif) < bufSiz) {
               ::strmove(cp+sizNew,cp+sizOld);
               ::memcpy(cp,tokNew,sizNew);
               return(true);
          }
     }
     return(false);
}

bool                               //   returns false if out of buffer room
flKeywordSearch::ReplaceTokenAll(  // replace all of one token with another
CHAR * buf,                        //   buffer containing string to modify
size_t bufSiz,                     //   size of buffer
CHAR const * tokOld,               //   old token to be replaced
CHAR const * tokNew)               //   new token to replace with
{
     CHAR * cp;                    // pointer to next token to replace
     size_t sizOld=::strlen(tokOld); // size of old token
     size_t sizNew=::strlen(tokNew); // size of new token
     INT sizDif=(sizNew-sizOld);   // difference in sizes

     // find tokens
     cp=buf;
     while ((cp=FindToken(cp,tokOld)) != NULL) {

          // make space for new token
          if ((::strlen(buf)+sizDif) >= bufSiz) {
               return(false);
          }
          ::strmove(cp+sizNew,cp+sizOld);

          // copy in new token
          ::memcpy(cp,tokNew,sizNew);
          cp+=sizNew;
     }

     return(true);
}

CHAR *                             //   returns a pointer to the token or NULL
flKeywordSearch::FindToken(        // find a token
CHAR * buf,                        //   buffer containing string to search
CHAR const * token)                //   token to search for
{
     if (*token == '\0') {
          return(NULL);
     }
     size_t tokenLen=::strlen(token);
     CHAR * cp=buf;
     for (;;) {
          cp=FindString(cp,token);
          if (cp == NULL) {
               break;
          }
          if ((cp == buf || isTokenSep(*(cp-1)))
           && (*(cp+tokenLen) == '\0' || isTokenSep(*(cp+tokenLen)))) {
               return(cp);
          }
          ++cp;
     }
     return(NULL);
}

bool                               //   returns true if replaced a token
flKeywordSearch::ReplaceStringFirst( // replace first of one string with another
CHAR * buf,                        //   buffer containing string to modify
size_t bufSiz,                     //   size of buffer
CHAR const * strOld,               //   old string to be replaced
CHAR const * strNew)               //   new string to replace with
{
     size_t sizOld=::strlen(strOld); // size of old string
     size_t sizNew=::strlen(strNew); // size of new string
     INT sizDif=(sizNew-sizOld);   // difference in sizes
     CHAR * cp=FindString(buf,strOld);
     if (cp != NULL) {
          if ((::strlen(buf)+sizDif) < bufSiz) {
               ::strmove(cp+sizNew,cp+sizOld);
               ::memcpy(cp,strNew,sizNew);
               return(true);
          }
     }
     return(false);
}

CHAR *                             //   returns a pointer to substring or NULL
flKeywordSearch::FindString(       // find a string within another
CHAR * str,                        //   string to search
CHAR const * sub)                  //   substring to search for
{
     if (*sub == '\0') {
          return(NULL);
     }
     while (*str != '\0') {
          if (tolower(*str) == tolower(*sub)) {
               CHAR const * scanStr=str+1;
               CHAR const * scanSub=sub+1;
               while (*scanStr != '\0' && *scanSub != '\0'
                   && tolower(*scanStr) == tolower(*scanSub)) {
                    ++scanStr;
                    ++scanSub;
               }
               if (*scanSub == '\0') {
                    return(str);
               }
          }
          ++str;
     }
     return(NULL);
}

bool
flKeywordSearch::isTokenSep(       // is this a token separator character?
CHAR c)                            //   character to test
{
     return(c == ' ' || c == '&' || c == '|' || c == '^' || c == '!'
         || c == '(' || c == ')');
}

bool                               //   returns true if any removed
flKeywordSearch::RemoveCharAll(    // remove all occurances of a character
CHAR * buf,                        //   buffer containing string to modify
CHAR c)                            //   character to delete
{
     bool rc=false;
     for (CHAR * pFirst=buf ; ; ) {
          if ((pFirst=::strchr(pFirst,c)) == NULL) {
               break;
          }
          CHAR * pAfter=pFirst+1;
          while (*pAfter == c) {
               ++pAfter;
          }
          ::strmove(pFirst,pAfter);
          rc=true;
     }
     return(rc);
}

CHAR *                             //   returns matching parenthesis
flKeywordSearch::FindMatch(        // find matching close parenthesis
CHAR * buf)                        //   buffer to search
{
     if (*buf == '(') {
          INT nParens=1;
          CHAR * s=buf+1;
          while (*s != '\0') {
               switch (*s) {
               case '(':
                    ++nParens;
                    break;
               case ')':
                    if (--nParens == 0) {
                         return(s);
                    }
                    break;
               }
               ++s;
          }
     }
     return(buf);
}

bool                               //   returns false if too big
flKeywordSearch::ExtractPart(      // extract a part of a delimited string
CHAR const ** ppSrc,               //   ptr to ptr to start of part to extract
CHAR * buf,                        //   buffer to receive part
size_t bufSiz)                     //   size of buffer
{
     CHAR const * pBeg=*ppSrc;
     CHAR const * pEnd=::strchr(pBeg,*STARTDELIM);
     if (pEnd == NULL) {
          pEnd=pBeg+::strlen(pBeg);
          *ppSrc=NULL;
     }
     else {
          *ppSrc=pEnd+1;
     }
     if ((pEnd-pBeg) >= bufSiz) {
          return(false);
     }
     ::memcpy(buf,pBeg,pEnd-pBeg);
     buf[pEnd-pBeg]='\0';
     return(true);
}

bool                               //   returns false if no more records
flKeywordSearch::ReadKeywordRec(   // read a record from the keyword database
struct key2 * key,                 //   keyword info (updated)
INT direction)                     //   read direction flags
{
     dfaSetBlk(::flkdat);
     bool rc=QueryRec(key,COMPKFL,direction);
     if (rc) {
          dfaAbsRec(key,COMPKFL);
     }
     dfaRstBlk();
     return(rc);
}

bool                               //   returns false if no more records
flKeywordSearch::ReadFileRec(      // read a record from the file database
struct flfile * buf,               //   buffer to receive file info
struct key1 const * key,           //   library/file key
INT direction)                     //   read direction flags
{
     dfaSetBlk(::flfdat);
     bool rc=QueryRec(key,COMPLF,direction);
     if (rc) {
          dfaAbsRec(buf,COMPLF);
     }
     dfaRstBlk();
     return(rc);
}

bool                               //   returns false if no more records
flKeywordSearch::QueryRec(         // perform directional query on a database
VOID const * pKey,                 //   key value
INT nKey,                          //   key number
INT direction)                     //   read direction flags
{
     switch (direction) {
     case DIR_EQ:
          return(dfaQueryEQ(pKey,nKey));
     case DIR_LT:
          return(dfaQueryLT(pKey,nKey));
     case DIR_LE:
          return(dfaQueryLE(pKey,nKey));
     case DIR_GT:
          return(dfaQueryGT(pKey,nKey));
     case DIR_GE:
          return(dfaQueryGE(pKey,nKey));
     }
     return(false);
}
