/*
 * CDE - Common Desktop Environment
 *
 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these libraries and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 */
/* $TOG: XlationSvc.c /main/13 1999/10/14 15:59:35 mgreess $ */
/****************************************************************************
$FILEBEG$:    XlationSvc.c
$PROJECT$:    Cde 1.0
$COMPONENT$:  DtXlate service
$1LINER$:     Implements a translation service using tables and regex search
$COPYRIGHT$:
 (c) Copyright 1993, 1994 Hewlett-Packard Company
 (c) Copyright 1993, 1994 International Business Machines Corp.
 (c) Copyright 1993, 1994 Sun Microsystems, Inc.
 (c) Copyright 1993, 1994 Unix System Labs, Inc., a subsidiary of Novell, Inc.
$END$
 ****************************************************************************
 ************************************<+>*************************************/

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#if defined(sun)
#include <sys/utsname.h>
#endif
#include <limits.h>           /* INT_MAX */
#include <pwd.h>              /* for getpw... */
#include <sys/utsname.h>      /* for uname */
#include <sys/param.h>        /* MAXPATHLEN */

/* for RADIXCHAR and nl_langinfo */
#if defined(__linux__)
# define RADIXCHAR MON_DECIMAL_POINT
#endif
#include <langinfo.h>       

#if defined(sun)
#include <regexpr.h>          /* for compile,advance */
#else
#include <regex.h>            /* for regcomp,regexec */
#endif

/* for Xrm */
#include <X11/Intrinsic.h>
#include <X11/Xresource.h>

/*=================================================================
$SHAREDBEG$:  This header appears in all appropriate DtXlate topics
=======================================================$SKIP$======*/
/*$INCLUDE$*/ 
#include "XlationSvc.h"
#include "LocaleXlate.h"
/*$END$*/

/**** For extensive Introductory info, go to the end of this file ****/

/*========================================================*/
/*====================== Constants =======================*/
/*========================================================*/

/*=============== private =================*/
/*#define DBG_MATCHING  ** if defined, debugging matching stages are compiled */

#define DEBUG_XLATE_NAME	"dtXlate.debugDtXlate"
#define DEBUG_XLATE_CLASS	"DtXlate.DebugDtXlate"

#define EOS                '\0'

/* A "random" number used to ensure that the Db has been initalized */
#define INIT_OCCURRED      2329479
#define PATH_SEPARATOR     ':'
#define DIR_SLASH          '/'

#define LESSTHAN_STR     "<"
#define EQUALS_STR       "="
#define CONTAINS_STR     "~"
#define MORETHAN_STR     ">"
#define INHIBIT_STR      "0"

#define MATCHALL_CHAR      '?'
#define ESCAPE_CHAR        '\\'
#define COMMENT_CHAR       '!'
#define QUOTE_CHAR         '\"'
#define OPER_SEPARATOR     ','
#define STDVALUE_SEPARATOR '.'
#define MATCHALL_VER       0

#define PLATFORM_QUARK   0      /* index offsets into quarklist */
#define VERSION_QUARK    1
#define OPERS_QUARK      2
#define DIRECTION_QUARK  3
#define FIRSTSTD_QUARK   4

#define MAXSPECQUARKS    43     /* std + platform + version + operation */
#define MAXSTDQUARKS     40
#define MAXRHSSIZE       100    /* max supported RHS size */
#define MAXLHSSIZE       200    /* max supported LHS size */
#define MAXINTSTRSIZE    15     /* handle any long int -> string */

typedef enum {
   __DtXLATE_TYPE_NONE = 0,
   __DtXLATE_TYPE_INHIBIT = 1,
   __DtXLATE_TYPE_REGEX = 2,
   __DtXLATE_TYPE_PURE = 3,
   __DtXLATE_TYPE_CONTAINS = 4
} __DtXlateType;

#if defined(sun)
/* Sun doesn't support regcomp() and regexec() yet, so
   define this here and fill it will the results of advance() */
typedef struct regmatch_t {
    int rm_so;        /* start offset */
    int rm_eo;        /* end offset */
} regmatch_t;
#endif

/*========================================================*/
/*====================== Typedefs ========================*/
/*========================================================*/

#if DOC
/*========================================================*/
$PTYPEBEG$: __DtXlateDbRec
$1LINER$:  A private object used to represent translation dbs
$SUMMARY$:
__DtXlateDbRec is the type of the contents of a translation database
object.  The database object must be opened before use and closed
after use.  The definition of the object is opaque to users.
$ARGS$:
xrmDb:         Xrm database used to hold specs
initGuard:     used to test whether Db initialized
/*================================================$SKIP$==*/
#endif

/*$DEF$*/
typedef struct __DtXlateDbRec
{
  XrmDatabase  xrmDb;
  int          initGuard;
  Boolean      debugMode;
} __DtXlateDbRec;
/*$END$*/

#if DOC
/*========================================================*/
$PTYPEBEG$: __DtXlateSrchData
$1LINER$:  A private object used to collect search-related data
$SUMMARY$:
__DtXlateSrchData stores all the data pertaining to a search
and the search results.  The search routines utilize this
to maintain status info over multiple calls by the
enumeration routines and to return info to the routine
that initiated the search.

$ARGS$:
filterQuarks:  quark list for the platform, version, and op filter
stdValueQuarks:   quark list for the std value
opValue:       ptr to operation-specific value string
SpecRef:       indices 1 to MAXSPECQUARKS point to matching Xrm strings
SubEx:         indices 1 to MAXSPECQUARKS index sub exprs in opValue
/*================================================$SKIP$==*/
#endif
/*$DEF$*/
typedef struct __DtXlateSrchData
{
   /* db info */
   _DtXlateDb      db;

   /* query info */
   const char *    platformStr;
   XrmQuark        platformQuark;
   int             version;
   char            verStr[MAXINTSTRSIZE];   /* handle any long int */
   const char *    operStr;
   int             operStrLen;
   XrmQuark        operQuark;
   XrmQuark        lessThanQuark;
   XrmQuark        equalsToQuark;
   XrmQuark        containsQuark;
   XrmQuark        moreThanQuark;
   XrmQuark        inhibitQuark;

   /* query or search info */
   XrmQuark        stdValueQuarks[MAXSTDQUARKS];
   const char *    opValue;

   /* search info */
   __DtXlateType   curTransType;
   __DtXlateType   bestTransType;
   int             curScore;
   int             bestScore;
   /* MAXSPECQUARKS is depended upon elsewhere to be the size of these */
   const char *    curSpecRefs[MAXSPECQUARKS];
   const char *    bestSpecRefs[MAXSPECQUARKS];  
   regmatch_t      curSubEx[MAXSPECQUARKS];   /* pattern match data */
   regmatch_t      bestSubEx[MAXSPECQUARKS];  /* pattern match data */
} __DtXlateSrchData;
/*$END$*/

/*========================================================*/
/*================== Private routines ====================*/
/*========================================================*/

#if DOC
/*========================================================*/
$PFUNBEG$:  ExpandPath()
$1LINER$: adds current working directory to front of path if its relative
$SUMMARY$: 
If path is absolute, returns a malloced copy.
If path is relative, inserts the CWD in front of the relative path
and returns a mallocedd memory.

The caller must free the memory when no longer needed.
$ARGS$:
filespec:  the pathname
$RETURNS$:
ptr to mallocedd memory or NULL
/*================================================$SKIP$==*/
#endif

char * ExpandPath (
       const char * filespec)
{  /* $CODE$ */
    char tmpPath[MAXPATHLEN + 2];
    char * pathName;
    char * eos = NULL;
    const char * slash = NULL;

    /* test args */
    if (NULL == filespec) return NULL;

    /*** is the file absolute ***/
    /* if filespec begins with / then it is absolute */
    if (   (   MB_CUR_MAX == 1 
            || mblen(filespec, MB_CUR_MAX) == 1)/* 1st char is 1 byte */
         && *filespec == DIR_SLASH)  /* and its a / */
    {
       return strdup(filespec);        /* RETURN */
    }

    /*** otherwise, make it relative to the current directory ***/

    /* get user's current working directory */
    if (getcwd(tmpPath, MAXPATHLEN) == 0) return NULL; /* RETURN: error */

    /*** add a slash to end of path component, if needed ***/
    /* get end of the string */
    eos = tmpPath + strlen(tmpPath);
    /* get last slash */
    _DtMBStrrchr(tmpPath,DIR_SLASH,-1,&slash);
    if (slash != (eos-1)) /* is slash last char of path? */
    {
       *eos++ = DIR_SLASH; 
       *eos = EOS; 
    }

    /* make a malloc'd copy of the path with room to grow */
    pathName = malloc(sizeof(char) * 
                 (strlen(filespec) + (eos-tmpPath) + 5) ); /* 5: arbitrary */
    if (NULL == pathName) return NULL;		/* RETURN: error */

    /* build the absolute path */
    strcpy(pathName,tmpPath);
    strcat(pathName,filespec);

    return pathName;                             /* RETURN: found */
}  /* $END$ */

#if DOC
/*========================================================*/
$PFUNBEG$:  DeleteDbMem()
$1LINER$: Zeros out the db mem & frees it
$SUMMARY$: 
Zeros out the db mem & frees it
The xrmDb should have already been destroyed before calling
this function.
$ARGS$:
io_db:  db to delete
$RETURNS$:
/*================================================$SKIP$==*/
#endif

static
void DeleteDbMem(
      _DtXlateDb * io_db)
{ /*$CODE$*/
       /* zero out object mem and free it */
       (*io_db)->xrmDb = NULL;
       (*io_db)->initGuard = 0;
       free(*io_db);
       *io_db= NULL;
} /*$END$*/

#if DOC
/*========================================================*/
$IFUNBEG$:  _DtMBStrchr()
$1LINER$: Searches for a character in a multibyte string
$SUMMARY$: 
Returns in 'ret_ptr' the address of the first occurence of 'value'
in string s1.  Value may also be the end of string marker '\0'.
$ARGS$:
$RETURNS$:
-1  If found an invalid character.
 0  If found value in string s2
 1  If found the null byte character without finding 'value'.
    'ret_ptr' will also be null in this case.
/*================================================$SKIP$==*/
#endif

int _DtMBStrchr (
    const char * s1,
    int          value,
    int          max_len,
    const char * * ret_ptr )
{       /*$CODE$*/
    int          len;
    const char * p1;
    wchar_t      wcs;

    *ret_ptr = NULL;

    if (!s1 || *s1 == '\0')
        return 1;

    if (max_len == 1)
      {
        *ret_ptr = strchr (s1, value);
        if (*ret_ptr)
            return 0;
        return 1;
      }

    p1 = s1;
    while (*p1 != '\0')
      {
        len = mblen (p1, max_len);
        if (len == -1)
            return -1;
        if (len == 1)
          {
            if (*p1 == value)
              {
                *ret_ptr = p1;
                return 0;
              }
            p1++;
          }
        else
          {
            if (mbstowcs (&wcs, p1, 1) == value)
              {
                *ret_ptr = p1;
                return 0;
              }
            p1 += len;
          }
      }

    /* check for match on EOS */
    if (*p1 == value) *ret_ptr = p1;

    return ((*ret_ptr) ? 0 : 1);
}       /*$CODE$*/


#if DOC
/*========================================================*/
$IFUNBEG$:  _DtMBStrrchr()
$1LINER$: Searches for a character in a multibyte string
$SUMMARY$: 
Returns in 'ret_ptr' the address of the last occurence of 'value'
in string s1.  Value may also be the end of string marker '\0'.
$ARGS$:
$RETURNS$:
-1  If found an invalid character.
 0  If found value in string s2
 1  If found the null byte character without finding 'value'.
    'ret_ptr' will also be null in this case.
/*================================================$SKIP$==*/
#endif

int _DtMBStrrchr (
    const char *   s1,
    int            value,
    int            max_len,
    const char * * ret_ptr )
{        /*$CODE$*/
    int          len;
    const char * p1;
    wchar_t      wcs;

    *ret_ptr = NULL;

    if (!s1 || *s1 == '\0')
        return 1;

    if (max_len == 1)
      {
        *ret_ptr = strrchr (s1, value);
        if (*ret_ptr)
            return 0;
        return 1;
      }

    p1 = s1;
    while (*p1 != '\0')
      {
        len = mblen (p1, max_len);
        if (len == -1)
            return -1;
        if (len == 1)
          {
            if (*p1 == value)   *ret_ptr = p1;
            p1++;
          }
        else
          {
            if (mbstowcs (&wcs, p1, 1) == value)   *ret_ptr = p1;
            p1 += len;
          }
      }

    /* check for match on EOS */
    if (*p1 == value) *ret_ptr = p1;

    return ((*ret_ptr) ? 0 : 1);
}


#if DOC
/*========================================================*/
$PFUNBEG$: SetDebugModeState()
$1LINER$:  Checks db for debug mode and sets flag
$SUMMARY$: 
$WARNING$:
$ARGS$:
$RETURNS$:
/*================================================$SKIP$==*/
#endif

static
void SetDebugModeState(
       _DtXlateDb    dbRec)
{   /*$CODE$*/
    XrmValue value;
    char * str_type;
    if (XrmGetResource(dbRec->xrmDb,
           DEBUG_XLATE_NAME,DEBUG_XLATE_CLASS,&str_type,&value) == True)
       dbRec->debugMode = True;
}


#if DOC
/*========================================================*/
$PFUNBEG$: ReplaceMatchallSubex()
$1LINER$:  Replace matchall subexpression refs (e.g. ?1) with values
$SUMMARY$: 
If subexpressions are specified and referenced, the
routine assumes that the string pointed to by pStr was
allocated using malloc() and can be resized using realloc().
The value and size of pStr may be different after the call.
$WARNING$:
This routine assumes it is working on a stdvalue expression
(e.g. from the LHS of a spec), that uses only stdvalue strings
or matchall-style subexpression replacement specs, e.g. ?1.
$ARGS$:
$RETURNS$:
/*================================================$SKIP$==*/
#endif

static
void ReplaceMatchallSubex(
         char * *       pStr,
         regmatch_t *   subex,
         const char *   matchedStr)
{        /*$CODE$*/
    char * nxt = *pStr;
    char * old = *pStr;

    /* strip escapes out */
    for ( *nxt = *old;        /* xfer but don't advance */
          *old != EOS;
          *nxt = *old )       /* xfer but don't advance */
    {
        if ( *old == MATCHALL_CHAR )    /* if an escaped char follows */
        {
           /* if MATCHALL_CHAR is not followed by a digit, e.g. \1
              or no replacement values exist, ignore it */
           if (   NULL == subex 
               || isdigit(*(old+1)) == 0 ) 
           {
              old++;                 /* go past the escape char */
              *nxt++ = *old++;       /* keep just the char that was escaped
                                        and assign here to avoid tranlating
                                        that character, then move on to the 
                                        next one */
              continue;                      /* CONTINUE */
           }
           else  /* a value reference is being made */
           {  /* get the refNum and advance the ptr */
              int refNum, numLen;
              int newOff, oldOff;

              sscanf(++old,"%d%n", &refNum, &numLen);
              old += numLen;   /* move old ptr past the ref number */

/* printf("%d=%s\n", refNum, &matchedStr[subex[refNum].rm_so]); ** DBG */

              /* test for valid replacement */
              if (   refNum >= 0 
                  && refNum < MAXSPECQUARKS 
                  && subex[refNum].rm_so != -1)
              {
                 int    repLen;
                 int    strLen;
                 char * oldTmp;
                 char * newTmp;

                 newOff = nxt - *pStr;
                 oldOff = old - *pStr;
                 repLen = subex[refNum].rm_eo - subex[refNum].rm_so;
                 strLen = strlen(*pStr);

                 /* expand memory and reset pointers */
                 *pStr = realloc(*pStr,strLen+repLen+1);
                 if (NULL == *pStr) return;           /* RETURN */
                 nxt = *pStr + newOff;
                 old = *pStr + oldOff;

                 /* move rest back to leave room for the replacement value */
                 oldTmp = *pStr+strLen;      /* pts to old EOS */
                 newTmp = *pStr+strLen-(oldOff-newOff)+repLen;/*pts to new EOS*/
                 while (oldTmp >= old) *newTmp-- = *oldTmp--; 

                 /* replace the ref with a value but don't append EOS */
                 strncpy(nxt,&matchedStr[subex[refNum].rm_so],repLen);
                 nxt += repLen;  /* move new to end of replace string */
                 old += repLen - (oldOff-newOff);  
                                 /* move old to end of expanded old string */
              }  /* if valid replacement */
           }  /* if a replacement requested */
           continue;                              /* CONTINUE */
        }  /* if an escaped character */
        /* if survived all the checks, can advance to next char */
        nxt++;
        old++;
    }
}        /*$END$*/


#if DOC
/*========================================================*/
$PFUNBEG$: StripMetaAndReplaceEscSubex()
$1LINER$:  Strips off meta chars and replaces escaped subex (e.g. \1) values
$SUMMARY$:
Strip is performed in place if replaceValues is NULL.

If replaceValues are specified and referenced, the
routine assumes that the string pointed to by pStr was
allocated using malloc() and can be resized using realloc().
The value and size of pStr may be different after the call.

$WARNING$:
This routine assumes it is working on a value expression
(e.g. from the RHS of a spec), that uses meta chars and
regex(5)-style subexpression replacement specs.
$ARGS$:
$RETURNS$:
/*================================================$SKIP$==*/
#endif

static
void StripMetaAndReplaceEscSubex(
         char * *       pStr,
         const Boolean  keepEscChar,
         const char * * replaceValues)
{        /*$CODE$*/
    char * nxt = *pStr;
    char * old = *pStr;
    Boolean inQuote = False;

    /* strip escapes out */
    for ( *nxt = *old;        /* xfer but don't advance */
          *old != EOS;
          *nxt = *old )       /* xfer but don't advance */
    {
        if ( *old == ESCAPE_CHAR )    /* if an escaped char follows */
        {
           /* if ESCAPE_CHAR is not followed by a digit, e.g. \1
              or no replacement values exist, ignore it */
           if (   NULL == replaceValues 
               || keepEscChar
               || isdigit(*(old+1)) == 0 )
           {
              if (!keepEscChar) old++;       /* go past the escape char */
              else *nxt++ = *old++;          /* copy esc char over */
              *nxt++ = *old++;       /* keep just the char that was escaped
                                        and assign here to avoid tranlating
                                        that character, then move on to the
                                        next one */
              continue;                      /* CONTINUE */
           }
           else  /* a value reference is being made */
           {  /* get the refNum and advance the ptr */
              int refNum, numLen;
              int newOff, oldOff;

              sscanf(++old,"%d%n", &refNum, &numLen);
              old += numLen;   /* move old ptr past the ref number */

/*  printf("%x=%s\n", replaceValues[refNum], replaceValues[refNum]); ** DBG */

              /* test for valid replacement */
              if (   refNum >= 0
                  && refNum < MAXSPECQUARKS
                  && replaceValues[refNum] != NULL)
              {
                 int    repLen;
                 int    strLen;
                 char * oldTmp;
                 char * newTmp;

                 newOff = nxt - *pStr;
                 oldOff = old - *pStr;
                 repLen = strlen(replaceValues[refNum]);
                 strLen = strlen(*pStr);
                 /* expand memory and reset pointers */
                 *pStr = realloc(*pStr,strlen(*pStr)+repLen+1);
                 if (NULL == *pStr) return;           /* RETURN */
                 nxt = *pStr + newOff;
                 old = *pStr + oldOff;

                 /* move rest back to leave room for the replacement value */
                 oldTmp = *pStr+strLen;      /* pts to old EOS */
                 newTmp = *pStr+strLen-(oldOff-newOff)+repLen;/*pts to new EOS*/
                 while (oldTmp >= old) *newTmp-- = *oldTmp--;

                 /* replace the ref with a value but don't append EOS */
                 strncpy(nxt,replaceValues[refNum],repLen);
                 nxt += repLen;  /* move new to end of replace string */
                 old += repLen - (oldOff-newOff);
                                 /* move old to end of expanded old string */
              }  /* if valid replacement */
           }  /* if a replacement requested */
           continue;                              /* CONTINUE */
        }  /* if an escaped character */
        else
        {   /* a non-escaped char; make further checks */
            if ( *old == COMMENT_CHAR )
            {
               *old = EOS;  /* end the string */
               continue;                          /* CONTINUE */
            }
            else if ( *old == QUOTE_CHAR )
            {
               if ( !inQuote) inQuote = True;  /* start quote */
               else inQuote = False;           /* end quote */
               old++;     /* go to next char */
               continue;                          /* CONTINUE */
            }
            else if ( !inQuote && isspace(*old) )
            {
               old++;     /* skip the space */
               continue;                          /* CONTINUE */
            }
        }   /* else non-escaped char */

        /* if survived all the checks, can advance to next char */
        nxt++;
        old++;
    }
}        /*$END$*/

/*========================================================*/
/*============== Private Xlate routines ==================*/
/*========================================================*/


#if DOC
/*========================================================*/
$PFUNBEG$: PrintDbEntry()
$1LINER$:  Prints a db entry to stdout
$SUMMARY$: 
$ARGS$:
quarks:  NULLQUARK-terminated list of quarks for the LHS of the entry
value:   value of the RHS of the entry
$RETURNS$:
/*================================================$SKIP$==*/
#endif
static
void PrintDbEntry(
       XrmQuarkList quarks,
       XrmValue *   value)
{        /*$CODE$*/
   char *   str;
   XrmQuark quark;

   /* print the entry */
   quark=*quarks;
   while ( quark != NULLQUARK )
   {
      str = XrmQuarkToString(quark);
      fprintf(stderr,"%s", str);
      quark = *(++quarks);
      if (quark != NULLQUARK) fprintf(stderr,".");
   }
   fprintf(stderr,":%s\n",value->addr);
}        /*$END$*/


#if DOC
/*========================================================*/
$PFUNBEG$: strCaseiCmp
$1LINER$:  case insensitive string comparison
$SUMMARY$: 
Rolled my own because strcasecmp() not available on
all platforms.
$ARGS$:
$RETURNS$:
True:  strings match
False: they do not
/*================================================$SKIP$==*/
#endif

static
Boolean strCaseiCmp(
       const char * str1,
       const char * str2)
{        /*$CODE$*/
   /* if ( !str1 || !str2 ) return False; *//* unneeded performance hit */
   while ( *str1 && *str2 )
     if ( tolower(*str1++) != tolower(*str2++) )  return False;
   return (*str1 == *str2);
}        /*$END$*/

#if DOC
/*========================================================*/
$PFUNBEG$: CheckForMatchall()
$1LINER$:  Matches search pattern to spec data
$SUMMARY$: 
$ARGS$:
$RETURNS$:
/*================================================$SKIP$==*/
#endif

static
Boolean CheckForMatchall(
           __DtXlateSrchData * srchData, 
           const char *      matchallString,
           const char *      matchingString)
{        /*$CODE$*/
   int refNum = -1;
   int score = 0;

   /* test for a match all */
   if (! (   matchallString[0] == MATCHALL_CHAR 
          && (   matchallString[1] == EOS
              || sscanf(matchallString+1,"%d", &refNum) == 1) ) )
      return False;        /* RETURN: syntax error or non-matchall */

   /* matchall occurred; save the matching string if valid ref num */
   if (   refNum != -1 
       && refNum >= 0 
       && refNum < XtNumber(srchData->curSpecRefs) )
   {
      /* Don't store the string if it is just a matchall */
      /* This allows a replacement ref to be deleted on a match 
         for which there is no replacement value. */

      if (   matchingString[0] == MATCHALL_CHAR
          && matchingString[1] == EOS )
      {
         srchData->curSpecRefs[refNum] = NULL;
         /* don't add to score for a matchall with no replacement value */
      }
      else
      {
         /* recall: string not owned by curSpecRefs */
         srchData->curSpecRefs[refNum] = matchingString;
         score = 1;
      }
   } 
   else  /* not a value reference; just determine if a plain match */
   {
      if (   matchingString[0] == MATCHALL_CHAR
          && matchingString[1] == EOS )
          score = 1;      /* a plain matchall matches a plain matchall */
   }

   /* if a perfect matchall match, bump the score */
   srchData->curScore += score;

   return True;        /* RETURN: matchall */
}        /*$END$*/


#if DOC
/*========================================================*/
$PFUNBEG$: CheckSearchPlatformMatchesSpec()
$1LINER$:  Matches search pattern to spec data
$SUMMARY$: 
$ARGS$:
$RETURNS$:
/*================================================$SKIP$==*/
#endif

static
Boolean CheckSearchPlatformMatchesSpec(
           __DtXlateSrchData * srchData, 
           XrmQuark          specPlatformQuark)
{        /*$CODE$*/
   /* CheckForMatchall stores away the matching string if of form ?n */
   if (srchData->platformQuark != NULLQUARK)
   {
      char * specStr = XrmQuarkToString(specPlatformQuark);
      /* CheckForMatchall incs score if appropriate */
      if (CheckForMatchall(srchData,specStr,srchData->platformStr) == True)
         return True;            /* RETURN: platform matches */
   }
   if (srchData->platformQuark == specPlatformQuark)
   {
      srchData->curScore += 2;   /* perfect match better than matchall match */
      return True;               /* RETURN: platform matches */
   }
   return False;                 /* RETURN: platform doesnt match */
}        /*$END$*/


#if DOC
/*========================================================*/
$PFUNBEG$: CheckSearchVerMatchesSpec()
$1LINER$:  Matches search pattern to spec data
$SUMMARY$: 
$ARGS$:
$RETURNS$:
/*================================================$SKIP$==*/
#endif

static
Boolean CheckSearchVerMatchesSpec(
           __DtXlateSrchData * srchData, 
           XrmQuark          specVersionQuark)
{        /*$CODE$*/
   const char * numStr = XrmQuarkToString(specVersionQuark);
   int          lowerBnd = 0;
   int          upperBnd = INT_MAX;
   int          score = 0;

   if (   srchData->version == MATCHALL_VER
       || CheckForMatchall(srchData,numStr,srchData->verStr) == True )
      return True;          /* RETURN; matchall specified */

   /*** sscanf()-based parsing ***/
   /* note that the (score=x) is an assignment, not a compare */
   if (   (score=1) && sscanf(numStr,"%d-%d", &lowerBnd, &upperBnd) != 2
       && (score=1) && sscanf(numStr,"%d+", &lowerBnd) != 1
       && (score=2) && sscanf(numStr,"%d", &lowerBnd) != 1 )
       return False;        /* RETURN: syntax error */

   if ( lowerBnd > srchData->version || upperBnd < srchData->version )
       return False;        /* RETURN: version doesnt match */

   srchData->curScore += score;
   return True;             /* RETURN: version matches */
}        /*$END$*/



#if DOC
/*========================================================*/
$PFUNBEG$: CheckSearchOperMatchesSpec()
$1LINER$:  Matches search pattern to spec data
$SUMMARY$: 
$ARGS$:
srchData:            state of the search
specOperationQuark:  quark for the operation specification string
$RETURNS$:
True:  if srchData->operation is found in specOperation string
       or the specOperation string is a match all
False: if not
/*================================================$SKIP$==*/
#endif

static
Boolean CheckSearchOperMatchesSpec(
           __DtXlateSrchData * srchData, 
           XrmQuark          specOperationQuark)
{        /*$CODE$*/
   const char * opStr = XrmQuarkToString(specOperationQuark);
   int          hitLen;
   const char * hit;
   const char * remainingOps;

   if (   srchData->operStr == NULL
       || CheckForMatchall(srchData,opStr,srchData->operStr) == True
       || CheckForMatchall(srchData,srchData->operStr,opStr) == True )
      return True;          /* RETURN; matchall specified */

   /* quark compare search */
   if ( specOperationQuark == srchData->operQuark )
       goto matched;

   /*** strstr-based search ***/
   hitLen = srchData->operStrLen;
   remainingOps = opStr;
   do 
   {
      /* look for operation in remainingOps */
      hit = strstr(remainingOps,srchData->operStr);
      /* see if the hit is on a complete token */
      if (   NULL != hit
          && (hit == remainingOps || *(hit-1) == OPER_SEPARATOR)
          && (hit[hitLen] == EOS || hit[hitLen] == OPER_SEPARATOR) )
      {
matched:
         srchData->curScore += 2; /*perfect match better than a matchall match*/
         return True;             /* RETURN: operation matches */
      }
   } while(hit != NULL && *(remainingOps = hit+1) != EOS);

   return False;        /* RETURN: no match on operation */
}        /*$END$*/



#if DOC
/*========================================================*/
$PFUNBEG$: CheckSearchDirOpToStdMatchesSpec()
$1LINER$:  Matches search pattern to spec data
$SUMMARY$: 
$ARGS$:
$RETURNS$:
/*================================================$SKIP$==*/
#endif

static
Boolean CheckSearchDirOpToStdMatchesSpec(
           __DtXlateSrchData * srchData, 
           XrmQuark          specDirectionQuark)
{        /*$CODE$*/
   int          score = 0;
   __DtXlateType type = __DtXLATE_TYPE_NONE;

   if (specDirectionQuark == srchData->inhibitQuark) 
       return False;  /* RETURN: no match */

   /* Note that the type and score expressions are assignments */
   if (! (   (   (type=__DtXLATE_TYPE_REGEX) 
              && (specDirectionQuark == srchData->lessThanQuark))
          || (   (type=__DtXLATE_TYPE_PURE)
              && (score=1) 
              && (specDirectionQuark == srchData->equalsToQuark)) 
          || (   (type=__DtXLATE_TYPE_CONTAINS)
              && (score=1) 
              && (specDirectionQuark == srchData->containsQuark)) ) )
       return False;        /* RETURN: no match */

   srchData->curScore += score;
   srchData->curTransType = type;
   return True;             /* RETURN: direction matches */
}        /*$END$*/


#if DOC
/*========================================================*/
$PFUNBEG$: CheckSearchDirStdToOpMatchesSpec()
$1LINER$:  Matches search pattern to spec data
$SUMMARY$: 
$ARGS$:
$RETURNS$:
/*================================================$SKIP$==*/
#endif

static
Boolean CheckSearchDirStdToOpMatchesSpec(
           __DtXlateSrchData * srchData, 
           XrmQuark          specDirectionQuark)
{        /*$CODE$*/
   int          score = 0;
   __DtXlateType type = __DtXLATE_TYPE_NONE;

   if (specDirectionQuark == srchData->inhibitQuark) 
       return False;  /* RETURN: no match */

   /* Note that the type and score expressions are assignments */
   if (! (   (   (type=__DtXLATE_TYPE_REGEX) 
              && (specDirectionQuark == srchData->moreThanQuark))
          || (   (type=__DtXLATE_TYPE_PURE)
              && (score=1) 
              && (specDirectionQuark == srchData->equalsToQuark))
          || (   (type=__DtXLATE_TYPE_CONTAINS)
              && (score=1) 
              && (specDirectionQuark == srchData->containsQuark)) ) )
       return False;        /* RETURN: no match */

   srchData->curScore += score;
   srchData->curTransType = type;
   return True;             /* RETURN: direction matches */
}        /*$END$*/



#if DOC
/*========================================================*/
$PFUNBEG$: CheckSearchStdValueMatchesSpec()
$1LINER$:  Matches search pattern to spec data
$SUMMARY$: 
$ARGS$:
$RETURNS$:
/*================================================$SKIP$==*/
#endif

static
Boolean CheckSearchStdValueMatchesSpec(
           __DtXlateSrchData * srchData, 
           XrmQuark *        specStdValueQuarks)
{        /*$CODE$*/
   int        score = 0;
   int        unmatched = 0;
   XrmQuark * patQuarks;

   /* walk through all available quarks */
   for ( patQuarks = srchData->stdValueQuarks;
         *specStdValueQuarks != NULLQUARK && *patQuarks != NULLQUARK;
         specStdValueQuarks++, patQuarks++ )
   { 
      char * specStr = XrmQuarkToString(*specStdValueQuarks);
      char * patStr = XrmQuarkToString(*patQuarks);
      if (    CheckForMatchall(srchData,specStr,patStr) == True 
           || CheckForMatchall(srchData,patStr,specStr) == True )
         continue;     /* no score for a matchall */

      /* is not exact match, match fails */
      /* be case insensitive when comparing standard values */
      if (    *patQuarks != *specStdValueQuarks
           && strCaseiCmp(specStr,patStr) == False )
          return False;              /* RETURN: no match */

      /* one more match--increase score, go to next */
      score++;
   }

   /* find out how many stdValue fields were left unmatched */
   for ( unmatched = 0;
         *specStdValueQuarks != NULLQUARK;
         specStdValueQuarks++ ) 
       { unmatched++; }

   /* Score is combo of the number matched - the number unmatched 
      and not counting the number matchalls that coincided with
      the search pattern.   This technique allows the spec for
      val1 to be at "better" match than the one for val2, and
      val2 to be a better match than val3, and val3 to be a better
      match than val4.
         .a.std     : val1          querypattern = a.std
         .?.std     : val2          querypattern = a.std
         .?1.std    : \\1val3       querypattern = a.std
         .a.std     : val4          querypattern = ?.std
         .?.std     : val5          querypattern = ?.std
         .?1.std    : \\1val6       querypattern = ?.std

         .a.std.?   : val4          querypattern = a.std
         .?.std.?   : val5          querypattern = a.std
         .?.std.?1  : \\1val5       querypattern = a.std
         .?1.std.?  : \\1val5       querypattern = a.std
         .?1.std.?2 : \\1\\2val5    querypattern = a.std
         .a.std.?   : val4          querypattern = a.std.b
         .?.std.?   : val5          querypattern = a.std.b
         .?.std.?1  : \\1val5       querypattern = a.std.b
         .?1.std.?  : \\1val5       querypattern = a.std.b
         .?1.std.?2 : \\1\\2val5    querypattern = a.std.b
   */
   srchData->curScore += score + MAXSTDQUARKS - unmatched;

   return True;             /* RETURN: direction matches */
}        /*$END$*/



#if DOC
/*========================================================*/
$PFUNBEG$: CheckSearchOpValueMatchesSpec()
$1LINER$:  Matches search pattern to spec data
$SUMMARY$: 
$ARGS$:
$RETURNS$:
/*================================================$SKIP$==*/
#endif

static
Boolean CheckSearchOpValueMatchesSpec(
           __DtXlateSrchData * srchData, 
           const char *        specOpValue)
{        /*$CODE$*/
    char       opValBuf[MAXRHSSIZE]; /* max supported RHS size */
    char *     pOpValBuf = opValBuf;  /* need this for StripMeta... call */
    size_t     opValLen;
    Boolean    matches = False;

    /* copy value to mutable memory */
    strncpy(opValBuf,specOpValue,sizeof(opValBuf));
    opValBuf[sizeof(opValBuf)-1] = EOS;
    opValLen = strlen(opValBuf);

    /* depending on the translation type of the spec, do a 
       regexex match of the spec value pattern to the search 
       value or do a pure match */
    if (srchData->curTransType == __DtXLATE_TYPE_REGEX)
    {
#if defined(sun)
        char *     ex = NULL;

        /* True: leave escape char in place */
        StripMetaAndReplaceEscSubex(&pOpValBuf,True,NULL);

        /* we need to use regexex to pattern match */
        /* and we need to save of the reference value matches */
        if (   (ex = compile(opValBuf,NULL,NULL)) != NULL
            && advance(srchData->opValue,ex) != 0)
        {
            int matchSize;
            int subExCnt = nbra;  /* Sun global for advance() */
            matches = True;       /* if got this far */

            /* need due to bug in advance()--operation doesn't meet documentation */
            if (NULL == loc1) loc1=(char *)srchData->opValue;

            /* inc score by the size of the match after 
               scaling for the maximum possible match size */
            matchSize = loc2 - loc1; /*loc[12] are Sun globals for advance()*/
            if (matchSize < 0 || matchSize >= sizeof(opValBuf)) matchSize = 0;

            /* NOTE: this scoring code should be identical in the 
               Sun-specific and non-Sun code blocks */
            if (matchSize == strlen(srchData->opValue))
            {
               /* if the matchSize is the length of srchData->opValue,
                  then we have a complete match.  In this case, use the
                  specificity of the pattern to pick the best match */
               /* NOTE: opValLen is a crude measure of specificity.  
                  A better measure would be to count the number of
                  literals/ranges that matched exactly.  When doing this,
                  a perfect match without regex syntax should rank higher
                  than a perfect match with regex syntax.  This is one
                  area where the current algorithm breaks.  For example:
                     opValue=23, pat1=23, pat2=[0-9]3.
                  Both patterns match and pat1 is a better match,
                  but not with the current length-based algorithm. */
               /* NOTE: this formula does not advance the score
                  to sizeof(opValBuf) for a perfect match.  Other match 
                  formulas use sizeof(opValBuf) as the max value
                  to indicate a perfect match. */
               srchData->curScore += matchSize + opValLen;
            }
            else
            {
               /* if its not a complete match, inc score by match size */
               srchData->curScore += matchSize;
            }

            /* put sub expression matching stuff in srchData->curSubEx */
            for( ; nbra > 0; nbra-- )
            {
                srchData->curSubEx[nbra].rm_so = braslist[nbra-1] - loc1;
                srchData->curSubEx[nbra].rm_eo = braelist[nbra-1] - loc1;
            }
        }
        if (ex) free(ex);
#else
        regex_t    re;

        /* True: leave escape char in place */
        StripMetaAndReplaceEscSubex(&pOpValBuf,True,NULL);

        /* we need to use regexex to pattern match */
        /* and we need to save of the reference value matches */
        if (   regcomp(&re,opValBuf,0) == 0
            && regexec(&re,srchData->opValue,
                   XtNumber(srchData->curSubEx),srchData->curSubEx,0) == 0)
        {
            int matchSize;
            matches = True;       /* if got this far */

            /* inc score by the size of the match after 
               scaling for the maximum possible match size */
            matchSize = srchData->curSubEx[0].rm_eo - 
                                               srchData->curSubEx[0].rm_so;

            /* NOTE: this scoring code should be identical in the 
               Sun-specific and non-Sun code blocks */
            if (matchSize == strlen(srchData->opValue))
            {
               /* if the matchSize is the length of srchData->opValue,
                  then we have a complete match.  In this case, use the
                  specificity of the pattern to pick the best match */
               /* NOTE: opValLen is a crude measure of specificity.  
                  A better measure would be to count the number of
                  literals/ranges that matched exactly.  When doing this,
                  a perfect match without regex syntax should rank higher
                  than a perfect match with regex syntax.  This is one
                  area where the current algorithm breaks.  For example:
                     opValue=23, pat1=23, pat2=[0-9]3.
                  Both patterns match and pat1 is a better match,
                  but not with the current length-based algorithm. */
               /* NOTE: this formula does not advance the score
                  to sizeof(opValBuf) for a perfect match.  Other match 
                  formulas use sizeof(opValBuf) as the max value
                  to indicate a perfect match. */
               srchData->curScore += matchSize + opValLen;
            }
            else
            {
               /* if its not a complete match, inc score by match size */
               srchData->curScore += matchSize;
            }

            /* sub expression matching stuff already in srchData->curSubEx */
        }
        regfree(&re);
#endif
    }
    else /* (srchData->curTransType == __DtXLATE_TYPE_PURE || __DtXLATE_TYPE_CONTAINS */
    {
        char * opValueInBuf;

        /* False: strip escape char as well */
        StripMetaAndReplaceEscSubex(&pOpValBuf,False,NULL);
        matches = (strcmp(srchData->opValue,opValBuf) == 0);

        /* if matches, inc score to show a perfect match (max poss value) */
        if (matches) srchData->curScore += sizeof(opValBuf);

        /* don't test for contains if a perfect match or a pure match spec */
        if (matches || srchData->curTransType == __DtXLATE_TYPE_PURE)
           return matches;                            /* RETURN */

        /*  (srchData->curTransType == __DtXLATE_TYPE_CONTAINS) */
        /* is opValue contained in opValBuf? */
        /* is opValBuf contained in opValue? */
        opValueInBuf = NULL;
        matches =    (opValBuf[0] != EOS && srchData->opValue[0] != EOS) 
                  && ((opValueInBuf=strstr(opValBuf,srchData->opValue)) != NULL
                      || strstr(srchData->opValue,opValBuf) != NULL);

        /* if matches, inc score to show a contains match */
        if (matches) 
        {
           if (opValueInBuf) srchData->curScore += strlen(srchData->opValue);
           else srchData->curScore += strlen(opValBuf);
        }
    }
    return matches;
}        /*$END$*/




#if DOC
/*========================================================*/
$PFUNBEG$: FindStdToOpMatchCB()
$1LINER$:  Matches std value of entry to search pattern; gets op value
$SUMMARY$: 
$ARGS$:
$RETURNS$:
/*================================================$SKIP$==*/
#endif

static
Bool FindStdToOpMatchCB(
            XrmDatabase *       database,
            XrmBindingList      bindings,
            XrmQuarkList        quarks,
            XrmRepresentation * type,
            XrmValue *          value,
            XPointer            client_data)
{       /*$CODE$*/
   __DtXlateSrchData * srchData = (__DtXlateSrchData *) client_data;

   /* always begin scoring from 0 and replacement values at NULL */
   srchData->curScore = 0;
   memset(srchData->curSpecRefs,0, sizeof(srchData->curSpecRefs));

#ifdef DBG_MATCHING
   fprintf(stderr,"FindStdToOpMatch: "); PrintDbEntry(quarks,value);
#endif

   /* check for a match */
   if ( CheckSearchPlatformMatchesSpec(srchData, 
                      quarks[PLATFORM_QUARK]) == False) 
      return False;      /* continue enumeration */

#ifdef DBG_MATCHING
   fprintf(stderr,"platform matches\n");
#endif

   if ( CheckSearchVerMatchesSpec(srchData, 
                      quarks[VERSION_QUARK]) == False) 
      return False;      /* continue enumeration */

#ifdef DBG_MATCHING
   fprintf(stderr,"ver matches\n");
#endif

   if ( CheckSearchOperMatchesSpec(srchData, 
                      quarks[OPERS_QUARK]) == False) 
      return False;      /* continue enumeration */

#ifdef DBG_MATCHING
   fprintf(stderr,"oper matches\n");
#endif

   if ( CheckSearchDirStdToOpMatchesSpec(srchData, 
                      quarks[DIRECTION_QUARK]) == False) 
      return False;      /* continue enumeration */

#ifdef DBG_MATCHING
   fprintf(stderr,"kind matches\n");
#endif

   /* now check for std value match and, if it is
      the best match so far, record the value */
   if ( CheckSearchStdValueMatchesSpec(srchData, 
                       &quarks[FIRSTSTD_QUARK]) == False )
   {
      if (srchData->db->debugMode)
      {
         fprintf(stderr,"mismatch ");
         PrintDbEntry(quarks,value);
      }
   
      return False;      /* continue enumeration */
   }

#ifdef DBG_MATCHING
   fprintf(stderr,"std value matches\n");
#endif

   if (srchData->db->debugMode)
   {
      fprintf(stderr,"match (%d) ",srchData->curScore);
      PrintDbEntry(quarks,value);
   }
   
   /* we have a match! (we made it through all match checks) */
   /* is it better than or same as any earlier match? */
   if ( srchData->curScore >= srchData->bestScore )
   {
      /* recall that all strings are owned by Xrm==>no need to free them */
      srchData->bestScore = srchData->curScore;
      srchData->bestTransType = srchData->curTransType;
      memcpy(srchData->bestSpecRefs,srchData->curSpecRefs,
                sizeof(srchData->bestSpecRefs)); /* no array assignment in C */
      srchData->opValue = value->addr;
   }

   return False;    /* continue enumeration */
}       /*$END$*/


#if DOC
/*========================================================*/
$PFUNBEG$: FindOpToStdMatchCB()
$1LINER$:  Matches op value of entry to search pattern; gets std value
$SUMMARY$: 
$ARGS$:
$RETURNS$:
/*================================================$SKIP$==*/
#endif

static
Bool FindOpToStdMatchCB(
            XrmDatabase *       database,
            XrmBindingList      bindings,
            XrmQuarkList        quarks,
            XrmRepresentation * type,
            XrmValue *          value,
            XPointer            client_data)
{       /*$CODE$*/
   __DtXlateSrchData * srchData = (__DtXlateSrchData *) client_data;

   /* always begin scoring from 0 and subexpression indices at -1 */
   srchData->curScore = 0;
   memset(srchData->curSubEx,-1, sizeof(srchData->curSubEx));

#ifdef DBG_MATCHING
   fprintf(stderr,"FindOpToStdMatch: "); PrintDbEntry(quarks,value);
#endif

   /* check for a match */
   if ( CheckSearchPlatformMatchesSpec(srchData, 
                      quarks[PLATFORM_QUARK]) == False) 
      return False;      /* continue enumeration */

#ifdef DBG_MATCHING
   fprintf(stderr,"platform matches\n");
#endif

   if ( CheckSearchVerMatchesSpec(srchData, 
                      quarks[VERSION_QUARK]) == False) 
      return False;      /* continue enumeration */

#ifdef DBG_MATCHING
   fprintf(stderr,"ver matches\n");
#endif

   if ( CheckSearchOperMatchesSpec(srchData, 
                      quarks[OPERS_QUARK]) == False) 
      return False;      /* continue enumeration */

#ifdef DBG_MATCHING
   fprintf(stderr,"oper matches\n");
#endif

   if ( CheckSearchDirOpToStdMatchesSpec(srchData, 
                      quarks[DIRECTION_QUARK]) == False) 
      return False;      /* continue enumeration */

#ifdef DBG_MATCHING
   fprintf(stderr,"kind matches\n");
#endif

   /* now check for op value match and, if it is
      the best match so far, record the std value */
   if ( CheckSearchOpValueMatchesSpec(srchData, 
                                   value->addr) == False )
   {
      if (srchData->db->debugMode)
      {
         fprintf(stderr,"mismatch ");
         PrintDbEntry(quarks,value);
      }
   
      return False;      /* continue enumeration */
   }

#ifdef DBG_MATCHING
   fprintf(stderr,"op value matches\n");
#endif

   if (srchData->db->debugMode)
   {
      fprintf(stderr,"match (%d) ",srchData->curScore);
      PrintDbEntry(quarks,value);
   }

   /* we have a match! (we made it through all match checks) */
   /* is it better than or same as any earlier match? */
   if ( srchData->curScore >= srchData->bestScore )
   {
      XrmQuarkList stdQ;
      XrmQuarkList curQ;
      /* recall that all strings are owned by Xrm==>no need to free them */
      srchData->bestScore = srchData->curScore;
      srchData->bestTransType = srchData->curTransType;
      memcpy(srchData->bestSubEx,srchData->curSubEx,
                sizeof(srchData->bestSubEx)); /* no array assignment in C */
      /* store off the std value of the best match */
      stdQ = srchData->stdValueQuarks;
      curQ = &quarks[FIRSTSTD_QUARK];
      while ( (*stdQ = *curQ) != NULLQUARK ) stdQ++, curQ++;
   }

   return False;    /* continue enumeration */
}       /*$END$*/


#if DOC
/*========================================================*/
$PFUNBEG$: DoCommonSrchDataPrep
$1LINER$:  Prep srchData to search for a pattern
$SUMMARY$: 
$ARGS$:
$RETURNS$:
/*================================================$SKIP$==*/
#endif
static
void DoCommonSrchDataPrep(
      __DtXlateSrchData * srchData,
      _DtXlateDb          db,
      const char *        platform,
      const int           version,
      const char *        operation)
{       /*$CODE$*/
   int       verNum = version;       /* for lint */

   /* zero the search data */
   memset(srchData,0,sizeof(__DtXlateSrchData));

   /* set the db */
   srchData->db = db;
   
   /* build filter list for enumerating the db */
   if (verNum < MATCHALL_VER) verNum = MATCHALL_VER;
   srchData->platformStr = platform;
   srchData->platformQuark = (platform?XrmStringToQuark(platform):NULLQUARK);
   srchData->version = verNum;
   sprintf(srchData->verStr,"%d",verNum);
   srchData->operStr = operation;
   srchData->operStrLen = strlen(operation);
   srchData->operQuark = (operation ? XrmStringToQuark(operation) : NULLQUARK);
   srchData->lessThanQuark = XrmStringToQuark(LESSTHAN_STR);
   srchData->equalsToQuark = XrmStringToQuark(EQUALS_STR);
   srchData->containsQuark = XrmStringToQuark(CONTAINS_STR);
   srchData->moreThanQuark = XrmStringToQuark(MORETHAN_STR);
   srchData->inhibitQuark = XrmStringToQuark(INHIBIT_STR);
}       /*$END$*/

/*========================================================*/
/*=============== Public Xlate routines ==================*/
/*========================================================*/



#if DOC
/*========================================================*/
$FUNBEG$:  _DtXlateOpenDb()
$1LINER$:   Open a translation database
$SUMMARY$: 
Opens a translation resource database and returns a
reference to it in ret_db.

Initializes the _DtXlateDb object to ready for use.
If an error occurs, _DtXlateDb is set to NULL.
$ARGS$:
$RETURNS$:
0:  no error occurred
-1: if ret_db is NULL
    if XrmGetFileDatabase() failed on databaseName
    if malloc fails to alloc a db structure
/*================================================$SKIP$==*/
#endif

int  _DtXlateOpenDb(
       const char *  databaseName,
       _DtXlateDb *  ret_db)
{       /*$CODE$*/
    XrmDatabase xrmDb;
    __DtXlateDbRec * dbRec = NULL;
    char * path = NULL;

    if(NULL == ret_db) return -1;        /* RETURN */
    *ret_db = NULL;

    /* Do NOT check for whether *ret_db is already
       a valid db; this is none of our affair. */

    /* get an absolute path for the file */
    path = ExpandPath(databaseName);

    if (NULL == path) return -1;        /* RETURN */

    xrmDb = XrmGetFileDatabase(path);
    if (NULL == xrmDb) { free(path); return -1; } /* RETURN */

    /* alloc a db ref */
    dbRec = (__DtXlateDbRec *) calloc(1,sizeof(__DtXlateDbRec));
    if(NULL == dbRec) 
    {
       XrmDestroyDatabase(xrmDb); 
       free(path);
       return -1;                        /* RETURN */
    }

    /* and populate it */
    dbRec->xrmDb = xrmDb;
    dbRec->initGuard = INIT_OCCURRED;

    /* check for debug mode */
    SetDebugModeState(dbRec);

    if (dbRec->debugMode) 
       fprintf(stderr,"_DtXlateOpenDb: opened: %s; new db: %p\n",path, (void *)dbRec);

    *ret_db = dbRec;
    free(path);
    return 0;       /* RETURN */
}       /*$END$*/

#if DOC
/*========================================================*/
$FUNBEG$: _DtXlateOpenAndMergeDbs()
$1LINER$: Opens a translation database and merges with earlier dbs
$SUMMARY$: 
Opens a translation resource database and returns a
reference to it in ret_db.

Initializes the _DtXlateDb object to ready for use.
$ARGS$:
$RETURNS$:
0:  no error occurred
-1: if io_db is NULL
    if XrmGetFileDatabase() failed on databaseName
/*================================================$SKIP$==*/
#endif

int  _DtXlateOpenAndMergeDbs(
       const char *   databaseName,
       _DtXlateDb *   io_db)
{       /*$CODE$*/
    XrmDatabase xrmDb;
    char * path;
    int  ret;

    if(NULL == io_db) return -1;                /* RETURN */

    /* if a db has not yet been opened */
    if(   NULL == *io_db
       || (*io_db)->initGuard != INIT_OCCURRED 
       || (*io_db)->xrmDb == NULL)
    {
       ret = _DtXlateOpenDb(databaseName,io_db); /* RETURN */
       if ( (*io_db) && (*io_db)->debugMode) 
          fprintf(stderr,"_DtXlateOpenAndMergeDb: "
                      "used _DtXlateOpenDb to open first file\n");
       return ret;      /* RETURN */
    }
       
    if ( (*io_db) && (*io_db)->debugMode) 
       fprintf(stderr,"_DtXlateOpenAndMergeDb: "
                      "target file: %s; existing db: %p\n",databaseName, (void *) *io_db);

    /* a db has been opened, let's merge with it */

    /* get an absolute path for the file */
    path = ExpandPath(databaseName);

    if (NULL == path) goto Failed;     /* RETURN */

    xrmDb = XrmGetFileDatabase(path);
    if (NULL == xrmDb) goto Failed;    /* RETURN */

    /* merge and destroy xrmDb for me */
    XrmMergeDatabases(xrmDb,&(*io_db)->xrmDb);

    /* check for debug mode */
    SetDebugModeState(*io_db);

    if ((*io_db)->debugMode) 
       fprintf(stderr,"_DtXlateOpenAndMergeDb: "
                      "opened: %s; merged db: %p\n",path, (void *) *io_db);

    free(path);
    return 0;      /* RETURN */

Failed:
    if ( (*io_db) && (*io_db)->debugMode) 
       fprintf(stderr,"_DtXlateOpenAndMergeDb: open failed; file: %s\n", 
          (path ? path : (databaseName ? databaseName : "NULL") ) );
    if (path) free(path);
    return -1;      /* RETURN */
}       /*$END$*/

#if DOC
/*========================================================*/
$FUNBEG$: _DtXlateMergeDbs()
$1LINER$: Merges two open dbs into one and closes the merged-in db.
$SUMMARY$: 
Merges two databases into one and closes the merged db.

The io_dbToMerge database must be a valid translation database.  
The io_dbToMerge database is merged into the io_mergeIntoDb.  
The io_mergeIntoDb may either be invalid or valid.  If invalid,
the io_dbToMerge database is simply moved over to io_mergeIntoDb.
If io_mergeIntoDb is valid, the entries in the io_dbToMerge
database are merged into it and take precedence over entries in the
io_mergeIntoDb, and the io_dbToMerge database is closed.
$ARGS$:
io_dbToMerge:    database to merge into io_mergeIntoDb
io_mergeIntoDb:  database to hold merged result
$RETURNS$:
0:  no error occurred
-1: if io_dbToMerge or io_mergeIntoDb is NULL
    if *io_dbToMerge is NULL or uninitialized
/*================================================$SKIP$==*/
#endif

int  _DtXlateMergeDbs(
       _DtXlateDb *  io_dbToMerge,
       _DtXlateDb *  io_mergeIntoDb)
{       /*$CODE$*/

    /* check args */
    if(   NULL == io_mergeIntoDb 
       || NULL == io_dbToMerge
       || NULL == *io_dbToMerge
       || (*io_dbToMerge)->initGuard != INIT_OCCURRED 
       || (*io_dbToMerge)->xrmDb == NULL)
       return -1;                             /* RETURN */

    /* check for debug mode */
    if (   ((*io_mergeIntoDb) && (*io_mergeIntoDb)->debugMode) 
        || (*io_dbToMerge)->debugMode) 
       fprintf(stderr,"_DtXlateMergeDbs: "
                  "mergeIntoDb: %p; dbToMerge: %p\n", (void *) *io_mergeIntoDb, (void *) *io_dbToMerge);

    /* if db_mergeIntoDb has not yet been opened */
    if(   NULL == *io_mergeIntoDb
       || (*io_mergeIntoDb)->initGuard != INIT_OCCURRED 
       || (*io_mergeIntoDb)->xrmDb == NULL)
    {
       /* just move dbToMerge into mergeIntoDb */
       *io_mergeIntoDb = *io_dbToMerge;
       DeleteDbMem(io_dbToMerge);

       return 0;				/* RETURN */
    }
       
    /* merge and destroy io_dbToMerge->xrmDb for me */
    XrmMergeDatabases((*io_dbToMerge)->xrmDb,&(*io_mergeIntoDb)->xrmDb);
    DeleteDbMem(io_dbToMerge);

    /* check for debug mode */
    SetDebugModeState(*io_mergeIntoDb);

    if ((*io_mergeIntoDb)->debugMode) 
       fprintf(stderr,"merged db: %p\n", (void *) *io_mergeIntoDb);

    return 0;
}       /*$END$*/

#if DOC
/*========================================================*/
$FUNBEG$: _DtXlateOpenAllDbs()
$1LINER$: Open and merge all locale translation databases that can be found
$SUMMARY$:
DtXlateOpenAllDbs() locates all translation databases
present in the search paths directories.
$ARGS$:
searchPaths:  ':' separated list of directories
databaseName: name of the database file in those directories
ret_db:       the reference to the open database is stored here
$RETURNS$:
 0: at least one database was opened
-1: no database was opened
/*================================================$SKIP$==*/
#endif

int  _DtXlateOpenAllDbs(
         const char * searchPaths,
         const char * databaseName,
         _DtXlateDb * ret_db)
{       /*$CODE$*/
    const char * workStart = searchPaths;
    const char * separator = NULL;
    char         dbFile[MAXPATHLEN+1];
    int          ret = ~0;    /* all bits set */

    /* cycle through the paths, opening each one */
    do
    {
       int  workLen = 0;
       const char * slash = NULL;

       dbFile[0] = EOS;

       /* isolate the next part of the path */
       _DtMBStrchr (workStart, PATH_SEPARATOR, -1, &separator);
       if (NULL == separator) _DtMBStrchr (workStart, EOS, -1, &separator);
       if (NULL == separator) break;           /* BREAK */
       workLen = separator - workStart;  /* don't include +1 for EOS */

       /* copy over the path component */
       strncpy(dbFile,workStart,workLen);
       workStart = separator + 1;

       /* add a slash to end of path component, if needed */
       *(dbFile+workLen) = EOS;  /* add an EOS for _DtMBStrrchr to find */
       _DtMBStrrchr(dbFile,DIR_SLASH,-1,&slash);
       if (slash != dbFile+workLen-1) /* is slash last char of path? */
       {
         *(dbFile+workLen) = DIR_SLASH;
         workLen++;
       }

       /* append the filename and EOS */
       strcpy(dbFile+workLen,databaseName);

/*printf("Working on: %s\n", dbFile);  **DBG*/

       /* open and merge the database with previously opened dbs */
       /* by ANDing, we determine whether at least one call returned 0 */
       ret &= _DtXlateOpenAndMergeDbs(dbFile,ret_db);

    } while ( *separator != EOS );

    if (*ret_db && (*ret_db)->debugMode) 
       fprintf(stderr,"_DtXlateOpenAllDbs: completed\n"
                      "  srchpaths: %s; db file: %s\n",searchPaths,databaseName);

    return (ret == 0 ? 0 : -1);  /* ret != 0 ==> no db was opened */
}       /*$END$*/


#if DOC
/*========================================================*/
$FUNBEG$: _DtXlateCloseDb()
$1LINER$: Close an open translation database
$SUMMARY$:
_DtXlafteCloseDb() releases all memory associated with
the translation database.  Further use of the database
object is an error.
$ARGS$:
$RETURNS$:
 0:  database was valid and has been closed
-1:  invalid database pointer
/*================================================$SKIP$==*/
#endif

int _DtXlateCloseDb(
       _DtXlateDb * io_db)
{       /*$CODE$*/
     __DtXlateDbRec * dbRec;
     
     if(   NULL == io_db 
        || NULL == (dbRec = *io_db)        /* dbRec assigned */
        || dbRec->initGuard != INIT_OCCURRED) 
        return -1;                     /* RETURN */
 
     XrmDestroyDatabase(dbRec->xrmDb);

     if (dbRec->debugMode) fprintf(stderr,"_DtXlateCloseDb: %p\n", (void *) dbRec);

     /* zero out object mem and free it */
     DeleteDbMem(io_db);

     return 0;
}       /*$END$*/


#if DOC
/*========================================================*/
$FUNBEG$: _DbXlateStdToOpValue()
$1LINER$:  Translates a standardized spec to an operation-specific value
$SUMMARY$: 
Looks up the best translation of the standard value for an
operation and places a pointer to the translation string
at the location pointed to by ret_opValue.

The translated string was allocated using malloc() and
must be freed when no longer needed.

If ret_opValue is NULL, the function merely verifies that
a valid translation exists.
$ARGS$:
db:		a translation database
platform:	the platform string (see _DtXlateGetXlateEnv())
version:	the version number (see _DtXlateGetXlateEnv())
operation:	the operation of interest, e.g. "setlocale"
stdValue:	the standard value pattern
ret_opValue:	location where ptr to translated string is stored
ret_reserved:	reserved for future use
$RETURNS$:
 0: translation found
-1: invalid database (NULL ptr, not opened)
    no operation was specified
    query failed to find a match
/*================================================$SKIP$==*/
#endif

int _DtXlateStdToOpValue(
       _DtXlateDb        db,
       const char *      platform,
       const int         version,
       const char *      operation,
       const char *      stdValue,
       char * *          ret_opValue,
       void *            ret_reserved)
{       /*$CODE$*/
   __DtXlateSrchData srchData;
   XrmQuark  empty = NULLQUARK;

   if (   NULL == db 
       || db->initGuard != INIT_OCCURRED
       || NULL == operation
       || operation[0] == EOS)
       return -1;                                   /* RETURN error */

   /* prep srch data for search */
   DoCommonSrchDataPrep(&srchData,db,platform,version,operation);

   /* handle a rare case */
   if (NULL == stdValue)
   {
       if (db->debugMode) fprintf(stderr,"_DtXlateStdToOpValue: NULL std value\n");
       if (ret_opValue) *ret_opValue = NULL;
       return -1;                                  /* RETURN error */
   }

   /* build std value list for use during comparison */
   srchData.stdValueQuarks[0] = NULLQUARK;
   if (NULL != stdValue && stdValue[0] != EOS)
      XrmStringToQuarkList(stdValue,srchData.stdValueQuarks);

   if (db->debugMode)
      fprintf(stderr,"_DtXlateStdToOpValue: %s.%d.%s.%s: <op>\n",
          platform,version,operation,stdValue);

   /* scan through this Db looking for matches and put in search */
   XrmEnumerateDatabase(db->xrmDb,&empty,&empty,
              XrmEnumAllLevels, FindStdToOpMatchCB, (XPointer) &srchData);

   if (   srchData.opValue != NULL
       && srchData.bestTransType != __DtXLATE_TYPE_INHIBIT )
   {
       char * opValue;
       
       if (ret_opValue == NULL)
       {
          if (db->debugMode) fprintf(stderr,"translation exists\n");
          return 0;  /* RETURN: translation exists */
       }
       
       /* alloc the string to return */
       opValue = strdup(srchData.opValue);

       if (db->debugMode) fprintf(stderr,"raw opval:%s\n",opValue);

       /* do quote and escape removal and ref replacement in the opValue */
       if ( srchData.bestTransType == __DtXLATE_TYPE_REGEX )
          StripMetaAndReplaceEscSubex(&opValue,False,srchData.bestSpecRefs);
       else
          StripMetaAndReplaceEscSubex(&opValue,False,NULL);

       if (db->debugMode) fprintf(stderr,"op value:%s\n",opValue);

       *ret_opValue = opValue;
       return 0;                              /* RETURN: search successful */
   }
   return -1;                                /* RETURN: search failed */
}       /*$END$*/


#if DOC
/*========================================================*/
$FUNBEG$: _DbXlateOpToStdValue()
$1LINER$:  Translates an operation-specific value to a standardized one
$SUMMARY$: 
Looks up the best translation of the operation value for an
operation and places a pointer to the standard string
at the location pointed to by ret_stdValue.

The standard string was allocated using malloc() and
must be freed when no longer needed.

If ret_stdValue is NULL, the function merely verifies that
a valid translation exists.
$ARGS$:
db:		a translation database
platform:	the platform string (see _DtXlateGetXlateEnv())
version:	the version number (see _DtXlateGetXlateEnv())
operation:	the operation of interest, e.g. "setlocale"
opValue:	the operation-specific value pattern
ret_stdValue:	location where ptr to standard string is stored
ret_reserved:	reserved for future use
$RETURNS$:
 0: translation found
-1: invalid database (NULL ptr, not opened)
    no operation was specified
    query failed to find a match
/*================================================$SKIP$==*/
#endif

int _DtXlateOpToStdValue(
       _DtXlateDb        db,
       const char *      platform,
       const int         version,
       const char *      operation,
       const char *      opValue,
       char * *          ret_stdValue,
       void *            ret_reserved)
{       /*$CODE$*/
   __DtXlateSrchData srchData;
   XrmQuark  empty = NULLQUARK;
   char      lhs[MAXLHSSIZE];

   if (   NULL == db 
       || db->initGuard != INIT_OCCURRED
       || NULL == operation
       || operation[0] == EOS)
       return -1;                                   /* RETURN error */

   /* prep srch data for search */
   DoCommonSrchDataPrep(&srchData,db,platform,version,operation);

   /* after check on value, store op value for use during comparison */
   /* not meaningful to check for a NULL value */
   if (NULL == opValue)
   {
      if (db->debugMode) fprintf(stderr,"_DtXlateOpToStdValue: NULL op value\n");
      if (ret_stdValue) *ret_stdValue = NULL;
      return -1;                                   /* RETURN error */
   }
   srchData.opValue = opValue;

   if (db->debugMode)
      fprintf(stderr,"_DtXlateOpToStdValue: %s.%d.%s.<std>: %s\n",
          platform,version,operation,opValue);

   /* scan through this Db looking for matches and put in search */
   XrmEnumerateDatabase(db->xrmDb,&empty,&empty,
              XrmEnumAllLevels, FindOpToStdMatchCB, (XPointer) &srchData);

   if (   srchData.stdValueQuarks[0] != NULLQUARK
       && srchData.bestTransType != __DtXLATE_TYPE_INHIBIT )
   {
      XrmQuarkList stdQ;
      char * stdValue = lhs;
      int    stdValueLen = 0;

      if (ret_stdValue == NULL)
      {
         if (db->debugMode) fprintf(stderr,"translation exists\n");
         return 0;  /* RETURN: translation exists */
      }

      /* make a STDVALUE_SEPARATOR separated string out of the std data */
      for ( stdQ = srchData.stdValueQuarks;
            *stdQ != NULLQUARK;
            stdQ++ )
      {
         const char * str = XrmQuarkToString(*stdQ);
         int strLen;
         if (NULL == str || str[0] == EOS) continue;
         
         strLen = strlen(str);
         if (stdValue != lhs) 
             {*stdValue++ = STDVALUE_SEPARATOR; stdValueLen++; }
         if ((stdValueLen + strLen) > sizeof(lhs) )
             strLen = sizeof(lhs)-stdValueLen;
         strncpy(stdValue,str,strLen);
         stdValueLen += strLen;
         stdValue += strLen;
      }
      *stdValue = EOS;

      if (db->debugMode) fprintf(stderr,"raw stdval:%s\n",lhs);

      /* do quote and escape removal and ref replacement in the stdValue */
      stdValue = strdup(lhs);        /* reset stdValue ptr */
      if ( srchData.bestTransType == __DtXLATE_TYPE_REGEX )
          ReplaceMatchallSubex(&stdValue,srchData.bestSubEx,srchData.opValue);

      if (db->debugMode) fprintf(stderr,"std value:%s\n",stdValue);

      *ret_stdValue = stdValue;
      return 0;                              /* RETURN: search successful */
   }
   return -1;                                /* RETURN: search failed */
}       /*$END$*/



#if DOC
/*========================================================*/
$FUNBEG$: _DtXlateGetXlateEnv()
$1LINER$: Get the DtXlate compilation and execution environments.
$SUMMARY$:
_DtXlateGetXlateEnv() recovers the identity of the application
current platform, the version value of the application
execution environment, and the version value of the operating
system version for which DtXlateGetXlateEnv() was
compiled. These values can be used in formulating queries,
especially for the _DtXlateStdToOpValue() query.

The technique used by this routine is as follows.  Using
uname(2), the routine retrieves the sysname, release, and version
strings.  The sysname is used as the platform name.  The
release and version strings are concatenated in that order
and as treated below as the <op-rel-ver>.  An OpToStd translation
looks for a match to
    <sysname>.?.version.<.<std-version>: <op-rel-ver>
 or
    <sysname>.?.version.=.<std-version>: <op-rel-ver>

If no match is found, the next fallback position is to
get the specification with the same sysname and the highest
std-version integer value.  If no specifications exist for
that sysname, then sysname is set to the empty string and
std-version is -1.

ret_AppExecEnvPlatform should point to a character array at least 
_DtPLATFORM_MAX_LEN characters long.  The sysname is copied to it.

ret_AppExecEnvVersion is given the integer value of the
std-version recovered from the translation.

ret_XlateCompiledForOSVersion is given the integer value
determined by using the OSMajorVersion and OSMinorVersion
constants defined by the build environment of _DtXlate as follows:

#define _STR(V) #V
#define STR(V) _STR(V)
      sprintf(buf,"%s%s%s", STR(OSMAJORVERSION), 
                     nl_langinfo(RADIXCHAR), STR(OSMAJORVERSION));
      verNum = (int) (100.0 * atof(buf));

For example:
OSMAJORVERSION & OSMINORVERSION are compile-time constants
that must be defined as part of the build environment.
It is assumed that these constants are of the form:

   e.g. Sun 5.3
   #define OSMAJORVERSION        5
   #define OSMINORVERSION        3
   
   530 = (int) 100.0 * atof("5.3");
   
   e.g. HP-UX 8.09
   #define OSMAJORVERSION         8
   #define OSMINORVERSION         09
   
   809 = (int) 100.0 * atof("8.09");
   
Note that it may be necessary for the application to determine
the version number of an operation in some platform-specific
and operation-specific manner, for example using a library
version value.  In many cases, however, the O.S. version value 
for which _DtXlate was compiled will be sufficient
when identifying version numbers for standard development
environment libraries, such as libc.

$EXAMPLE$:
char * 
xlateStd2Op(_DtXlateDb db,Boolean runtimeOp, char * operation,char * stdVal)
{
   char platform[_DtPLATFORM_MAX_LEN];
   int  version;
   char * opVal = NULL;
   int    ret;

   if (runtimeOp)
      ret=_DtXlateGetXlateEnv(db,platform,&version,NULL);
   else
      ret=_DtXlateGetXlateEnv(db,platform,NULL,&version);
   if (ret == 0)
      _DtXlateStdToOpValue(db,platform,version,operation,stdVal,&opVal,NULL);
   return opVal;       /* will be NULL if error occurred */
}

$ARGS$:
ret_AppExecEnvPlatform:  pts to a string at least _DtPLATFORM_MAX_LEN long
                         that will hold the string uname(2) returns for sysname
ret_AppExecEnvVersion:   pts to an integer that will receive the platform
                         standardized version number, as determined by
                         a translation on uname(2) release+version.
ret_XlateCompiledForOSVersion:   pts to an integer that will receive the
                         operating system version for which _DtXlate was
                         compiled using OSMajorVersion * 100 + OSMinorVersion
Any of the arguments may be NULL.
$RETURNS$:
 0: if no error occurred
-1: if no translation was possible to get the AppExecEnvVersion
/*================================================$SKIP$==*/
#endif

int _DtXlateGetXlateEnv(
         _DtXlateDb db,
         char *     ret_AppExecEnvPlatform,
         int *      ret_AppExecEnvVersion,
         int *      ret_XlateCompiledForOSVersion)
{       /*$CODE$*/
   struct utsname names;
   int    ret = 0;
   char * platform = "NULL";
   int    execver = -1;
   int    compver = -1;

   /* get host specifics */
   uname(&names);

   /* first get execution host name */
   if (ret_AppExecEnvPlatform) 
   {
      strncpy(ret_AppExecEnvPlatform,names.sysname,_DtPLATFORM_MAX_LEN-1);
      ret_AppExecEnvPlatform[_DtPLATFORM_MAX_LEN-1] = EOS;
      platform=ret_AppExecEnvPlatform;
   }

   /* then look up version number of execution host */
   if (ret_AppExecEnvVersion) 
   {
      char version[sizeof(names.release)+sizeof(names.version)-1];
      char * stdVer = NULL;
      int  verNum = MATCHALL_VER;

      /* cat release version and do a translation on it to a std value */
      /* then convert the std value to a integer */
      strncpy(version,names.release,sizeof(names.release)-1);
      version[sizeof(names.release)-1] = EOS;
      strncat(version,names.version,sizeof(names.version)-1);
      ret = _DtXlateOpToStdValue(db,names.sysname,0,
                   _DtXLATE_OPER_VERSION,version,&stdVer,NULL);
      if (ret == 0)
      {
         if (sscanf(stdVer,"%d",&verNum) != 1) verNum = MATCHALL_VER;;
         free(stdVer);
      }
      *ret_AppExecEnvVersion = verNum;
      execver = verNum;
   }
  
   /* then look up version number of execution host */
   if (ret_XlateCompiledForOSVersion) 
   {
      char buf[MAXINTSTRSIZE];

#define _STR(V) #V
#define STR(V) _STR(V)

      /*=========================== 
         OSMAJORVERSION & OSMINORVERSION are compile-time constants
         that must be defined as part of the build environment. 
         It is assumed that these constants are of the form:

          e.g. Sun 5.3
          #define OSMAJORVERSION        5
          #define OSMINORVERSION        3
          
             530 = (int) (100.0 * atof("5.3"));
          
          e.g. HP-UX 8.09
          #define OSMAJORVERSION         8
          #define OSMINORVERSION         09
          
             809 = (int) (100.0 * atof("8.09"));
       ===========================*/

#if !defined(OSMAJORVERSION) && !defined(OSMINORVERSION)
#warning "OSMAJORVERSION and OSMINORVERSION not defined, assuming 99.0:
#define OSMAJORVERSION 99
#define OSMINORVERSION 0
#endif

#if defined(__linux__) || defined(CSRG_BASED)
      sprintf(buf,"%s%s%s", STR(OSMAJORVERSION), 
                     nl_langinfo('.'), STR(OSMINORVERSION));
#else
      sprintf(buf,"%s%s%s", STR(OSMAJORVERSION), 
                     nl_langinfo(RADIXCHAR), STR(OSMINORVERSION));
#endif

      *ret_XlateCompiledForOSVersion = (int) (100.0 * atof(buf));
      compver = *ret_XlateCompiledForOSVersion;
   }

   if (db->debugMode) 
      fprintf(stderr,"_DtXlateGetXlateEnv: "
                     "Platform: %s; Execution Ver: %d; Compiled Ver: %d\n",
                      platform,execver,compver);

   return ret;
}       /*$END$*/


/*========================================================*/
/*================ Introductory Info =====================*/
/*========================================================*/

#if DOC
/*========================================================*/
$INTROBEG$: _DtXlate family
$1LINER$: API to translate any value to a standard value and back again
$SUMMARY$:
_DtXlate is a collection of routines that allow translation
between platform, version, and operation specific values
into standard values and back again.  Translation is
based on the contents of database files specified using
Xrm-style resources.  The semantics of the translation
are a combination of standard semantics for specifying
platform, version, operation, and translation type, and
caller-specific semantics for the standard value format
and operation values.

The API contains routines to process translation databases
in a useful manner.

_DtXlateOpenDb() opens a particular translation database
_DtXlateOpenAndMergeDb() opens a particular database and
  merges it with an already opened one, overriding any
  repeated specifications.
_DtXlateOpenAllDbs() opens all occurrences of a translation
   database found in a search path and cumulatively merges 
   the contents, allowing for the override of existing 
   specifications as well as the addition of new ones.
_DtXlateCloseDb() closes an open database
_DtXlateOpToStdValue() translates an platform, version,
   and operation specific value into a standard value.
_DtXlateStdToOpValue() translates a standard value into
   a platform, version, and operation specific value.
/*=$END$================================================*/
#endif

#if DOC
/*========================================================*/
$INTROBEG$: Design and Implementation Considerations
$1LINER$: Factors influencing design and implementation
$SUMMARY$:
The syntax of the translation specification table is designed
to be compatible with Xrm resource specifications.  This
allows Xrm to be used to load, parse, and merge the databases
for processing.  This also causes the specifications to be
case-sensitive, as case is preserved by Xrm and is used when
processing queries.

However, due to the semantics of translation specifications
and limitations of Xrm, XrmGetResource() queries generally
will not be useful.  Rather, a pattern matching API is provided
that implements the query capability used for the translations.
The initial implementation will use XrmEnumerateDatabase().

Using Xrm leads to an in-memory approach to translation, meaning
that all the resources files are parsed and loaded into memory
as the first step of a translation.  A line-at-a-time approach,
that process one line at a time directly from the file in a
grep- or awk-like manner, is likely more memory-efficient.

Note that the line-at-a-time approach does not avoid parsing
all the specification files, as the API supports inheritence
and override of specifications.  Hence, to ensure the correct
value, the entire file set must be processed.  Note also that
in a situation where translations will be repeatedly performed,
the Xrm in-memory approach may be more time-efficient, as the
files need only be parsed once, and then are utilized repeatedly.

Because of time constraints, a line-at-a-time approach will not
be used for the first implementation, as Xrm provides all the
necessary parsing, filtering, and symbol hashing routines
for free.  Given the likely large size of the tables and their
infrequent use, a line-at-a-time approach is likely the better
choice.

/*=$END$================================================*/
#endif


#if DOC
/*========================================================*/
$INTROBEG$: translation BNF syntax and semantics
$1LINER$: _DtXlate translation specification syntax and semantics
$SUMMARY$:
   BNF Syntax of Translation Specification
   =======================================

<specfile>   ::= (<xlatespec> | <comment> | <cr>)*
<xlatespec>  ::= <platform>.<version>.<operations>.<transtype>
                      .<stdvalue>:<opvalue> (<comment>|<cr>)
<platform>   ::= <identifier> | <matchall>
<version>     ::= <number> [+ | (- <number>)] | <matchall>
<operations> ::= <identifier> [',' <identifier>]* | <matchall>
<transtype>  ::= '<' | '=' | '~' | '>' | '0'
<stdvalue>   ::= <identifier> [.<identifier>]*
<langterr>   ::= <identifier> | <matchall>
<codeset>    ::= <identifier> | <matchall>
<modifier>   ::= <identifier> | <matchall>
<opvalue>    ::= (<vischars>|<metachar>)+ | '"'(<anychar>|<metachar>)+'"'

<matchall>   ::= '?'[<number>]
<cr>         ::= '\n'
<comment>    ::= '!' [^<cr>]* <cr>
<number>     ::= [0-9]+
<identifier> ::= [-_a-ZA-Z0-9]
<vischar>    ::= any non-whitespace character; meta and regular
                 expression chars must be escaped with "\\"
<anychar>    ::= any printable character; meta and regular
                 expression chars must be escaped with "\\"
<metachar>   ::= '!' | '"' | <backslash> | <valex> | <regex>
<valex>      ::= <backslash><number>
<regex>      ::= see regexp(5)
<backslash>  ::= the backslash character (\).

   Semantics of the Translation Specification
   ==========================================

<specfile> : a file containing zero or more translation specifications
or comments

<xlatespec> : a translation specification defines a fully qualified
string value which may be recovered by query pattern matching the
specification.  The qualifiers identify the semantics of the usage
of the value, allowing queries to be meaningful.  The entire xlatespec
must be on one line of the file.

<platform> : a CDE-standardized identifier for an operating system
platform.  The platform need not be supported by CDE, but CDE must
have standardized an identifier for the platform in order for it to
be used.  For example, candidate platform identifiers are "HP-UX",
"AIX", "SunOS", "Solaris", "SCO", etc.  These identifiers are the
values returned by uname(2):utsname.sysname and uname(1) -s, and
I propose CDE standardize on using these values.

The identifier string and matching constant value must be added to
the source code of the translation routine in order for it to be
recognized.  This allows control over the platform registry
and enables automatic generation of the platform on the execution
host through the use of #ifdef <PLATFORM_CONST> in the source code.

<version> : a platform-specific numeric value or range of numberic
values that allows the value to be qualified to a particular version
and release (version + release = version) or range of versions and
releases of the platform.  The version numbers must be integral and
suitable for numeric comparison.  The '+' can be used to specify an
open upwards bound, as in 900+.  The '- <number>' can be used to
specify an upper bound, as in 900-999.  If no range is specified,
the version number must match exactly to the query pattern and/or
execution environment.

Platforms may not directly provide version numbers in a numeric
format suitable for use when comparing with a translation
specification.  As part of the translation API source code,
conversion routines must be supplied that translate the platform-
specific values, such as those provided by uname(2), into a
translation specification-compliant format suitable for comparison
to <version>.  Understanding the conversion routines operation
will be necessary to ensure correct translation specifications.

<operations> : a CDE-standardized identifier for the operation(s) to
which the value applies.  The operation(s) need not be supported by every
platform, but CDE must have standardized an identifier for the operation
in order for it to be used.  More than one identifier may be included by
concatenating them using the ',' separator, eg. "iconv1,iconv3".

<transtype> : The <transtype> field records the direction of
translation that the specification supports:  '<', '=', '~', '>', '0'.
Use '<' if the <opvalue> contains a regular expression; the
specification may only be used to match an operation value in
a _DtLcxXlateOpToStd() translation.  Use '>' if the <opvalue>
contains value replacement expressions using values from
the <stdvalue> strings; the specification may only be used by a
_DtLcxXlateStdToOp() translation.  Use '=' if the <opvalue>
is a pure string that may be used for either OpToStd() or
StdToOp() translations, and the  specification op-value must exactly
match the op-value of the translation request.  use '~' if the
<opvalue> is a pure string that may be used for either OpToStd() or
StdToOp() translations, and the specifation op-value must be
a string within the op-value of the translation request.
Use the '0' translation to represent that this particular 
translation should result in an error, that is should not result 
in a translation.

Translations are two-way operations: from a host- and
operation-specific value to standardized value, and back again.
In many cases, a straight string value for string value translation
is sufficient.

But often, more sophisticated pattern matching and value generation
mechanisms are useful to keep the table size small and as general
as possible.  In these cases, wildcard and regular expression
operators are useful.  Unfortunately, these specifications are not
bi-direction.  That is, the pattern matching string can be used
to match against a pattern, but not to generate a result string.
For example, the vi expression  s/he*lo/hello/ matches "hello"
as well as "hey look out below", but the reverse direction of
"hello"->"he*lo" will not work.

<stdvalue> : a sequence of one or more '.'-separated CDE-standardizd
identifiers or matchall characters.  This represents the canonical
string used as a standard representation of a semantic value that
may vary in different situations.

<opvalue> : a string that has different uses depending on the
value of <transtype>.  If transtype is '<', the string is used
as a regular expression to match the opValue in _DtLcxXlateOpToStd()
requests.  If the transtype is '>', the string is used as a
replacement expression to generate a opValue in _DtLcxXlateStdToOp().
If transtype is '=' or '~', the string is used as a straight string to
both match for the OpToStd translation and as a value for
the StdToOp translation.
%
If the opvalue contains whitespace, the opvalue must be enclosed in
double quote characters.  If the opvalue contains any meta characters
that should not be treated as meta characters, they must be escaped by
two preceding backslashs.Lack of an opvalue is not an error, but will
be ignored during matches and return an empty string in a StdToOp
translation; to specify that a given translation does not exist, the
'0' translation type should be used or the specification should not
occur.

<comment> : a comment begins on an empty line with an unescaped !
and continues to the end of the line.

<matchall> : The matchall character is a universal quantifier, meaning
that it symbolizes all possible values of the field where it is used.
The matchall character may occur only in the qualifers side of
a translation specification.  Traditionally, the matchall character
has been '*', but because of the semantics of Xrm, if the '*' is used
as the matchall, Xrm does not restrict enumeration as needed.

The matchall character may be followed by a number when the <transtype>
is '>'.  When this occurs, the string that matches the matchall
character may be referenced in the <opvalue> by using the sequence
\<number>, as in "\1".  The occurrence is replaced with the matched
string.
/*=$END$================================================*/
#endif

#if DOC
/*========================================================*/
$INTROBEG$: translation specification examples
$1LINER$: examples of _DtXlate translation specifications
$EXAMPLE$:

!! These are examples of bi-directional straight translations
HP-UX.900-999.setlocale.=.en_US.hp-roman8:        american
HP-UX.900-999.setlocale.=.en_US.iso88591:         american.iso88591
HP-UX.900-999.setlocale.=.nl_BE.hp-roman8.fold:   dutch@fold
HP-UX.900-999.setlocale.=.nl_BE.hp-roman8.nofold: dutch@nofold

!! These are examples of OpToStd translations utilizing regular 
!! expression patterns to match the search value.
HP-UX.?.version.<.900:   "A\\.09\\..*"     !! any HPUX 9.x version
HP-UX.?.version.<.807:   "A\\.08\\.07.*"
HP-UX.?.version.<.800:   "A\\.08\\..*"
AIX.?.version.<.320:     "2 3"
AIX.?.version.<.300:     "[0-9] 3"
SunOS.?.version.<.530:   "5\\.3.*"
SunOS.?.version.<.500:   "5\\..*"

!! These are examples of StdToOp translations utilizing matchall
!! specifiers in the std value to match the search value.
HP-UX.900+.iconv1.>.?.iso88596:     arabic8
HP-UX.900+.iconv1.>.?.iso88597:     greek8
HP-UX.900+.iconv1.>.?.hp-kana8:     kana8
HP-UX.900+.iconv1.>.?.hp-roman8:    roman8

!! The following examples use value replacement as part of their
!! specifications.  Using this can lead to much sparser tables, but
!! it depends on op-specific and std values sharing the same strings.
HP-UX.900-999.setlocale.<.nl_BE.hp-roman8.?1:     [dD][uU][tT][cC][hH]@\\(.*\\)
!HP-UX.900-999.setlocale.<.nl_BE.hp-roman8.?1:     dutch@\\(.*\\)
HP-UX.1000+.setlocale.>.?1.hp-roman8:    \\1.roman8     !! all non-modif cases
HP-UX.1000+.setlocale.>.?1.hp-roman8.?2: \\1.roman8@\\2 !! all modif cases

/*=$END$================================================*/
#endif


#if DOC
/*========================================================*/
$INTROBEG$: _DtXlate example usage
$1LINER$: Examples of how to _DtXlate
$EXAMPLE$:
#include <XlationSvc.h>
main()
{
   _DtXlateDb db = NULL;
   int  ret;
   char plat[_DtPLATFORM_MAX_LEN];
   int  execver;
   int  compver;
   char * val = NULL;
   char * str = NULL;
   char empty = 0;
#define OPER_YOUROP "myop"

   env = getenv("MYPATH");
   if (env == NULL) env = &empty;

   ret = _DtXlateOpenAllDbs(env,"myfile.xlt",&db);

   ret = _DtXlateGetXlateEnv(db,plat,&execver,&compver);
   printf("Platform: %s\nExec Ver: %d\nComp Ver: %d\n",
                    plat,execver,compver);

   ret = _DtXlateStdToOpValue(db,plat,compver,OPER_YOUROP,
                     str="en_US.hp-roman8",&val,NULL);
   if (ret==0) printf("setlocale(%s) xlation=%s\n", str, val);
   else printf("no xlation\n", val);

   ret = _DtXlateOpToStdValue(db,plat,execver,OPER_YOUROP,
                     str="american",&val,NULL);
   if (ret==0) printf("setlocale(%s) xlation=%s\n", str, val);
   else printf("no xlation\n", val);

   ret = _DtXlateCloseDb(&db);
}
/*=$END$================================================*/
#endif
