/******************************************************************************
*
*  NSSDC/CDF                   Toolbox of routines for CDF Toolkit (General).
*
*  Version 1.5d, 23-Nov-97, Hughes STX.
*
*  Modification history:
*
*   V1.0  25-Jan-94, J Love     Original version.
*   V1.0a  6-Feb-94, J Love     DEC Alpha/OpenVMS port.
*   V1.1  10-Mar-94, J Love     Modified `ParseOptionList' to make the
*                               enclosing parentheses optional (on any
*                               operating system).
*   V1.2  15-Dec-94, J Love     CDF V2.5.
*   V1.2a 13-Jan-95, J Love     Allow mixed-case in tokens.  Changed
*                               `CDFdirList' to allow all possible extensions
*                               on Macintosh machines also.
*   V1.2b  6-Mar-95, J Love     Added macro definition checking to catch
*                               bug in directory listing system calls.  Pass
*                               `char' as `int'.
*   V1.3  21-Mar-95, J Love     POSIX.
*   V1.3a 18-Apr-95, J Love     More POSIX.
*   V1.3b 24-Apr-95, J Love     Fixed `EncodeString' to not overflow the
*                               maximum length if the string won't fit.
*   V1.4  25-May-95, J Love     Added `GetCdataType'.  EPOCH styles.
*                               SOLARISbsdDIRUTILSbug.
*   V1.4a 12-Jun-95, J Love     EPOCH custom format.  catchrX.
*   V1.4b  5-Sep-95, J Love     Allow NULL for output fields not needed.
*                               NUL-terminate decoded string in `DecodeValues'.
*   V1.4c 19-Sep-95, J Love     Macintosh event handling.
*   V1.4d 27-Sep-95, J Love     Reduced TextEdit text size for Think C.
*   V1.5  27-Aug-96, J Love     CDF V2.6.
*   V1.5a  2-Nov-96, J Love	Changed `DisplayIdentification' to not print
*				a `blank' sub-increment.
*   V1.5b 21-Feb-97, J Love	Removed RICE.
*   V1.5c  3-Mar-97, J Love	Windows NT for MS Visual C/C++ on an IBM PC.
*   V1.5d 23-Nov-97, J Love	More Windows NT.
*
******************************************************************************/

#define TOOLBOX1
#include "cdftools.h"

/******************************************************************************
* Local macros.
******************************************************************************/

#define MAX_cFORMAT_LEN 10

/******************************************************************************
* Local enumerators.
******************************************************************************/

#if defined(unix) || defined(mac) || defined(posixSHELL)
enum typeENUM {TBD, STR, WILD1, WILDn};
#endif

/******************************************************************************
* Local structures.
******************************************************************************/

struct FILEstruct {
  char *name;
  struct FILEstruct *next;
};

/******************************************************************************
* Local function prototypes.
******************************************************************************/

static Logical FtoCformat PROTOARGs((long, char *, long *, char *));
static int UseCformat PROTOARGs((long, long, char *, void *, char *));
static long GetCdataType PROTOARGs((char *, long *));
static void AddToList PROTOARGs((struct FILEstruct **FILEhead,
				 struct FILEstruct *FILEptr));
#if defined(unix) || defined(mac) || defined(posixSHELL)
static int PatternMatch PROTOARGs((char *pattern, char *name));
static int CheckPattern PROTOARGs((int nTypes, enum typeENUM types[],
				   char *strings[], char *name));
#endif

/******************************************************************************
* FatalError.
******************************************************************************/

void FatalError (message)
char *message;
{
#if defined(mac)
  MacMessageDialog ("FATAL ERROR!", message);
#else
#if defined(win32)
  WinMessageDialog ("FATAL ERROR!", message);
#else
  WriteOut (stdout, "FATAL ERROR!  ");
  WriteOut (stdout, message);
  WriteOut (stdout, "\n");
#endif
#endif
  ExitBAD;
}

/******************************************************************************
* DisplayError.
******************************************************************************/

void DisplayError (message)
char *message;
{
#if defined(mac)
  MacMessageDialog ("Error!", message);
#else
#if defined(win32)
  WinMessageDialog ("Error!", message);
#else
  WriteOut (stdout, "Error!  ");
  WriteOut (stdout, message);
  WriteOut (stdout, "\n");
#endif
#endif
  return;
}

/******************************************************************************
* DisplayWarning.
*    It is assumed that the message contains at least one character.
******************************************************************************/

void DisplayWarning (message)
char *message;
{
#if defined(mac)
  MacMessageDialog ("Warning...", message);
#else
  WriteOut (stdout, "Warning: ");
  WriteOut (stdout, message);
  WriteOut (stdout, "\n");
#endif
  return;
}

/******************************************************************************
* DisplayInfo.
******************************************************************************/

void DisplayInfo (message)
char *message;
{
#if defined(mac)
  MacMessageDialog ("", message);
#else
  WriteOut (stdout, message);
  WriteOut (stdout, "\n");
#endif
  return;
}

/******************************************************************************
* DecodeVariances.
******************************************************************************/

Logical DecodeVariances (mask, numDims, recVary, dimVarys)
char *mask;
long numDims;
long *recVary;
long dimVarys[];
{
  int dimN, charN;

  switch (mask[0]) {
    case 'T':
    case 't':
      *recVary = VARY;
      break;
    case 'F':
    case 'f':
      *recVary = NOVARY;
      break;
    default:
      return FALSE;
  }

  for (dimN = 0, charN = 2; dimN < numDims; dimN++, charN++) {
     switch (mask[charN]) {
       case 'T':
       case 't':
	 dimVarys[dimN] = VARY;
	 break;
       case 'F':
       case 'f':
	 dimVarys[dimN] = NOVARY;
	 break;
       default:
	 return FALSE;
   }
  }

  return TRUE;
}

/******************************************************************************
* DecodeDelimitedString.
******************************************************************************/

Logical DecodeDelimitedString (dString, pString)
char *dString;          /* Delimited string. */
char *pString;          /* Parsed (decoded) string. */
{
  char *d1 = dString;           /* First delimiter. */
  char *d2;                     /* Second delimiter. */
  size_t len;                   /* Size of decoded string. */

  while (*d1 != NUL && Spacing(*d1)) d1++;
  if (*d1 == NUL) return FALSE;

  d2 = d1 + 1;
  while (*d2 != NUL && *d2 != *d1) d2++;
  if (*d2 == NUL) return FALSE;

  len = (size_t) (d2 - d1 - 1);
  memmove (pString, d1 + 1, len);
  pString[len] = NUL;

  return TRUE;
}

/******************************************************************************
* DecodeDimensionality.
******************************************************************************/

Logical DecodeDimensionality (string, numDims, dimSizes)
char *string;
long *numDims;
long *dimSizes;
{
  char *cp;
  /****************************************************************************
  * The number of dimensions should be first.
  ****************************************************************************/
  cp = NextNonSpace (string);
  if (sscanf(cp,"%ld",numDims) != 1) return FALSE;
  if (*numDims < 0) return FALSE;
  cp = NextNonDigit (cp);
  /****************************************************************************
  * Skip past the `:' and `['.
  ****************************************************************************/
  cp = NextNonSpace (cp);
  if (*cp != ':') return FALSE;
  cp = NextNonSpace (cp+1);
  if (*cp != '[') return FALSE;
  cp = NextNonSpace (cp+1);
  /****************************************************************************
  * Decode each dimension size.
  ****************************************************************************/
  if (*numDims > 0) {
    int dimN, lastDim = (int) (*numDims - 1);
    for (dimN = 0; dimN <= lastDim; dimN++) {
       if (sscanf(cp,"%ld",&dimSizes[dimN]) != 1) return FALSE;
       cp = NextNonDigit (cp);
       cp = NextNonSpace (cp);
       if (dimN != lastDim) {
	 if (*cp != ',') return FALSE;
	 cp = NextNonSpace (cp+1);
       }
    }
  }
  /****************************************************************************
  * Check for a trailing `]'.
  ****************************************************************************/
  if (*cp != ']') return FALSE;
  /****************************************************************************
  * Everything went Ok, return TRUE;
  ****************************************************************************/
  return TRUE;
}

/******************************************************************************
* DecodeRecordAndIndices.
******************************************************************************/

Logical DecodeRecordAndIndices (string, recN, nIndices, indices)
char *string;
long *recN;                     /* Can be NULL if not needed. */
long *nIndices;
long indices[CDF_MAX_DIMS];
{
  char *cp; long recNt;
  /****************************************************************************
  * The record number will be first if it exists;
  ****************************************************************************/
  cp = NextNonSpace (string);
  if (*cp == '[') {
    recNt = 0;
  }
  else {
    if (sscanf(cp,"%ld",&recNt) != 1) return FALSE;
    recNt -= 1;
    if (recNt < 0) return FALSE;
    cp = NextNonDigit (cp);
    cp = NextNonSpace (cp);
    if (*cp != ':') return FALSE;
    cp = NextNonSpace (cp+1);
    if (*cp != '[') return FALSE;
  }
  ASSIGNnotNULL (recN, recNt)
  cp = NextNonSpace (cp+1);
  /****************************************************************************
  * Decode each index while counting how many there are.
  ****************************************************************************/
  *nIndices = 0;
  if (*cp == ']') return TRUE;
  for (;;) {
     if (*nIndices == CDF_MAX_DIMS) return FALSE;       /* Too many indices. */
     if (sscanf(cp,"%ld",&indices[(int)(*nIndices)]) != 1) return FALSE;
     indices[(int)(*nIndices)] -= 1;
     if (indices[(int)(*nIndices)] < 0) return FALSE;
     *nIndices += 1;
     cp = NextNonDigit (cp);    /* Skip to space, comma, or `]'. */
     cp = NextNonSpace (cp);    /* Skip past spaces. */
     switch (*cp) {
       case ']':
	 return TRUE;
       case ',':
	 cp = NextNonSpace (cp + 1);
	 break;
       default:
	 return FALSE;
     }
  }
}

/******************************************************************************
* NextNonSpace.
******************************************************************************/

char *NextNonSpace (cp)
char *cp;
{
  while (Spacing(*cp)) cp++;
  return cp;
}

/******************************************************************************
* NextNonDigit.
******************************************************************************/

char *NextNonDigit (cp)
char *cp;
{
  while (Decimal(*cp)) cp++;
  return cp;
}

/******************************************************************************
* FormatWidth.
*    Returns width of a format specifier [or zero if the width is unknown or
* illegal].
******************************************************************************/

int FormatWidth (format)
char *format;
{
  int width; char *ptr = format;
  /****************************************************************************
  * Skip past Fortran repeat count (eg. the `20' in `20I8' or `20(I8)').
  * Note that this won't skip past a C `%'.
  ****************************************************************************/
  while (*ptr != NUL && (*ptr == '(' ||
			 Spacing(*ptr) ||
			 Decimal(*ptr))) ptr++;
  if (*ptr == NUL) return 0;
  /****************************************************************************
  * Skip past Fortran format type (eg. the `F' in `F4.1') or C `%' and/or
  * flags (one of which might be a `0').
  ****************************************************************************/
  while (*ptr != NUL && (!Decimal(*ptr) || *ptr == '0')) ptr++;
  if (*ptr == NUL) return 0;
  /****************************************************************************
  * Decode format width.
  ****************************************************************************/
  if (sscanf(ptr,"%d",&width) != 1) width = 0;
  return width;
}

/******************************************************************************
* FormatPrecision.
*   Returns precision of a format specifier [or zero if the precision doesn't
* exist [eg. integer format]).
******************************************************************************/

int FormatPrecision (format)
char *format;
{
  int precision;
  char *ptr = strchr (format, '.');
  if (ptr == NULL)
    precision = 0;
  else
    if (sscanf(ptr+1,"%d",&precision) != 1) precision = 0;
  return precision;
}

/******************************************************************************
* EncodeValuesFormat.  Returns width of encoded values.
******************************************************************************/

int EncodeValuesFormat (dataType, numElems, binary, text, format, minWidth,
			maxWidth, style)
long dataType;          /* Data type of values. */
long numElems;          /* Number of elements (values). */
void *binary;           /* In: Values in binary. */
char *text;             /* Out: Encoded values. */
char *format;           /* If NULL, use default formats.  Not applicable if
			   a string data type. */
int minWidth;           /* Minimum width (including delimeters if a string
			   data type.  If zero, no minimum width (no blank
			   padding).  If positive, right justified (padding
			   on left side).  If negative, left justified
			   (padding on right side). */
int maxWidth;           /* Maximum width (including delimeters if a string
			   data type.  If zero, no maximum width (no
			   truncating). */
int style;              /* EPOCH style. */
{
  /****************************************************************************
  * Encode value(s).
  ****************************************************************************/
  if (STRINGdataType(dataType))
    EncodeString (numElems, binary, text, minWidth, maxWidth);
  else {
    long elemN;
    char tText[MAX_nonSTRING_VALUE_LEN+1];
    strcpyX (text, "", maxWidth);
    for (elemN = 0; elemN < numElems; elemN++) {
       /***********************************************************************
       * Encode each value (if it fits).
       ***********************************************************************/
       EncodeValueFormat (dataType, (Byte *) binary +
				    (size_t) (elemN*CDFelemSize(dataType)),
			  tText, format, 0, 0, style);
       if (maxWidth > 0) {
	 int n = strlen(tText);
	 if (strlen(text) + (elemN > 0 ? 2 : 0) + n > (size_t) maxWidth) {
	   /*******************************************************************
	   * This value won't fit.  Find a comma after which to put the
	   * truncation symbol (`...').  `i' starts at the NUL just in case
	   * this is the first value (`text' is a null-string).
	   *******************************************************************/
	   int i;
	   for (i = strlen(text); i != 0; i--) {
	      if (text[i] == ',' &&
		  (i + strlen(" ...") < (size_t) maxWidth)) break;
	   }
	   if (i != 0)
	     strcpyX (&text[i+1], " ...", maxWidth);
	   else {
	     switch (elemN) {
	       case 0:
		 CpyNcharacters (text, maxWidth, (int) '*');
		 break;
	       case 1:
		 if (strlen(text) + strlen(", ...") <= (size_t) maxWidth)
		   strcatX (text, ", ...", maxWidth);
		 else {
		   CpyNcharacters (text, maxWidth, (int) '*');
		 }
		 break;
	       default:
		 CpyNcharacters (text, maxWidth, (int) '*');
		 break;
	     }
	   }
	   break;
	 }
	 else
	   sprintf (&text[strlen(text)], "%s%s", (elemN > 0 ? ", " : ""),
		    tText);
       }
       else
	 sprintf (&text[strlen(text)], "%s%s", (elemN > 0 ? ", " : ""), tText);
    }
    /**************************************************************************
    * Blank pad to `minWidth' if necessary.
    **************************************************************************/
    if (minWidth != 0) {
      int n = strlen(text);
      if (minWidth > 0) {               /* Right justify. */
	if (n < minWidth) {
	  int i, pad = minWidth - n;
	  memmove (&text[pad], text, n + 1);
	  for (i = 0; i < pad; i++) text[i] = ' ';
	}
      }
      else {                    /* Left justify. */
	int min = -minWidth;
	if (n < min) {
	  int pad = min - n;
	  CatNcharacters (text, pad, (int) ' ');
	}
      }
    }
  }
  /****************************************************************************
  * Return length of encoded value(s).
  ****************************************************************************/
  return strlen(text);
}

/******************************************************************************
* EncodeValueFormat.
*    Returns width of encoded value.  Not for use with character string data
* types (CDF_CHAR or CDF_UCHAR).
******************************************************************************/

int EncodeValueFormat (dataType, binary, text, format, minWidth, maxWidth,
		       style)
long dataType;          /* Data type of value being encoded. */
void *binary;           /* In: Value in binary. */
char *text;             /* Out: Encoded value. */
char *format;           /* Format string to be used. */
int minWidth;           /* Minimum width (including delimeters if a string
			   data type.  If zero, no minimum width (no blank
			   padding).  If positive, right justified (padding
			   on left side).  If negative, left justified
			   (padding on right side). */
int maxWidth;           /* Maximum width.  If zero, no maximum width (no
			   asteriks if too big). */
int style;              /* EPOCH style. */
{
  long CdataType;
  char Cformat[MAX_cFORMAT_LEN+1];
  char tText[MAX_nonSTRING_VALUE_LEN+1];
  /****************************************************************************
  * Encode value.
  ****************************************************************************/
  if (EPOCHdataType(dataType)) {
    switch (style) {
      case EPOCHf_STYLE:
	return EncodeValueFormat(CDF_DOUBLE,binary,text,
				 format,minWidth,maxWidth,0);
      case EPOCHx_STYLE:
	encodeEPOCHx (*((double *) binary), format, tText);
	break;
      default:
	EncodeValue (CDF_EPOCH, binary, tText, style);
	break;
    }
  }
  else
    if (format == NULL)
      EncodeValue (dataType, binary, tText, 0);
    else
      if (strchr(format,'%') != NULL)
	if (GetCdataType(format,&CdataType))
	  UseCformat (dataType, CdataType, format, binary, tText);
	else
	  EncodeValue (dataType, binary, tText, 0);
      else
	if (FtoCformat(dataType,format,&CdataType,Cformat))
	  UseCformat (dataType, CdataType, Cformat, binary, tText);
	else
	  EncodeValue (dataType, binary, tText, 0);
  /****************************************************************************
  * Fill in asteriks if encoded value is too big.  Otherwise, copy encoded
  * value.
  ****************************************************************************/
  if (maxWidth > 0) {
    int n = strlen(tText);
    if (n > maxWidth)
      CpyNcharacters (text, maxWidth, (int) '*');
    else
      strcpyX (text, tText, maxWidth);
  }
  else
    strcpyX (text, tText, maxWidth);
  /****************************************************************************
  * Blank pad to `minWidth' if necessary.
  ****************************************************************************/
  if (minWidth != 0) {
    int n = strlen(text);
    if (minWidth > 0) {         /* Right justify. */
      if (n < minWidth) {
	int i, pad = minWidth - n;
	memmove (&text[pad], text, n + 1);
	for (i = 0; i < pad; i++) text[i] = ' ';
      }
    }
    else {                      /* Left justify. */
      int min = -minWidth;
      if (n < min) {
	int pad = min - n;
	CatNcharacters (text, pad, (int) ' ');
      }
    }
  }
  /****************************************************************************
  * Return length of encoded value.
  ****************************************************************************/
  return strlen(text);
}

/******************************************************************************
* EncodeValue.
* Returns width of encoded value.
******************************************************************************/

int EncodeValue (dataType, binary, text, style)
long dataType;          /* CDF data type. */
void *binary;           /* In: Value to encode. */
char *text;             /* Out: Encoded value. */
int style;              /* In: EPOCH style. */
{
  MakeNUL (text);
  switch (dataType) {
    case CDF_BYTE:
    case CDF_INT1:
      sprintf (text, "%d", (int) *((sChar *) binary));
      break;
    case CDF_INT2:
      sprintf (text, "%d", (int) *((Int16 *) binary));
      break;
    case CDF_INT4:
      sprintf (text, Int32FORMAT, *((Int32 *) binary));
      break;
    case CDF_UINT1:
      sprintf (text, "%u", (uInt) *((uChar *) binary));
      break;
    case CDF_UINT2:
      sprintf (text, "%u", (uInt) *((uInt16 *) binary));
      break;
    case CDF_UINT4:
      sprintf (text, Int32uFORMAT, *((uInt32 *) binary));
      break;
    case CDF_REAL4:
    case CDF_FLOAT: {
      if (NegativeZeroReal4((float *) binary))
	strcpyX (text, NEGATIVEzeroSYMBOL, 0);
      else {
	size_t len;
	char *ePtr, tText[80+1];
	sprintf (text, "%g", *((float *) binary));
	if ((ePtr = strchr(text,'e')) == NULL) {  /* eg., 0 */
	  if (strchr(text,'.') == NULL) strcatX (text,".0", 0);
	}
	else {
	  if (strchr(text,'.') == NULL) {  /* eg., 1e+07 */
	    len = (size_t) (ePtr - text);
	    strcpyX (tText, text, MINIMUM(len,80));
	    strcatX (tText, ".0", 0);
	    strcatX (tText, ePtr, 0);
	    strcpyX (text, tText, 0);
	  }
	}
      }
      break;
    }
    case CDF_REAL8:
    case CDF_DOUBLE: {
      if (NegativeZeroReal8((double *) binary))
	strcpyX (text, NEGATIVEzeroSYMBOL, 0);
      else {
	size_t len;
	char *ePtr, tText[80+1];
	sprintf (text, "%g", *((double *) binary));
	if ((ePtr = strchr(text,'e')) == NULL) {  /* eg., 0 */
	  if (strchr(text,'.') == NULL) strcatX (text,".0", 0);
	}
	else {
	  if (strchr(text,'.') == NULL) {  /* eg., 1e+07 */
	    len = (size_t) (ePtr - text);
	    strcpyX (tText, text, MINIMUM(len,80));
	    strcatX (tText, ".0", 0);
	    strcatX (tText, ePtr, 0);
	    strcpyX (text, tText, 0);
	  }
	}
      }
      break;
    }
    case CDF_CHAR:
      sprintf (text, "%c", *((sChar *) binary));
      break;
    case CDF_UCHAR:
      sprintf (text, "%c", *((uChar *) binary));
      break;
    case CDF_EPOCH:
      switch (style) {
	case EPOCH0_STYLE:
	  encodeEPOCH (*((double *) binary), text);
	  break;
	case EPOCH1_STYLE:
	  encodeEPOCH1 (*((double *) binary), text);
	  break;
	case EPOCH2_STYLE:
	  encodeEPOCH2 (*((double *) binary), text);
	  break;
	case EPOCH3_STYLE:
	  encodeEPOCH3 (*((double *) binary), text);
	  break;
	case EPOCHf_STYLE:
	  return EncodeValue(CDF_DOUBLE,binary,text,0);
	case EPOCHx_STYLE:
	  /* Unsupported (a format specification is needed). */
	  break;
      }
      break;
  }
  return strlen(text);
}

/******************************************************************************
* EncodeString.  Returns width of encoded string (including delimeters).
******************************************************************************/

int EncodeString (nChars, iString, oString, minWidth, maxWidth)
long nChars;            /* Number of characters (elements). */
char *iString;          /* String to encode (input). */
char *oString;          /* Encoded string (output). */
int minWidth;           /* Minimum width (including delimeters if a string
			   data type.  If zero, no minimum width (no blank
			   padding).  If positive, right justified (padding
			   on left side).  If negative, left justified
			   (padding on right side). */
int maxWidth;           /* Maximum width (including delimeters).  If zero,
			   no maximum width (no truncating). */
{
  char delim = PickDelimiter (iString, (size_t) nChars); int i;
  /****************************************************************************
  * Encode string.
  ****************************************************************************/
  if (nChars + 2 > BOO(maxWidth == 0,(int)(nChars + 2),maxWidth)) {
    /**************************************************************************
    * Won't fit.  Should at least encode `"a..."'.  If not enough room, fill
    * in with astericks.
    **************************************************************************/
    if (maxWidth < 6) {
      CpyNcharacters (oString, maxWidth, (int) '*');
    }
    else {
      oString[0] = delim;
      for (i = 0; i < maxWidth - 5; i++) {
	 oString[i+1] = BOO(Printable(iString[i]),iString[i],'.');
      }
      oString[i+1] = '.';
      oString[i+2] = '.';
      oString[i+3] = '.';
      oString[i+4] = delim;
      oString[i+5] = NUL;
    }
  }
  else {
    /**************************************************************************
    * Fits.
    **************************************************************************/
    oString[0] = delim;
    for (i = 0; i < nChars; i++) {
       oString[i+1] = BOO(Printable(iString[i]),iString[i],'.');
    }
    oString[i+1] = delim;
    oString[i+2] = NUL;
  }
  /****************************************************************************
  * Blank pad to `minWidth' if necessary.
  ****************************************************************************/
  if (minWidth != 0) {
    int n = strlen(oString);
    if (minWidth > 0) {         /* Right justify. */
      if (n < minWidth) {
	int i, pad = minWidth - n;
	memmove (&oString[pad], oString, n + 1);
	for (i = 0; i < pad; i++) oString[i] = ' ';
      }
    }
    else {                      /* Left justify. */
      int min = -minWidth;
      if (n < min) {
	int pad = min - n;
	CatNcharacters (oString, pad, (int) ' ');
      }
    }
  }
  /****************************************************************************
  * Return length of encoded string.
  ****************************************************************************/
  return strlen(oString);
}

/******************************************************************************
* EncodeNegativeZero.
******************************************************************************/

void EncodeNegativeZero (string, format)
char *string;
char *format;
{
  char *ptr;
  /****************************************************************************
  * Encode 0.0 using format then check if the value was successfully encoded.
  ****************************************************************************/
  string[0] = NUL;
  sprintf(string, format, 0.0);
  if (NULstring(string)) {
    strcpyX (string, "-0.0(bad FORMAT)", 0);
    return;
  }
  /****************************************************************************
  * 0.0 was successfully encoded - put a negative sign at the beginning
  * (increasing the length of the string if necessary).
  ****************************************************************************/
  ptr = string;
  while (*ptr == ' ') ptr++;
  if (ptr == string) {
    memmove (&string[1], string, strlen(string) + 1);
    string[0] = '-';
  }
  else
    *(ptr - 1) = '-';
  return;
}

/******************************************************************************
* UseCformat.
* Returns width of encoded value.  Not for use with CDF_EPOCH or CDF_(U)CHAR
* values.
******************************************************************************/

static int UseCformat (dataType, CdataType, Cformat, binary, text)
long dataType;          /* Value's actual data type.  This should never be
			   CDF_EPOCH. */
long CdataType;         /* Data type corresponding to C format.  This should
			   never be CDF_EPOCH. */
char *Cformat;          /* C format specification. */
void *binary;           /* Value to be encoded. */
char *text;             /* String into which to encode the value. */
{
  switch (dataType) {
    case CDF_BYTE:
    case CDF_INT1:
      switch (CdataType) {
	case CDF_BYTE:
	case CDF_INT1:
	  sprintf (text, Cformat, (sChar) *((sChar *) binary));
	  break;
	case CDF_UINT1:
	  sprintf (text, Cformat, (uChar) *((sChar *) binary));
	  break;
	case CDF_INT2:
	  sprintf (text, Cformat, (Int16) *((sChar *) binary));
	  break;
	case CDF_UINT2:
	  sprintf (text, Cformat, (uInt16) *((sChar *) binary));
	  break;
	case CDF_INT4:
	  sprintf (text, Cformat, (Int32) *((sChar *) binary));
	  break;
	case CDF_UINT4:
	  sprintf (text, Cformat, (uInt32) *((sChar *) binary));
	  break;
	case CDF_REAL4:
	case CDF_FLOAT:
	  sprintf (text, Cformat, (float) *((sChar *) binary));
	  break;
	case CDF_REAL8:
	case CDF_DOUBLE:
	  sprintf (text, Cformat, (double) *((sChar *) binary));
	  break;
      }
      break;
    case CDF_UINT1:
      switch (CdataType) {
	case CDF_BYTE:
	case CDF_INT1:
	  sprintf (text, Cformat, (sChar) *((uChar *) binary));
	  break;
	case CDF_UINT1:
	  sprintf (text, Cformat, (uChar) *((uChar *) binary));
	  break;
	case CDF_INT2:
	  sprintf (text, Cformat, (Int16) *((uChar *) binary));
	  break;
	case CDF_UINT2:
	  sprintf (text, Cformat, (uInt16) *((uChar *) binary));
	  break;
	case CDF_INT4:
	  sprintf (text, Cformat, (Int32) *((uChar *) binary));
	  break;
	case CDF_UINT4:
	  sprintf (text, Cformat, (uInt32) *((uChar *) binary));
	  break;
	case CDF_REAL4:
	case CDF_FLOAT:
	  sprintf (text, Cformat, (float) *((uChar *) binary));
	  break;
	case CDF_REAL8:
	case CDF_DOUBLE:
	  sprintf (text, Cformat, (double) *((uChar *) binary));
	  break;
      }
      break;
    case CDF_INT2:
      switch (CdataType) {
	case CDF_BYTE:
	case CDF_INT1:
	  sprintf (text, Cformat, (sChar) *((Int16 *) binary));
	  break;
	case CDF_UINT1:
	  sprintf (text, Cformat, (uChar) *((Int16 *) binary));
	  break;
	case CDF_INT2:
	  sprintf (text, Cformat, (Int16) *((Int16 *) binary));
	  break;
	case CDF_UINT2:
	  sprintf (text, Cformat, (uInt16) *((Int16 *) binary));
	  break;
	case CDF_INT4:
	  sprintf (text, Cformat, (Int32) *((Int16 *) binary));
	  break;
	case CDF_UINT4:
	  sprintf (text, Cformat, (uInt32) *((Int16 *) binary));
	  break;
	case CDF_REAL4:
	case CDF_FLOAT:
	  sprintf (text, Cformat, (float) *((Int16 *) binary));
	  break;
	case CDF_REAL8:
	case CDF_DOUBLE:
	  sprintf (text, Cformat, (double) *((Int16 *) binary));
	  break;
      }
      break;
    case CDF_UINT2:
      switch (CdataType) {
	case CDF_BYTE:
	case CDF_INT1:
	  sprintf (text, Cformat, (sChar) *((uInt16 *) binary));
	  break;
	case CDF_UINT1:
	  sprintf (text, Cformat, (uChar) *((uInt16 *) binary));
	  break;
	case CDF_INT2:
	  sprintf (text, Cformat, (Int16) *((uInt16 *) binary));
	  break;
	case CDF_UINT2:
	  sprintf (text, Cformat, (uInt16) *((uInt16 *) binary));
	  break;
	case CDF_INT4:
	  sprintf (text, Cformat, (Int32) *((uInt16 *) binary));
	  break;
	case CDF_UINT4:
	  sprintf (text, Cformat, (uInt32) *((uInt16 *) binary));
	  break;
	case CDF_REAL4:
	case CDF_FLOAT:
	  sprintf (text, Cformat, (float) *((uInt16 *) binary));
	  break;
	case CDF_REAL8:
	case CDF_DOUBLE:
	  sprintf (text, Cformat, (double) *((uInt16 *) binary));
	  break;
      }
      break;
    case CDF_INT4:
      switch (CdataType) {
	case CDF_BYTE:
	case CDF_INT1:
	  sprintf (text, Cformat, (sChar) *((Int32 *) binary));
	  break;
	case CDF_UINT1:
	  sprintf (text, Cformat, (uChar) *((Int32 *) binary));
	  break;
	case CDF_INT2:
	  sprintf (text, Cformat, (Int16) *((Int32 *) binary));
	  break;
	case CDF_UINT2:
	  sprintf (text, Cformat, (uInt16) *((Int32 *) binary));
	  break;
	case CDF_INT4:
	  sprintf (text, Cformat, (Int32) *((Int32 *) binary));
	  break;
	case CDF_UINT4:
	  sprintf (text, Cformat, (uInt32) *((Int32 *) binary));
	  break;
	case CDF_REAL4:
	case CDF_FLOAT:
	  sprintf (text, Cformat, (float) *((Int32 *) binary));
	  break;
	case CDF_REAL8:
	case CDF_DOUBLE:
	  sprintf (text, Cformat, (double) *((Int32 *) binary));
	  break;
      }
      break;
    case CDF_UINT4:
      switch (CdataType) {
	case CDF_BYTE:
	case CDF_INT1:
	  sprintf (text, Cformat, (sChar) *((uInt32 *) binary));
	  break;
	case CDF_UINT1:
	  sprintf (text, Cformat, (uChar) *((uInt32 *) binary));
	  break;
	case CDF_INT2:
	  sprintf (text, Cformat, (Int16) *((uInt32 *) binary));
	  break;
	case CDF_UINT2:
	  sprintf (text, Cformat, (uInt16) *((uInt32 *) binary));
	  break;
	case CDF_INT4:
	  sprintf (text, Cformat, (Int32) *((uInt32 *) binary));
	  break;
	case CDF_UINT4:
	  sprintf (text, Cformat, (uInt32) *((uInt32 *) binary));
	  break;
	case CDF_REAL4:
	case CDF_FLOAT:
	  sprintf (text, Cformat, (float) *((uInt32 *) binary));
	  break;
	case CDF_REAL8:
	case CDF_DOUBLE:
	  sprintf (text, Cformat, (double) *((uInt32 *) binary));
	  break;
      }
      break;
    case CDF_REAL4:
    case CDF_FLOAT:
      switch (CdataType) {
	case CDF_BYTE:
	case CDF_INT1:
	  sprintf (text, Cformat, (sChar) *((float *) binary));
	  break;
	case CDF_UINT1:
	  sprintf (text, Cformat, (uChar) *((float *) binary));
	  break;
	case CDF_INT2:
	  sprintf (text, Cformat, (Int16) *((float *) binary));
	  break;
	case CDF_UINT2:
	  sprintf (text, Cformat, (uInt16) *((float *) binary));
	  break;
	case CDF_INT4:
	  sprintf (text, Cformat, (Int32) *((float *) binary));
	  break;
	case CDF_UINT4:
	  sprintf (text, Cformat, (uInt32) *((float *) binary));
	  break;
	case CDF_REAL4:
	case CDF_FLOAT:
	  if (NegativeZeroReal4((float *) binary))
	    EncodeNegativeZero (text, Cformat);
	  else
	    sprintf (text, Cformat, (float) *((float *) binary));
	  break;
	case CDF_REAL8:
	case CDF_DOUBLE:
	  if (NegativeZeroReal4((float *) binary))
	    EncodeNegativeZero (text, Cformat);
	  else
	    sprintf (text, Cformat, (double) *((float *) binary));
	  break;
      }
      break;
    case CDF_REAL8:
    case CDF_DOUBLE:
      switch (CdataType) {
	case CDF_BYTE:
	case CDF_INT1:
	  sprintf (text, Cformat, (sChar) *((double *) binary));
	  break;
	case CDF_UINT1:
	  sprintf (text, Cformat, (uChar) *((double *) binary));
	  break;
	case CDF_INT2:
	  sprintf (text, Cformat, (Int16) *((double *) binary));
	  break;
	case CDF_UINT2:
	  sprintf (text, Cformat, (uInt16) *((double *) binary));
	  break;
	case CDF_INT4:
	  sprintf (text, Cformat, (Int32) *((double *) binary));
	  break;
	case CDF_UINT4:
	  sprintf (text, Cformat, (uInt32) *((double *) binary));
	  break;
	case CDF_REAL4:
	case CDF_FLOAT:
	  if (NegativeZeroReal8((double *) binary))
	    EncodeNegativeZero (text, Cformat);
	  else
	    sprintf (text, Cformat, (float) *((double *) binary));
	  break;
	case CDF_REAL8:
	case CDF_DOUBLE:
	  if (NegativeZeroReal8((double *) binary))
	    EncodeNegativeZero (text, Cformat);
	  else
	    sprintf (text, Cformat, (double) *((double *) binary));
	  break;
      }
      break;
  }
  return strlen(text);
}

/******************************************************************************
* FtoCformat.
* Returns TRUE if valid FORTRAN format specification.
******************************************************************************/

static Logical FtoCformat (dataType, Fformat, CdataType, Cformat)
long dataType;          /* Value's actual data type.  This should never be
			   CDF_EPOCH. */
char *Fformat;          /* FORTRAN format specification. */
long *CdataType;        /* Data type implied by C format specification. */
char *Cformat;          /* C format specification. */
{
  int nFound;
  int w, m, d;
  /****************************************************************************
  * Change `Fformat' to point to first non-blank, non-digit, non-`('
  * character.  This will skip over a Fortran repeat count (eg. the `20' in
  * `20F8.4' or `20(F8.4)').
  ****************************************************************************/
  while (*Fformat != NUL && (*Fformat == '(' ||
			     Spacing(*Fformat) ||
			     Decimal(*Fformat))) Fformat++;
  if (*Fformat == NUL) return FALSE;
  /****************************************************************************
  * Encode C format specification.
  ****************************************************************************/
  switch (Fformat[0]) {
    /**************************************************************************
    * Integer/decimal.
    **************************************************************************/
    case 'I':
    case 'i':
      nFound = sscanf (&Fformat[1], "%d.%d", &w, &m);
      switch (nFound) {
	case 0:
	  return FALSE;
	case 1:
	  switch (dataType) {
	    case CDF_BYTE:
	    case CDF_INT1:
	      sprintf (Cformat, "%%%dd", w);
	      *CdataType = CDF_INT1;
	      return TRUE;
	    case CDF_UINT1:
	      sprintf (Cformat, "%%%du", w);
	      *CdataType = CDF_UINT1;
	      return TRUE;
	    case CDF_INT2:
	      sprintf (Cformat, "%%%dd", w);
	      *CdataType = CDF_INT2;
	      return TRUE;
	    case CDF_UINT2:
	      sprintf (Cformat, "%%%du", w);
	      *CdataType = CDF_UINT2;
	      return TRUE;
	    case CDF_INT4:
	      sprintf (Cformat, "%%%d%sd", w, Int32FORMATmod);
	      *CdataType = CDF_INT4;
	      return TRUE;
	    case CDF_UINT4:
	      sprintf (Cformat, "%%%d%su", w, Int32FORMATmod);
	      *CdataType = CDF_UINT4;
	      return TRUE;
	    case CDF_REAL4:
	    case CDF_FLOAT:
	    case CDF_REAL8:
	    case CDF_DOUBLE:
	      sprintf (Cformat, "%%%d%sd", w, Int32FORMATmod);
	      *CdataType = CDF_INT4;
	      return TRUE;
	  }
	  return FALSE;
	case 2:
	  switch (dataType) {
	    case CDF_BYTE:
	    case CDF_INT1:
	      sprintf (Cformat, "%%%d.%dd", w, m);
	      *CdataType = CDF_INT1;
	      return TRUE;
	    case CDF_UINT1:
	      sprintf (Cformat, "%%%d.%du", w, m);
	      *CdataType = CDF_UINT1;
	      return TRUE;
	    case CDF_INT2:
	      sprintf (Cformat, "%%%d.%dd", w, m);
	      *CdataType = CDF_INT2;
	      return TRUE;
	    case CDF_UINT2:
	      sprintf (Cformat, "%%%d.%du", w, m);
	      *CdataType = CDF_UINT2;
	      return TRUE;
	    case CDF_INT4:
	      sprintf (Cformat, "%%%d.%d%sd", w, m, Int32FORMATmod);
	      *CdataType = CDF_INT4;
	      return TRUE;
	    case CDF_UINT4:
	      sprintf (Cformat, "%%%d.%d%su", w, m, Int32FORMATmod);
	      *CdataType = CDF_UINT4;
	      return TRUE;
	    case CDF_REAL4:
	    case CDF_FLOAT:
	    case CDF_REAL8:
	    case CDF_DOUBLE:
	      sprintf (Cformat, "%%%d.%d%sd", w, m, Int32FORMATmod);
	      *CdataType = CDF_INT4;
	      return TRUE;
	  }
	  return FALSE;
      }
      return FALSE;
    /**************************************************************************
    * Integer/octal.
    **************************************************************************/
    case 'O':
    case 'o':
      nFound = sscanf (&Fformat[1], "%d.%d", &w, &m);
      switch (nFound) {
	case 0:
	  return FALSE;
	case 1:
	  switch (dataType) {
	    case CDF_BYTE:
	    case CDF_INT1:
	      sprintf (Cformat, "%%%do", w);
	      *CdataType = CDF_UINT1;
	      return TRUE;
	    case CDF_UINT1:
	      sprintf (Cformat, "%%%do", w);
	      *CdataType = CDF_UINT1;
	      return TRUE;
	    case CDF_INT2:
	      sprintf (Cformat, "%%%do", w);
	      *CdataType = CDF_UINT2;
	      return TRUE;
	    case CDF_UINT2:
	      sprintf (Cformat, "%%%do", w);
	      *CdataType = CDF_UINT2;
	      return TRUE;
	    case CDF_INT4:
	      sprintf (Cformat, "%%%d%so", w, Int32FORMATmod);
	      *CdataType = CDF_UINT4;
	      return TRUE;
	    case CDF_UINT4:
	      sprintf (Cformat, "%%%d%so", w, Int32FORMATmod);
	      *CdataType = CDF_UINT4;
	      return TRUE;
	    case CDF_REAL4:
	    case CDF_FLOAT:
	    case CDF_REAL8:
	    case CDF_DOUBLE:
	      sprintf (Cformat, "%%%d%so", w, Int32FORMATmod);
	      *CdataType = CDF_UINT4;
	      return TRUE;
	  }
	  return FALSE;
	case 2:
	  switch (dataType) {
	    case CDF_BYTE:
	    case CDF_INT1:
	      sprintf (Cformat, "%%%d.%do", w, m);
	      *CdataType = CDF_UINT1;
	      return TRUE;
	    case CDF_UINT1:
	      sprintf (Cformat, "%%%d.%do", w, m);
	      *CdataType = CDF_UINT1;
	      return TRUE;
	    case CDF_INT2:
	      sprintf (Cformat, "%%%d.%do", w, m);
	      *CdataType = CDF_UINT2;
	      return TRUE;
	    case CDF_UINT2:
	      sprintf (Cformat, "%%%d.%do", w, m);
	      *CdataType = CDF_UINT2;
	      return TRUE;
	    case CDF_INT4:
	      sprintf (Cformat, "%%%d.%d%so", w, m, Int32FORMATmod);
	      *CdataType = CDF_UINT4;
	      return TRUE;
	    case CDF_UINT4:
	      sprintf (Cformat, "%%%d.%d%so", w, m, Int32FORMATmod);
	      *CdataType = CDF_UINT4;
	      return TRUE;
	    case CDF_REAL4:
	    case CDF_FLOAT:
	    case CDF_REAL8:
	    case CDF_DOUBLE:
	      sprintf (Cformat, "%%%d.%d%so", w, m, Int32FORMATmod);
	      *CdataType = CDF_UINT4;
	      return TRUE;
	  }
	  return FALSE;
      }
      return FALSE;
    /**************************************************************************
    * Integer/hexadecimal.
    **************************************************************************/
    case 'Z':
    case 'z':
      nFound = sscanf (&Fformat[1], "%d.%d", &w, &m);
      switch (nFound) {
	case 0:
	  return FALSE;
	case 1:
	  switch (dataType) {
	    case CDF_BYTE:
	    case CDF_INT1:
	      sprintf (Cformat, "%%%dX", w);
	      *CdataType = CDF_UINT1;
	      return TRUE;
	    case CDF_UINT1:
	      sprintf (Cformat, "%%%dX", w);
	      *CdataType = CDF_UINT1;
	      return TRUE;
	    case CDF_INT2:
	      sprintf (Cformat, "%%%dX", w);
	      *CdataType = CDF_UINT2;
	      return TRUE;
	    case CDF_UINT2:
	      sprintf (Cformat, "%%%dX", w);
	      *CdataType = CDF_UINT2;
	      return TRUE;
	    case CDF_INT4:
	      sprintf (Cformat, "%%%d%sX", w, Int32FORMATmod);
	      *CdataType = CDF_UINT4;
	      return TRUE;
	    case CDF_UINT4:
	      sprintf (Cformat, "%%%d%sX", w, Int32FORMATmod);
	      *CdataType = CDF_UINT4;
	      return TRUE;
	    case CDF_REAL4:
	    case CDF_FLOAT:
	    case CDF_REAL8:
	    case CDF_DOUBLE:
	      sprintf (Cformat, "%%%d%sX", w, Int32FORMATmod);
	      *CdataType = CDF_UINT4;
	      return TRUE;
	  }
	  return FALSE;
	case 2:
	  switch (dataType) {
	    case CDF_BYTE:
	    case CDF_INT1:
	      sprintf (Cformat, "%%%d.%dX", w, m);
	      *CdataType = CDF_UINT1;
	      return TRUE;
	    case CDF_UINT1:
	      sprintf (Cformat, "%%%d.%dX", w, m);
	      *CdataType = CDF_UINT1;
	      return TRUE;
	    case CDF_INT2:
	      sprintf (Cformat, "%%%d.%dX", w, m);
	      *CdataType = CDF_UINT2;
	      return TRUE;
	    case CDF_UINT2:
	      sprintf (Cformat, "%%%d.%dX", w, m);
	      *CdataType = CDF_UINT2;
	      return TRUE;
	    case CDF_INT4:
	      sprintf (Cformat, "%%%d.%d%sX", w, m, Int32FORMATmod);
	      *CdataType = CDF_UINT4;
	      return TRUE;
	    case CDF_UINT4:
	      sprintf (Cformat, "%%%d.%d%sX", w, m, Int32FORMATmod);
	      *CdataType = CDF_UINT4;
	      return TRUE;
	    case CDF_REAL4:
	    case CDF_FLOAT:
	    case CDF_REAL8:
	    case CDF_DOUBLE:
	      sprintf (Cformat, "%%%d.%d%sX", w, m, Int32FORMATmod);
	      *CdataType = CDF_UINT4;
	      return TRUE;
	  }
	  return FALSE;
      }
      return FALSE;
    /**************************************************************************
    * Floating-point/non-scientific notation (which is called...
    **************************************************************************/
    case 'F':
    case 'f':
      nFound = sscanf (&Fformat[1], "%d.%d", &w, &d);
      switch (nFound) {
	case 2:
	  sprintf (Cformat, "%%%d.%df", w, d);
	  *CdataType = CDF_DOUBLE;
	  return TRUE;
      }
      return FALSE;
    /**************************************************************************
    * Floating-point/scientific notation.
    **************************************************************************/
    case 'E':
    case 'e':
    case 'D':
    case 'd':
      nFound = sscanf (&Fformat[1], "%d.%d", &w, &d);
      switch (nFound) {
	case 2:
	  sprintf (Cformat, "%%%d.%de", w, d);
	  *CdataType = CDF_DOUBLE;
	  return TRUE;
      }
      return FALSE;
    /**************************************************************************
    * Floating-point/notation depends on value.
    **************************************************************************/
    case 'G':
    case 'g':
      nFound = sscanf (&Fformat[1], "%d.%d", &w, &d);
      switch (nFound) {
	case 2:
	  sprintf (Cformat, "%%%d.%dg", w, d);
	  *CdataType = CDF_DOUBLE;
	  return TRUE;
      }
      return FALSE;
    /**************************************************************************
    * Character.
    **************************************************************************/
    case 'A':
    case 'a':
      nFound = sscanf (&Fformat[1], "%d", &w);
      switch (nFound) {
	case 1:
	  sprintf (Cformat, "%%%d.%ds", w, w);
	  *CdataType = CDF_CHAR;
	  return TRUE;
      }
      return FALSE;
  }
  return FALSE;                         /* Unknown FORTRAN format character. */
}

/******************************************************************************
* GetCdataType.
*   Given a C format specification, returns the corresponding C data type.
******************************************************************************/

static long GetCdataType (Cformat, CdataType)
char *Cformat;          /* C format specification. */
long *CdataType;        /* Corresponding C data type. */
{
  char *ptrB;           /* Beginning of specification. */
  char *ptrE;           /* End of specification. */
  /****************************************************************************
  * Find beginning of specification.
  ****************************************************************************/
  ptrB = Cformat;
  while (*ptrB != NUL && Spacing(*ptrB)) ptrB++;
  if (*ptrB != '%') return FALSE;
  /****************************************************************************
  * Find end of specification.
  ****************************************************************************/
  ptrE = Cformat + strlen(Cformat) - 1;
  while (ptrE != ptrB && Spacing(*ptrE)) ptrE--;
  if (ptrE == ptrB) return FALSE;
  /****************************************************************************
  * Choose a data type based on the conversion character and possible modifier.
  ****************************************************************************/
  switch (*ptrE) {
    case 'd':
    case 'i':
      switch (*(ptrE-1)) {
	case 'l':
	  *CdataType = CDF_INT4;
	  break;
	case 'h':
	  *CdataType = CDF_INT2;
	  break;
	default:
#if defined(dos)
	  *CdataType = CDF_INT2;
#else
	  *CdataType = CDF_INT4;
#endif
	  break;
      }
      break;
    case 'x':
    case 'X':
    case 'o':
    case 'u':
      switch (*(ptrE-1)) {
	case 'l':
	  *CdataType = CDF_UINT4;
	  break;
	case 'h':
	  *CdataType = CDF_UINT2;
	  break;
	default:
#if defined(dos)
	  *CdataType = CDF_UINT2;
#else
	  *CdataType = CDF_UINT4;
#endif
	  break;
      }
      break;
    case 'c':
    case 's':
      *CdataType = CDF_CHAR;
      break;
    case 'f':
    case 'e':
    case 'E':
    case 'g':
    case 'G':
      *CdataType = CDF_DOUBLE;
      break;
    default:
      return FALSE;     /* Unknown C format description. */
  }
  return TRUE;
}

/******************************************************************************
* EncodeDimensionality.  Returns width of encoded dimensionality
******************************************************************************/

int EncodeDimensionality (string, numDims, dimSizes)
char *string;
long numDims;
long *dimSizes;
{
  int dimN;
  sprintf (string, "%ld:[", numDims);
  for (dimN = 0; dimN < numDims; dimN++)
     sprintf (&(string[strlen(string)]), "%s%ld", (dimN > 0 ? "," : ""),
	      dimSizes[dimN]);
  strcatX (string, "]", 0);
  return strlen(string);
}

/******************************************************************************
* EncodeVariances.  Returns width of encoded variances.
******************************************************************************/

int EncodeVariances (string, recVary, numDims, dimVarys)
char *string;
long recVary;
long numDims;
long *dimVarys;
{
  int dimN;
  strcpyX (string, TFvarianceToken(recVary), 0);
  strcatX (string, "/", 0);
  for (dimN = 0; dimN < numDims; dimN++)
     strcatX (string, TFvarianceToken(dimVarys[dimN]), 0);
  return strlen(string);
}

/******************************************************************************
* EncodeRecordIndices.
* Uses `*' for record/dimension variances of NOVARY.  Returns width of
* record/indices.
******************************************************************************/

int EncodeRecordIndices (string, recNum, recVary, numDims, indices, dimVarys)
char *string;
long recNum;
long recVary;
long numDims;
long indices[];
long dimVarys[];
{
  int dimN;
  if (recVary)
    sprintf (string, "%ld", recNum + 1);
  else
    strcpyX (string, "*", 0);
  strcatX (string, ":[", 0);
  for (dimN = 0; dimN < numDims; dimN++) {
     if (dimN != 0) strcatX (string, ",", 0);
     if (dimVarys[dimN])
       sprintf (&string[strlen(string)], "%ld", indices[dimN] + 1);
     else
       strcatX (string, "*", 0);
  }
  strcatX (string, "]", 0);
  return strlen(string);
}

/******************************************************************************
* EncodeRecordIndicesVARY.  Force record/dimension variances to VARY.
******************************************************************************/

int EncodeRecordIndicesVARY (string, recNum, numDims, indices)
char *string;
long recNum;
long numDims;
long indices[];
{
  static long recVary = VARY;
  static long dimVarys[CDF_MAX_DIMS] = { VARY, VARY, VARY, VARY, VARY,
					 VARY, VARY, VARY, VARY, VARY };
  EncodeRecordIndices (string, recNum, recVary, numDims, indices, dimVarys);
  return strlen(string);
}

/*****************************************************************************
* OnlineHelpFP.
*
*  UNIX: The full pathname of the executable is not passed in via `argv[0]'.
*  The command which was entered on the command line is passed in (aliases
*  are, however, evaluated).
*
******************************************************************************/

FILE *OnlineHelpFP (filename, execpath)
char *filename;                         /* Name of help file. */
char *execpath;                         /* Pathname of executable (argv[0]). */
{
  FILE *fp;
  char pathname[DU_MAX_PATH_LEN+1];
#if !defined(mac)
  char execname[DU_MAX_NAME_LEN+1];
  char *envPtr;
#endif
#if defined(mac)
  execpath;                     /* Quites the MPW C compiler. */
#endif
  /****************************************************************************
  * Try to use logical name (VMS) or environment variable (UNIX/POSIXshell,
  * DOS, & Windows).
  ****************************************************************************/
#if defined(vms)
  strcpyX (pathname, "CDF$HELP:", DU_MAX_PATH_LEN);
  AppendToDir (pathname, filename);
  fp = fopen (pathname, "r");
  if (fp != NULL) return fp;
#endif
#if defined(unix) || defined(dos) || defined(posixSHELL) || defined(win32)
  envPtr = getenv ("CDF_HELP");
  if (envPtr != NULL) {
    strcpyX (pathname, envPtr, DU_MAX_PATH_LEN);
    AppendToDir (pathname, filename);
    fp = fopen (pathname, "r");
    if (fp != NULL) return fp;
  }
#endif
  /***************************************************************************
  * If not on a Macintosh...
  * Try relative paths from location of executable in the following order...
  *  1. From standard executable/binary directory.
  *  2. From source directory.
  *  3. Same directory as that containing the executable.
  ***************************************************************************/
#if !defined(mac)
  if (execpath != NULL) {
    if (execpath[0] != NUL) {
      ParsePath (execpath, pathname, execname);
#if defined(vms)
      pathname[strlen(pathname)-1] = NUL;   /* Wipes out trailing `]'. */
      strcatX (pathname, "-.HELP]", DU_MAX_PATH_LEN);
#endif
#if defined(unix) || defined(posixSHELL)
      AppendToDir (pathname, "../lib/cdf/help");
#endif
#if defined(dos) || defined(win32)
      AppendToDir (pathname, "..\\help");
#endif
      AppendToDir (pathname, filename);
      fp = fopen (pathname, "r");
      if (fp != NULL) return fp;
      ParsePath (execpath, pathname, execname);
#if defined(vms)
      pathname[strlen(pathname)-1] = NUL;   /* Wipes out trailing `]'. */
      strcatX (pathname, "-.-.HELP]", DU_MAX_PATH_LEN);
#endif
#if defined(unix) || defined(posixSHELL)
      AppendToDir (pathname, "../help");
#endif
#if defined(dos) || defined(win32)
      AppendToDir (pathname, "..\\..\\help");
#endif
      AppendToDir (pathname, filename);
      fp = fopen (pathname, "r");
	  if (fp != NULL) return fp;
      ParsePath (execpath, pathname, execname);
      AppendToDir (pathname, filename);
      fp = fopen (pathname, "r");
      if (fp != NULL) return fp;
    }
  }
#endif
  /***************************************************************************
  * If on a Macintosh...try relative (partial) paths to the `help' file.
  ***************************************************************************/
#if defined(mac)
  strcpyX (pathname, "::help", DU_MAX_PATH_LEN);
  AppendToDir (pathname, filename);
  fp = fopen (pathname, "r");
  if (fp != NULL) return fp;
  strcpyX (pathname, ":::help", DU_MAX_PATH_LEN);
  AppendToDir (pathname, filename);
  fp = fopen (pathname, "r");
  if (fp != NULL) return fp;
#endif
  /***************************************************************************
  * If on a UNIX/POSIXshell or DOS/Windows system, assume that the `bin'
  * directory has been added to the `path' and try a relative path from each
  * `path' entry.
  ***************************************************************************/
#if defined(unix) || defined(posixSHELL) || defined(dos) || defined(win32)
#if defined(unix) || defined(posixSHELL)
#define SEP_CHAR ':'
#else
#define SEP_CHAR ';'
#endif
  envPtr = getenv ("PATH");
  if (envPtr != NULL) {
    char *path = (char *) AllocateMemory (strlen(envPtr) + 1, FatalError);
    char *p1 = path;
    strcpyX (path, envPtr, 0);
    for (;;) {
       char *sepPtr = strchr (p1, SEP_CHAR);
       if (sepPtr != NULL) *sepPtr = NUL;
       strcpyX (pathname, p1, DU_MAX_PATH_LEN);
#if defined(unix) || defined(posixSHELL)
       AppendToDir (pathname, "../lib/cdf/help");
#else
       AppendToDir (pathname, "..\\help");
#endif
       AppendToDir (pathname, filename);
       fp = fopen (pathname, "r");
       if (fp != NULL) {
	 FreeMemory (path, FatalError);
	 return fp;
       }
       if (sepPtr == NULL) break;
       p1 = sepPtr + 1;
    }
    FreeMemory (path, FatalError);
  }
#endif
  /***************************************************************************
  * Try pathes from the current directory.
  ***************************************************************************/
#if defined(vms)
  strcpyX (pathname, "[-.HELP]", DU_MAX_PATH_LEN);
#endif
#if defined(unix) || defined(posixSHELL)
  strcpyX (pathname, "../help", DU_MAX_PATH_LEN);
#endif
#if defined(dos) || defined(win32)
  strcpyX (pathname, "..\\help", DU_MAX_PATH_LEN);
#endif
  AppendToDir (pathname, filename);
  fp = fopen (pathname, "r");
  if (fp != NULL) return fp;
#if defined(vms)
  strcpyX (pathname, "[-.-.HELP]", DU_MAX_PATH_LEN);
#endif
#if defined(unix) || defined(posixSHELL)
  strcpyX (pathname, "../../help", DU_MAX_PATH_LEN);
#endif
#if defined(dos) || defined(win32)
  strcpyX (pathname, "..\\..\\help", DU_MAX_PATH_LEN);
#endif
  AppendToDir (pathname, filename);
  fp = fopen (pathname, "r");
  if (fp != NULL) return fp;
#if defined(vms)
  strcpyX (pathname, "[-.-.-.HELP]", DU_MAX_PATH_LEN);
#endif
#if defined(unix) || defined(posixSHELL)
  strcpyX (pathname, "../../../help", DU_MAX_PATH_LEN);
#endif
#if defined(dos) || defined(win32)
  strcpyX (pathname, "..\\..\\..\\help", DU_MAX_PATH_LEN);
#endif
  AppendToDir (pathname, filename);
  fp = fopen (pathname, "r");
  if (fp != NULL) return fp;
  /***************************************************************************
  * Try current directory.
  ***************************************************************************/
  fp = fopen (filename, "r");
  if (fp != NULL) return fp;
  /***************************************************************************
  * Give up.
  ***************************************************************************/
  return NULL;
}

/*****************************************************************************
* PageOLH.
*****************************************************************************/

void PageOLH (olhFile, argv0)
char *olhFile;
char *argv0;
{
  FILE *fp;
  char line[MAX_SCREENLINE_LEN+1+1];
  int nestLevel = 0;                    /* Depth into `#ifos's. */
  int osMask = 0;                       /* When 0, display line.  Note that bit
					   0 is not used (eg. nesting depth of
					   1 uses bit 1, etc.). */
#if defined(vms)
  static char thisOS[] = "vms";
#endif
#if defined(unix) || defined(posixSHELL)
  static char thisOS[] = "unix";
#endif
#if defined(dos)
  static char thisOS[] = "dos";
#endif
#if defined(mac)
  static char thisOS[] = "mac";
#endif
#if defined(win32)
  static char thisOS[] = "win";
#endif
  static char *openErrorMsg = {
    "Error opening online help file - contact CDFsupport.\n"
  };
  static char *syntaxErrorMsg = {
    "Syntax error in online help file - contact CDFsupport.\n"
  };
  /****************************************************************************
  * Open online help file.
  ****************************************************************************/
  fp = OnlineHelpFP (olhFile, argv0);
  if (fp == NULL) {
    WriteOut (stdout, openErrorMsg);
    return;
  }
  /****************************************************************************
  * Read online help file - passing appropriate lines through for display.
  ****************************************************************************/
  while (fgets(line,MAX_SCREENLINE_LEN+1+1,fp) != NULL) {
    /**************************************************************************
    * Check if an operating system directive.  If not, display the line if
    * all of the bits in the operating system mask are clear.
    **************************************************************************/
    if (line[0] == '#') {
      line[strlen(line)-1] = NUL;
      /************************************************************************
      * Check for a `#ifos' directive.  If this operating system is not
      * specified, then set the bit in the operating system mask for this
      * nesting level.
      ************************************************************************/
      if (!strncmp(line,"#ifos",5)) {
	nestLevel++;
	if (strstr(line,thisOS) == NULL) SETBIT (osMask, nestLevel);
	continue;
      }
      /************************************************************************
      * Check for a `#else' directive.  Simply flip the bit in the operating
      * system mask for this nesting level.
      ************************************************************************/
      if (!strcmp(line,"#else")) {
	if (nestLevel < 1) {
	  WriteOut (stdout, syntaxErrorMsg);
	  break;
	}
	FLPBIT (osMask, nestLevel);
	continue;
      }
      /************************************************************************
      * Check for a `#endos' directive.  Clear the bit in the operating system
      * mask for this nesting level and decrement the nesting level.
      ************************************************************************/
      if (!strcmp(line,"#endos")) {
	if (nestLevel < 1) {
	  WriteOut (stdout, syntaxErrorMsg);
	  break;
	}
	CLRBIT (osMask, nestLevel);
	nestLevel--;
	continue;
      }
      /************************************************************************
      * An unknown directive has been encountered - assume it to be a comment.
      ************************************************************************/
      continue;
    }
    else {
      if (osMask == 0) WriteOut (stdout, line);
    }
  }
  if (nestLevel != 0) WriteOut (stdout, syntaxErrorMsg);
  fclose (fp);
  return;
}

/******************************************************************************
* OutputWithMargin.
*   Outputs a long text string to multiple lines of a file (or stdout)
* breaking the lines at blanks if possible.
******************************************************************************/

void OutputWithMargin (fp, text, maxLen, marginLen)
FILE *fp;
char *text;
int maxLen;     /* Maximum length of line (including margin). */
int marginLen;  /* Length of margin preceeding text on each line. */
{
  char *ptr = text, *ptrT;
  int maxLenT = maxLen - marginLen;     /* Maximum number of characters per
					   line (excluding the margin). */
  for (;;) {
     /*************************************************************************
     * If the remaining characters will fit on a line, print them and return.
     * This also covers the case where the entire text will fit on one line.
     *************************************************************************/
     if (strlen(ptr) <= (size_t) maxLenT) {
       if (marginLen > 0) Ncharacters (fp, marginLen, (int) ' ');
       WriteOut (fp, ptr);
       WriteOut (fp, "\n");
       return;
     }
     /*************************************************************************
     * Otherwise, find a blank character at which to break the characters.  The
     * blank is not printed on either line.
     *************************************************************************/
     for (ptrT = ptr + maxLenT; ptrT != ptr; ptrT--) {
	if (*ptrT == ' ') {
	  char *string = NULedString (ptr, (int) (ptrT - ptr));
	  if (marginLen > 0) Ncharacters (fp, marginLen, (int) ' ');
	  WriteOut (fp, string);
	  WriteOut (fp, "\n");
	  ptr = ptrT + 1;
	  FreeMemory (string, FatalError);
	  break;
	}
     }
     /*************************************************************************
     * If a blank could not be found, break the characters at the end of the
     * maximum number per line.
     *************************************************************************/
     if (ptrT == ptr) {
       char *string = NULedString (ptr, maxLenT);
       if (marginLen > 0) Ncharacters (fp, marginLen, (int) ' ');
       WriteOut (fp, string);
       WriteOut (fp, "\n");
       ptr += maxLenT;
       FreeMemory (string, FatalError);
     }
  }
}

/******************************************************************************
* InitPctLog.
******************************************************************************/

void InitPctLog (lastPct, pctOn)
int *lastPct;
Logical *pctOn;
{
  WriteOut (stdout, "..0%");
  *lastPct = 0;
  *pctOn = TRUE;
  return;
}

/******************************************************************************
* UpdatePctLog.
******************************************************************************/

void UpdatePctLog (pct, lastPct)
int pct;
int *lastPct;
{
  if (pct != *lastPct) {
    char string[10+1];
    sprintf (string, "\b\b\b\b%s%d%%",
	     (pct < 10 ? ".." : (pct < 100 ? "." : "")), pct);
    WriteOut (stdout, string);
    *lastPct = pct;
  }
  return;
}

/******************************************************************************
* RefreshPctLog.
******************************************************************************/

void RefreshPctLog (lastPct)
int lastPct;
{
  char string[6+1];
  sprintf (string, "%s%d%%",
	   (lastPct < 10 ? ".." : (lastPct < 100 ? "." : "")), lastPct);
  WriteOut (stdout, string);
  return;
}

/******************************************************************************
* CleanupPctLog.
*   100% is output in case there were no values (in which case 0% would be
* currently displayed).
******************************************************************************/

void CleanupPctLog (pctOn)
Logical *pctOn;
{
  WriteOut (stdout, "\b\b\b\b100%\n");
  *pctOn = FALSE;
  return;
}

/******************************************************************************
* Ncharacters.
*   Outputs a specified number of a selected character to a file pointer.
******************************************************************************/

void Ncharacters (fp, nChars, chr)
FILE *fp;
int nChars;
int chr;
{
  int i; char string[1+1];
  string[0] = (char) chr;
  string[1] = NUL;
  for (i = 0; i < nChars; i++) WriteOut (fp, string);
  return;
}

/******************************************************************************
* CatNcharacters.
*   Concatenates some number of a specified character to a string.
******************************************************************************/

void CatNcharacters (string, nChars, chr)
char *string;
int nChars;
int chr;
{
  int i;
  for (i = 0; i < nChars; i++) catchrX (string, chr, 0);
  return;
}

/******************************************************************************
* CpyNcharacters.
*   Copies some number of a specified character to a string.
******************************************************************************/

void CpyNcharacters (string, nChars, chr)
char *string;
int nChars;
int chr;
{
  int i;
  for (i = 0; i < nChars; i++) string[i] = (char) chr;
  string[nChars] = NUL;
  return;
}

/******************************************************************************
* TFqualifier.
******************************************************************************/

Logical TFqualifier (qop, variable, Tx, Fx, defaultTF, conflictText)
QOP *qop;
Logical *variable;
int Tx;
int Fx;
Logical defaultTF;
char *conflictText;
{
  int count = 0;
  if (qop->qualEntered[Tx]) count++;
  if (qop->qualEntered[Fx]) count++;
  switch (count) {
    case 0:
      *variable = defaultTF;
      return TRUE;
    case 1:
      *variable = (qop->qualEntered[Tx] ? TRUE : FALSE);
      return TRUE;
    default: {
      char tempS[MAX_MESSAGE_TEXT_LEN+1];
      sprintf (tempS, "Conflicting qualifiers (%s & no%s).",
	       conflictText, conflictText);
      DisplayError (tempS);
      return FALSE;
    }
  }
}

/******************************************************************************
* S2qualifierLong.
******************************************************************************/

Logical S2qualifierLong (qop, variable, S1x, S1, S2x, S2, defaultS,
			 conflictText)
QOP *qop;
long *variable;
int S1x;
long S1;
int S2x;
long S2;
long defaultS;
char *conflictText;
{
  int count = 0;
  if (qop->qualEntered[S1x]) count++;
  if (qop->qualEntered[S2x]) count++;
  switch (count) {
    case 0:
      *variable = defaultS;
      return TRUE;
    case 1:
      *variable = (qop->qualEntered[S1x] ? S1 : S2);
      return TRUE;
    default: {
      char tempS[MAX_MESSAGE_TEXT_LEN+1];
      sprintf (tempS, "Conflicting qualifiers (%s).", conflictText);
      DisplayError (tempS);
      return FALSE;
    }
  }
}

/******************************************************************************
* S3qualifierLong.
******************************************************************************/

Logical S3qualifierLong (qop, variable, S1x, S1, S2x, S2, S3x, S3, defaultS,
			 conflictText)
QOP *qop;
long *variable;
int S1x;
long S1;
int S2x;
long S2;
int S3x;
long S3;
long defaultS;
char *conflictText;
{
  int count = 0;
  if (qop->qualEntered[S1x]) count++;
  if (qop->qualEntered[S2x]) count++;
  if (qop->qualEntered[S3x]) count++;
  switch (count) {
    case 0:
      *variable = defaultS;
      return TRUE;
    case 1:
      *variable = BOO(qop->qualEntered[S1x],S1,
		      BOO(qop->qualEntered[S2x],S2,S3));
      return TRUE;
    default: {
      char tempS[MAX_MESSAGE_TEXT_LEN+1];
      sprintf (tempS, "Conflicting qualifiers (%s).", conflictText);
      DisplayError (tempS);
      return FALSE;
    }
  }
}

/******************************************************************************
* BlankPadRight.
*   Concatenate blanks to the right end of a string.
******************************************************************************/

void BlankPadRight (string, count)
char *string;
int count;
{
  int i;
  for (i = 0; i < count; i++) catchrX (string, (int) ' ', 0);
  return;
}

/******************************************************************************
* GetFormatEntry.
* It is assumed that the current CDF has been selected.
******************************************************************************/

CDFstatus GetFormatEntry (Z, varNum, format)
Logical Z;
long varNum;
char **format;
{
  CDFstatus pStatus = CDF_OK, status; long dataType, numElems;
  status = CDFlib (SELECT_, ATTR_NAME_, "FORMAT",
			    BOO(Z,zENTRY_,rENTRY_), varNum,
		   GET_, BOO(Z,zENTRY_DATATYPE_,rENTRY_DATATYPE_), &dataType,
			 BOO(Z,zENTRY_NUMELEMS_,rENTRY_NUMELEMS_), &numElems,
		   NULL_);
  switch (status) {
    case NO_SUCH_ATTR:
    case NO_SUCH_ENTRY:
      *format = NULL;
      return pStatus;
    default:
      if (!sX(status,&pStatus)) {
	*format = NULL;
	return pStatus;
      }
  }
  if (!STRINGdataType(dataType)) {
    *format = NULL;
    return pStatus;
  }
  *format = (char *) AllocateMemory ((size_t) (numElems + 1),
				     FatalError);
  status = CDFlib (GET_, BOO(Z,zENTRY_DATA_,rENTRY_DATA_), *format,
		   NULL_);
  if (!sX(status,&pStatus)) {
    FreeMemory (*format, FatalError);
    *format = NULL;
    return pStatus;
  }
  (*format)[(int)numElems] = NUL;
  return pStatus;
}

/******************************************************************************
* ParseOptionList.
******************************************************************************/

Logical ParseOptionList (nTokens, tokens, list, present)
int nTokens;
char *tokens[];
char *list;
Logical present[];
{
  int tokenN, matchN; size_t tokenLen;
  char token[MAX_TOKEN_LEN+1], *ptr1, *ptr2, terminator;
  /****************************************************************************
  * Assume initially that none of the tokens were found.
  ****************************************************************************/
  for (tokenN = 0; tokenN < nTokens; tokenN++) present[tokenN] = FALSE;
  /****************************************************************************
  * Check that the entered list of tokens is not a NUL string, that there are
  * some possible tokens to match, etc.
  ****************************************************************************/
  if (NULstring(list)) return TRUE;     /* Obviously, no tokens were found. */
  if (nTokens < 1) return FALSE;        /* Probably a logic error. */
  /****************************************************************************
  * Scan the entered list of tokens searching for matches with the list of
  * possible tokens.  First determine the starting character position and the
  * ending delimiter.
  ****************************************************************************/
  switch (list[0]) {
    case '(':           /* VMS-style. */
      terminator = ')';
      ptr1 = list + 1;
      break;
    case '"':		/* UNIX-style on a Mac (double quotes not stripped). */
      terminator = '"';
      ptr1 = list + 1;
      break;
    default:            /* UNIX-style on a UNIX machine or IBM PC. */
      terminator = NUL;
      ptr1 = list;
      break;
  }
  for (;;) {
     /*************************************************************************
     * Find beginning of the next token in list (skipping past any leading
     * blanks).  If the end of the list is reached instead, then there are
     * no more tokens.
     *************************************************************************/
     while (*ptr1 == ' ') ptr1++;
     if (*ptr1 == terminator) return TRUE;
     /*************************************************************************
     * Find the end of the token and copy.  The token is ended by either a
     * blank, comma, or the terminator.
     *************************************************************************/
     ptr2 = ptr1 + 1;
     while (*ptr2 != ' ' && *ptr2 != ',' && *ptr2 != terminator) ptr2++;
     tokenLen = (size_t) (ptr2 - ptr1);
     strcpyX (token, ptr1, MINIMUM(tokenLen,MAX_TOKEN_LEN));
     /*************************************************************************
     * Search possible tokens for a match.  If more than one match is found,
     * return an error.
     *************************************************************************/
     for (matchN = -1, tokenN = 0; tokenN < nTokens; tokenN++)
	if (strncmpIgCase(token,tokens[tokenN],strlen(token)) == 0)
	  if (matchN == -1)
	    matchN = tokenN;
	  else
	    return FALSE;
     if (matchN == -1)
       return FALSE;
     else
       present[matchN] = TRUE;
     /*************************************************************************
     * Setup to search for next token in list.
     *************************************************************************/
     switch (*ptr2) {
       case ' ':
	 /*********************************************************************
	 * The last token was ended by a blank.  Check to see if a comma is
	 * the next non-blank character.  If so, the next token begins one
	 * character beyond the comma (leading blanks will be skipped when
	 * the beginning of that token is located).  Otherwise, the next token
	 * begins at the non-blank character.
	 *********************************************************************/
	 ptr1 = ptr2 + 1;
	 while (*ptr1 == ' ') ptr1++;
	 if (*ptr1 == ',') ptr1++;
	 break;
       case ',':
	 /*********************************************************************
	 * The last token was ended by a comma.  The next token begins one
	 * character beyond the comma.
	 *********************************************************************/
	 ptr1 = ptr2 + 1;
	 break;
       default:
	 /*********************************************************************
	 * The terminator must have ended the last token.
	 *********************************************************************/
	 return TRUE;
     }
  }
}

/******************************************************************************
* NULedString.
*    Returns a pointer to a NUL-terminated string that was allocated here but
* not freed.  The caller must free the string when no longer needed.  Also,
* `len'+1 characters will be allocated for the new string (and copied to from
* the existing string) regardless of whether or not a NUL appears in the first
* `len' characters.
******************************************************************************/

char *NULedString (ptr, len)
char *ptr;                      /* Pointer to the string from which to extract
				   the first `len' characters. */
size_t len;                     /* Number of characters to extract.  This does
				   not include the terminating NUL that will
				   be appended to the string being returned. */
{
  char *string;
  string = (char *) AllocateMemory (len + 1, FatalError);
  memmove (string, ptr, len);
  string[len] = NUL;
  return string;
}

/******************************************************************************
* WriteOutSO.
******************************************************************************/

#if defined(STDARG)
int WriteOutSO (char *format, ...)
#else
int WriteOutSO (va_alist)
va_dcl
#endif
{
#if !defined(STDARG)
  char *format;
#endif
  int n;
  char text[MAX_oTEXT_LEN];
  va_list ap;
#if defined(STDARG)
  va_start (ap, format);
#else
  VA_START (ap);
  format = va_arg (ap, char *);
#endif
  vsprintf (text, format, ap);
  n = WriteOut (stdout, text);
  va_end (ap);
  return n;
}

/******************************************************************************
* WriteOutFP.
******************************************************************************/

#if defined(STDARG)
int WriteOutFP (FILE *fp, char *format, ...)
#else
int WriteOutFP (va_alist)
va_dcl
#endif
{
#if !defined(STDARG)
  FILE *fp;
  char *format;
#endif
  int n;
  char text[MAX_oTEXT_LEN];
  va_list ap;
#if defined(STDARG)
  va_start (ap, format);
#else
  VA_START (ap);
  fp = va_arg (ap, FILE *);
  format = va_arg (ap, char *);
#endif
  vsprintf (text, format, ap);
  n = WriteOut (fp, text);
  va_end (ap);
  return n;
}

/******************************************************************************
* WriteOut.
* Returns number of characters output.
******************************************************************************/

int WriteOut (fp, text)
FILE *fp;
char *text;
{
  /****************************************************************************
  * If the output is not going to `stdout', then assume it is going to a file
  * (and ignore paging if requested).
  ****************************************************************************/
  if (fp != stdout)
    fprintf (fp, "%s", text);
  else {
    /**************************************************************************
    * The output is going to `stdout'.  Assume that `stdout' has not been
    * redirected away from the user's terminal (to a file).  If `stdout' has
    * been redirected to a file, then hopefully the user had enough sense not
    * to request paging.
    **************************************************************************/
#if defined(mac) || defined(win32)
#if defined(mac)
    WriteOutMacSO (text);
#else
#if defined(SO)				/* Jeff doesn't like this.  This function should */
						    /* not be compiled if an FSI application. */
	WriteOutWin32 (text);
#endif
#endif
#else
    char *ptr = text, *ptrNL; size_t len;
    /**************************************************************************
    * If paging is on, first prompt for `more...' if the end of the standard
    * output screen has been reached.
    **************************************************************************/
    for (;;) {
       /***********************************************************************
       * If paging on, check if the maximum number of lines have been written.
       ***********************************************************************/
       if (pagingOn) {
	 if (soLineCount == MAX_LINES_WHEN_PAGING) {
	   char key = NUL; static char prompt[] = "\nEnter RETURN for more...";
	   WriteStringStdOut (prompt, strlen(prompt));
	   ReadCharStdIn (&key);
	   if (key != '\n') pagingOn = FALSE;
	   WriteStringStdOut ("\n\n", 2);
	   soLineCount = 0;
	 }
       }
       /***********************************************************************
       * Write the string up to the next newline character.  If there aren't
       * any(more) newline characters, write the rest of the string.
       ***********************************************************************/
       ptrNL = strchr (ptr, Nl);
       if (ptrNL == NULL) {
	 len = strlen (ptr);
	 WriteStringStdOut (ptr, len);
	 break;
       }
       else {
	 len = (int) (ptrNL - ptr + 1);
	 WriteStringStdOut (ptr, len);
	 if (pagingOn) soLineCount++;
	 ptr = ptrNL + 1;
	 if (*ptr == NUL) break;
       }
    }
    fflush (stdout);
#endif
  }
  return strlen(text);
}

/******************************************************************************
* DirList.
* If an error occurs (or no files found), zero (0) is returned.
******************************************************************************/

int DirList (dir, patternC, patternS, dirS, nameS)
char *dir;              /* In: Directory to search. */
int patternC;           /* In: Pattern count. */
char **patternS;        /* In: Patterns to search for. */
char ***dirS;           /* Out: Directories. */
char ***nameS;          /* Out: File names. */
{
int i;
int nFiles = 0;
int tNameChars = 0;
struct FILEstruct *FILEhead = NULL;
struct FILEstruct *FILEptr;
struct FILEstruct *FILEptrT;
char *dirP, *nameP;
size_t nNameChars;

#if defined(BORLANDC)
char pathX[DU_MAX_PATH_LEN + 1];
struct ffblk ffblk;
int done;
#endif

#if defined(MICROSOFTC)
char pathX[DU_MAX_PATH_LEN + 1];
struct find_t fileinfo;
int done;
#endif

#if defined(unix) || defined(posixSHELL)
DIR *dirp;
char dirX[DU_MAX_PATH_LEN + 1];
char pattern[DU_MAX_NAME_LEN+1];
#if defined(Mach)
struct direct *dp;
#else
struct dirent *dp;
#endif
#endif

#if defined(vms)
char pathX[DU_MAX_PATH_LEN + 1];
char pathFound[DU_MAX_PATH_LEN + 1];
struct dsc$descriptor_s pathDESCR;
struct dsc$descriptor_s pathFoundDESCR;
uLong context = 0;
char name[DU_MAX_NAME_LEN + 1];
uLong status;
#endif

#if defined(mac)
short vRefNum;
long dirID;
OSErr rCode;
CInfoPBRec cParms;
Str255 tempS;
char *name;
char pattern[DU_MAX_NAME_LEN+1];
#endif

#if defined(win32)
char pathX[DU_MAX_PATH_LEN + 1];
long handle;
struct _finddata_t fdata;
int done;
#endif

#if defined(win32)
  /****************************************************************************
  * Find files on Windows machine.  Note that only one directory is possible
  * per path on Windows systems.
  ****************************************************************************/
  for (i = 0; i < patternC; i++) {
     ExpandPath (dir, pathX);
     AppendToDir (pathX, patternS[i]);
     handle = _findfirst (pathX, &fdata);
     if (handle != -1) {
	done = FALSE;
        while (!done) {
          /***********************************************************************
          * Allocate/setup file structure.
          ***********************************************************************/
          FILEptr = (struct FILEstruct *) AllocateMemory (sizeof(struct FILEstruct), FatalError);
          nNameChars = strlen (fdata.name);
          FILEptr->name = (char *) AllocateMemory (nNameChars + 1, FatalError);
          strcpyX (FILEptr->name, fdata.name, nNameChars);
          tNameChars += nNameChars + 1;
          nFiles++;
          FILEptr->next = NULL;
          AddToList (&FILEhead, FILEptr);
          /***********************************************************************
          * Get next matching file.
          ***********************************************************************/
          done = _findnext (handle, &fdata);
        }
	_findclose (handle);
     }
   }
#endif

#if defined(dos)
  /****************************************************************************
  * Find files on MS-DOS machine.  Note that only one directory is possible
  * per path on MS-DOS systems.
  ****************************************************************************/
  for (i = 0; i < patternC; i++) {
     ExpandPath (dir, pathX);
     AppendToDir (pathX, patternS[i]);
#if defined(BORLANDC)
     done = findfirst (pathX, &ffblk, 0);
#endif
#if defined(MICROSOFTC)
     done = _dos_findfirst (pathX, 0, &fileinfo);
#endif
     while (!done) {
       /***********************************************************************
       * Allocate/setup file structure.
       ***********************************************************************/
       FILEptr = (struct FILEstruct *)
		 AllocateMemory (sizeof(struct FILEstruct),
				 FatalError);
#if defined(BORLANDC)
       nNameChars = strlen (ffblk.ff_name);
       FILEptr->name = (char *) AllocateMemory (nNameChars + 1, FatalError);
       strcpyX (FILEptr->name, ffblk.ff_name, nNameChars);
#endif
#if defined(MICROSOFTC)
       nNameChars = strlen (fileinfo.name);
       FILEptr->name = (char *) AllocateMemory (nNameChars + 1, FatalError);
       strcpyX (FILEptr->name, fileinfo.name, nNameChars);
#endif
       tNameChars += nNameChars + 1;
       nFiles++;
       FILEptr->next = NULL;
       AddToList (&FILEhead, FILEptr);
       /***********************************************************************
       * Get next matching file.
       ***********************************************************************/
#if defined(BORLANDC)
       done = findnext (&ffblk);
#endif
#if defined(MICROSOFTC)
       done = _dos_findnext (&fileinfo);
#endif
     }
   }
#endif

#if defined(unix) || defined(posixSHELL)
  /****************************************************************************
  * Find files on UNIX machine.  Note that only one directory is possible per
  * path on UNIX systems.
  ****************************************************************************/
  ExpandPath (dir, dirX);
  if (NULstring(dirX)) strcpyX (dirX, ".", DU_MAX_PATH_LEN);
						  /* Default to current dir. */
  dirp = opendir (dirX);
  if (dirp == NULL) return 0;
  dp = readdir (dirp);
  while (dp != NULL) {
    char *name = dp->d_name;
#if SOLARISbsdDIRUTILSbug
    name -= 2;
#endif
    for (i = 0; i < patternC; i++) {
       strcpyX (pattern, patternS[i], DU_MAX_NAME_LEN);
       if (NULstring(pattern)) strcpyX (pattern, "*", DU_MAX_NAME_LEN);
						  /* Default to all files. */
       if (PatternMatch (pattern, name)) {
	 /*********************************************************************
	 * Allocate/setup file structure.
	 *********************************************************************/
	 FILEptr = (struct FILEstruct *)
		   AllocateMemory (sizeof(struct FILEstruct),
				   FatalError);
	 nNameChars = strlen (name);
	 FILEptr->name = (char *) AllocateMemory (nNameChars + 1, FatalError);
	 strcpyX (FILEptr->name, name, nNameChars);
	 tNameChars += nNameChars + 1;
	 nFiles++;
	 FILEptr->next = NULL;
	 AddToList (&FILEhead, FILEptr);
	 break;
       }
    }
    /**************************************************************************
    * Get next file.
    **************************************************************************/
    dp = readdir (dirp);
  }
  closedir (dirp);
#endif

#if defined(vms)
  /****************************************************************************
  * Find files on VMS machine.  Note that a logical name defined to be a set of
  * two or more directories may result in files from more than one directory
  * being found.  In that case, the logical name would be returned as the name
  * of the directory (the VMS file system handles this when opening a file).
  * The actual directories are ignored.
  ****************************************************************************/
  pathFoundDESCR.dsc$w_length = sizeof(pathFound) - 1; /* Pass full string. */
  pathFoundDESCR.dsc$b_dtype = DSC$K_DTYPE_T;
  pathFoundDESCR.dsc$b_class = DSC$K_CLASS_S;
  pathFoundDESCR.dsc$a_pointer = pathFound;
  for (i = 0; i < patternC; i++) {
     ExpandPath (dir, pathX);
     AppendToDir (pathX, patternS[i]);
     pathDESCR.dsc$w_length = strlen(pathX);            /* Pass part used. */
     pathDESCR.dsc$b_dtype = DSC$K_DTYPE_T;
     pathDESCR.dsc$b_class = DSC$K_CLASS_S;
     pathDESCR.dsc$a_pointer = pathX;
     while ((status = lib$find_file (&pathDESCR, &pathFoundDESCR, &context,
				     NULL, NULL, NULL, NULL)) == RMS$_NORMAL) {
       /***********************************************************************
       * A file was found, NUL terminate and remove trailing blanks (version
       * number is left on).
       ***********************************************************************/
       pathFound[pathFoundDESCR.dsc$w_length] = NUL;    /* NUL-terminate. */
       for (i = strlen(pathFound) - 1; i >= 0 && pathFound[i] == ' '; i--) {
	  pathFound[i] = NUL;
       }
       ParsePath (pathFound, NULL, name);
       /***********************************************************************
       * Allocate/setup file structure.
       ***********************************************************************/
       FILEptr = (struct FILEstruct *)
		 AllocateMemory (sizeof(struct FILEstruct),
				 FatalError);
       nNameChars = strlen (name);
       FILEptr->name = (char *) AllocateMemory (nNameChars + 1,
						FatalError);
       strcpyX (FILEptr->name, name, nNameChars);
       tNameChars += nNameChars + 1;
       nFiles++;
       FILEptr->next = NULL;
       AddToList (&FILEhead, FILEptr);
     }
     status = lib$find_file_end (&context);
  }
#endif

#if defined(mac)
  /****************************************************************************
  * Find files on a Macintosh.  Note that only one directory is possible per
  * path on Macintosh systems.
  ****************************************************************************/
  if (!MacDirSpecified(dir,&vRefNum,&dirID)) {
    *nameS = NULL;
    *dirS = NULL;
    return 0;
  }
  cParms.hFileInfo.ioNamePtr = tempS;
  cParms.hFileInfo.ioVRefNum = vRefNum;
  cParms.hFileInfo.ioFDirIndex = 1;
  cParms.hFileInfo.ioDirID = dirID;
  rCode = PBGetCatInfo (&cParms, FALSE);
  while (rCode == noErr) {
    /**************************************************************************
    * Check if a file (rather than a directory).
    **************************************************************************/
    if (BITCLR(cParms.hFileInfo.ioFlAttrib,4)) {
      name = PtoCstr (cParms.hFileInfo.ioNamePtr);
      for (i = 0; i < patternC; i++) {
	 strcpyX (pattern, patternS[i], DU_MAX_NAME_LEN);
	 if (NULstring(pattern)) strcpyX (pattern, "*", DU_MAX_NAME_LEN);
	 if (PatternMatch(pattern,name)) {
	   /*******************************************************************
	   * Allocate/setup file structure.
	   *******************************************************************/
	   FILEptr = (struct FILEstruct *)
		     AllocateMemory (sizeof(struct FILEstruct),
				     FatalError);
	   nNameChars = strlen (name);
	   FILEptr->name = (char *) AllocateMemory (nNameChars+1,
						    FatalError);
	   strcpyX (FILEptr->name, name, nNameChars);
	   tNameChars += nNameChars + 1;
	   nFiles++;
	   FILEptr->next = NULL;
	   AddToList (&FILEhead, FILEptr);
	   break;
	 }
      }
    }
    /**************************************************************************
    * Get next file.
    **************************************************************************/
    cParms.hFileInfo.ioDirID = dirID;    /* This must be reset each time. */
    cParms.hFileInfo.ioFDirIndex++;         /* Increment to next file. */
    rCode = PBGetCatInfo (&cParms, FALSE);
  }
#endif

  /****************************************************************************
  * Build directory/file arrays.
  ****************************************************************************/
  if (nFiles > 0) {
    /**************************************************************************
    * Setup to traverse linked list.
    **************************************************************************/
    *dirS = (char **) AllocateMemory ((nFiles*sizeof(char *))+strlen(dir)+1,
				      FatalError);
    *nameS = (char **) AllocateMemory ((nFiles*sizeof(char *)) + tNameChars,
				       FatalError);
    dirP = (char *) (*dirS) + nFiles*sizeof(char *);
    nameP = (char *) (*nameS) + nFiles*sizeof(char *);
    /**************************************************************************
    * Traverse linked list of file structures.  Note that the second through
    * last directory strings actually point to the first directory string.
    **************************************************************************/
    for (FILEptr=FILEhead, i=0; FILEptr != NULL; FILEptr=FILEptr->next, i++) {
       if (i == 0) {
	 strcpyX (dirP, dir, 0);
	 (*dirS)[i] = dirP;
       }
       else
	 (*dirS)[i] = (*dirS)[i-1];
       strcpyX (nameP, FILEptr->name, 0);
       (*nameS)[i] = nameP;
       nameP += strlen(FILEptr->name) + 1;
    }
    /**************************************************************************
    * Free linked list of file structures.
    **************************************************************************/
    FILEptr = FILEhead;
    while (FILEptr != NULL) {
      FILEptrT = FILEptr;
      FILEptr = FILEptr->next;
      FreeMemory (FILEptrT->name, FatalError);
      FreeMemory (FILEptrT, FatalError);
    }
  }
  else {
    /**************************************************************************
    * No files found.
    **************************************************************************/
    *nameS = NULL;
    *dirS = NULL;
  }
  return nFiles;
}

/******************************************************************************
* AddToList.
******************************************************************************/

static void AddToList (FILEhead, FILEptr)
struct FILEstruct **FILEhead;
struct FILEstruct *FILEptr;
{
  if (*FILEhead == NULL)
    *FILEhead = FILEptr;
  else
    if (strcmp(FILEptr->name,(*FILEhead)->name) < 0) {
      FILEptr->next = *FILEhead;
      *FILEhead = FILEptr;
    }
    else {
      struct FILEstruct *FILEptrT = *FILEhead;
      for (;;) {
	 if (FILEptrT->next == NULL) {
	   FILEptrT->next = FILEptr;
	   break;
	 }
	 else
	   if (strcmp(FILEptr->name,FILEptrT->next->name) < 0) {
	     FILEptr->next = FILEptrT->next;
	     FILEptrT->next = FILEptr;
	     break;
	   }
	 FILEptrT = FILEptrT->next;
      }
    }
  return;
}

/******************************************************************************
* PatternMatch. (Only used on UNIX/POSIXshell and Macintosh systems).
******************************************************************************/

#if defined(unix) || defined(mac) || defined(posixSHELL)
static int PatternMatch (pattern, name)
char *pattern;
char *name;
{
enum typeENUM types[DU_MAX_PATTERNS];
char *strings[DU_MAX_PATTERNS];
int nTypes = 0;
int len, i, result;

for (i = 0; i < DU_MAX_PATTERNS; i++) types[i] = TBD;

for (i = 0; pattern[i] != NUL; i++)
   switch (pattern[i]) {
     case '?':
       types[nTypes++] = WILD1;
       break;
     case '*':
       if (nTypes == 0 || types[nTypes] != WILDn) types[nTypes++] = WILDn;
       break;
     default:
       if (types[nTypes] == TBD) {
	 types[nTypes] = STR;
	 strings[nTypes] = (char *) AllocateMemory (DU_MAX_NAME_LEN + 1,
						    FatalError);
	 strings[nTypes][0] = pattern[i];
	 strings[nTypes][1] = NUL;
	 nTypes++;
       }
       else {
	 len = strlen (strings[nTypes-1]);
	 strings[nTypes-1][len] = pattern[i];
	 strings[nTypes-1][len+1] = NUL;
       }
   }

result = CheckPattern (nTypes, types, strings, name);

for (i = 0; i < DU_MAX_PATTERNS; i++) {
   if (types[i] == STR) FreeMemory (strings[i], FatalError);
}

return result;
}
#endif

/******************************************************************************
* CheckPattern. (Only used on UNIX/POSIXshell and Macintosh systems).
******************************************************************************/

#if defined(unix) || defined(mac) || defined(posixSHELL)
static int CheckPattern (nTypes, types, strings, name)
int nTypes;
enum typeENUM types[];
char *strings[];
char *name;
{
int sp = 0;
int i, j;

for (i = 0; i < nTypes; i++) {
   switch (types[i]) {
     case TBD:
       /* Do nothing? */
       break;
     case STR:
       if (strncmp(&name[sp],strings[i],strlen(strings[i])) != 0)
	 return FALSE;
       else
	 sp += strlen(strings[i]);
       break;
     case WILD1:
       if (name[sp] == NUL)
	 return FALSE;
       else
	 sp++;
       break;
     case WILDn:
       if (i == nTypes-1)       /* At last type to check? */
	 return TRUE;
       else {
	 for (j = i; name[j] != NUL; j++)
	    if (CheckPattern (nTypes-i-1, &types[i+1],
			      &strings[i+1], &name[j])) return TRUE;
	 return FALSE;
       }
   }
}

if (name[sp] == NUL)
  return TRUE;
else
  return FALSE;
}
#endif

/******************************************************************************
* CDFdirList.
*    Does a `DirList' using the possible CDF extensions (`.cdf' on all
* platforms and also `.CDF', `.cdf;1', and `.CDF;1' on UNIX/POSIXshell and
* Macintosh platforms because of CD-ROM inconsistencies
******************************************************************************/

int CDFdirList (path, dirS, nameS)
char *path;
char ***dirS;
char ***nameS;
{
#if defined(unix) || defined(mac) || defined(posixSHELL)
  /****************************************************************************
  * UNIX/POSIXshell and Macintosh.
  ****************************************************************************/
  char dir[DU_MAX_DIR_LEN+1];
  char pattern[DU_MAX_NAME_LEN+1];
  static char pattern1[DU_MAX_NAME_LEN+1];
  static char pattern2[DU_MAX_NAME_LEN+1];
  static char pattern3[DU_MAX_NAME_LEN+1];
  static char pattern4[DU_MAX_NAME_LEN+1];
  static char *patternS[4] = { pattern1, pattern2, pattern3, pattern4 };
  int nCDFs;

  ParsePath (path, dir, pattern);

  strcpyX (pattern1, pattern, DU_MAX_NAME_LEN);
  if (NULstring(pattern1)) strcatX (pattern1, "*", DU_MAX_NAME_LEN);
  strcatX (pattern1, ".cdf", DU_MAX_NAME_LEN);

  strcpyX (pattern2, pattern, DU_MAX_NAME_LEN);
  if (NULstring(pattern2)) strcatX (pattern2, "*", DU_MAX_NAME_LEN);
  strcatX (pattern2, ".CDF", DU_MAX_NAME_LEN);

  strcpyX (pattern3, pattern, DU_MAX_NAME_LEN);
  if (NULstring(pattern3)) strcatX (pattern3, "*", DU_MAX_NAME_LEN);
  strcatX (pattern3, ".cdf;1", DU_MAX_NAME_LEN);

  strcpyX (pattern4, pattern, DU_MAX_NAME_LEN);
  if (NULstring(pattern4)) strcatX (pattern4, "*", DU_MAX_NAME_LEN);
  strcatX (pattern4, ".CDF;1", DU_MAX_NAME_LEN);

  nCDFs = DirList (dir, 4, patternS, dirS, nameS);
  RemoveExtensions (nCDFs, *nameS);

  return nCDFs;
#endif
#if defined(vms) || defined(dos) || defined(win32)
  /****************************************************************************
  * VMS, Windows, and MS-DOS.
  ****************************************************************************/
  char dir[DU_MAX_DIR_LEN+1];
  static char pattern[DU_MAX_NAME_LEN+1];
  static char *patternS[1] = { pattern };
  int nCDFs;

  ParsePath (path, dir, pattern);

  if (NULstring(pattern)) strcatX (pattern, "*", DU_MAX_NAME_LEN);
  strcatX (pattern, ".cdf", DU_MAX_NAME_LEN);

  nCDFs = DirList (dir, 1, patternS, dirS, nameS);
  RemoveExtensions (nCDFs, *nameS);

  return nCDFs;
#endif
}

/******************************************************************************
* RemoveExtensions.  Locates the last '.' in a file name and replaces it with
*                    a NUL.  This effectively removes the file extension.  If
*                    the file name does not have an extension and has a '.' in
*                    its name, this could have undesired effects.
******************************************************************************/

void RemoveExtensions (nFiles, files)
int nFiles;
char *files[];
{
  int i;
  char *ptr;
  for (i = 0; i < nFiles; i++)
     if ((ptr = strrchr(files[i],'.')) != NULL) *ptr = NUL;
  return;
}

/******************************************************************************
* WriteStringStdOut.
******************************************************************************/

void WriteStringStdOut (string, length)
char *string;
size_t length;
{
  fprintf (stdout, "%*.*s", (int) length, (int) length, string);
  return;
}

/******************************************************************************
* ReadCharStdIn.
******************************************************************************/

#if !defined(mac)
void ReadCharStdIn (key)
char *key;
{
  *key = (char) fgetc (stdin);
  return;
}
#endif

/******************************************************************************
* DisplayStatistics.
******************************************************************************/

void DisplayStatistics (label, vStatsDotCDF, vStatsStage, vStatsCompress)
char *label;
vSTATS *vStatsDotCDF;
vSTATS *vStatsStage;
vSTATS *vStatsCompress;
{
  char temp1[MAX_SCREENLINE_LEN+1], temp2[MAX_SCREENLINE_LEN+1],
       temp3[MAX_SCREENLINE_LEN+1], labelX[MAX_SCREENLINE_LEN+1];
  if (vStatsDotCDF->maxBuffers > 0) {
    strcpyX (labelX, label, MAX_SCREENLINE_LEN);
    strcatX (labelX, " DotCDF file", MAX_SCREENLINE_LEN);
    BuildStatistics (labelX, vStatsDotCDF, temp1, temp2, temp3);
    WriteOut (stdout, temp1);
    WriteOut (stdout, "\n");
    WriteOut (stdout, temp2);
    WriteOut (stdout, "\n");
    WriteOut (stdout, temp3);
    WriteOut (stdout, "\n");
  }
  if (vStatsStage->maxBuffers > 0) {
    strcpyX (labelX, label, MAX_SCREENLINE_LEN);
    strcatX (labelX, " staging file", MAX_SCREENLINE_LEN);
    BuildStatistics (labelX, vStatsStage, temp1, temp2, temp3);
    WriteOut (stdout, temp1);
    WriteOut (stdout, "\n");
    WriteOut (stdout, temp2);
    WriteOut (stdout, "\n");
    WriteOut (stdout, temp3);
    WriteOut (stdout, "\n");
  }
  if (vStatsCompress->maxBuffers > 0) {
    strcpyX (labelX, label, MAX_SCREENLINE_LEN);
    strcatX (labelX, " compression scratch file", MAX_SCREENLINE_LEN);
    BuildStatistics (labelX, vStatsCompress, temp1, temp2, temp3);
    WriteOut (stdout, temp1);
    WriteOut (stdout, "\n");
    WriteOut (stdout, temp2);
    WriteOut (stdout, "\n");
    WriteOut (stdout, temp3);
    WriteOut (stdout, "\n");
  }
  return;
}

/******************************************************************************
* BuildStatistics.
******************************************************************************/

void BuildStatistics (label, vStats, temp1, temp2, temp3)
char *label;
vSTATS *vStats;
char temp1[MAX_SCREENLINE_LEN+1];
char temp2[MAX_SCREENLINE_LEN+1];
char temp3[MAX_SCREENLINE_LEN+1];
{
  strcpyX (temp1, "  Statistics", MAX_SCREENLINE_LEN);
  if (label != NULL) {
    strcatX (temp1, " for ", MAX_SCREENLINE_LEN);
    strcatX (temp1, label, MAX_SCREENLINE_LEN);
  }
  strcatX (temp1, ":", MAX_SCREENLINE_LEN);
  sprintf (temp2, "    %d buffer%s used of the %d allowed, ", vStats->nBuffers,
	   BOO(vStats->nBuffers == 1,"","s"), vStats->maxBuffers);
  sprintf (EofS(temp2), "[%ld reads, %ld writes]  ", vStats->nV_reads,
	   vStats->nV_writes);
  sprintf (temp3, "    %ld page in%s, %ld page out%s, ",
	   vStats->nPageIns, BOO(vStats->nPageIns==1,"","s"),
	   vStats->nPageOuts, BOO(vStats->nPageOuts==1,"","s"));
  sprintf (EofS(temp3), "%ld block read%s, %ld block write%s  ",
	   vStats->nBlockReads, BOO(vStats->nBlockReads==1,"","s"),
	   vStats->nBlockWrites, BOO(vStats->nBlockWrites==1,"","s"));
  return;
}

/******************************************************************************
* StatusCodeReportOptions.
******************************************************************************/

char *StatusCodeReportOptions (e,w,i)
Logical e;
Logical w;
Logical i;
{
  static char mask[MAX_REPORTMASK_LEN+1];
#if defined(vms)
  strcpyX (mask, "(", MAX_REPORTMASK_LEN);
#else
  strcpyX (mask, "\"", MAX_REPORTMASK_LEN);
#endif
  strcatX (mask, (e ? "errors" : ""), MAX_REPORTMASK_LEN);
  strcatX (mask, (w ? (e ? ",warnings" : "warnings") : ""),
	   MAX_REPORTMASK_LEN);
  strcatX (mask, (i ? (e || w ? ",informationals" : "informationals") : ""),
	   MAX_REPORTMASK_LEN);
#if defined(vms)
  strcatX (mask, ")", MAX_REPORTMASK_LEN);
#else
  strcatX (mask, "\"", MAX_REPORTMASK_LEN);
#endif
#if defined(vms)
  MakeUpperString (mask);
#endif
  return mask;
}

/******************************************************************************
* DisplayIdentification.
******************************************************************************/

void DisplayIdentification (name)
char *name;
{
  long version, release, increment; char subIncrement;
  char text[MAX_MESSAGE_TEXT_LEN+1]; CDFstatus status;
  status = CDFlib (GET_, LIB_VERSION_, &version,
			 LIB_RELEASE_, &release,
			 LIB_INCREMENT_, &increment,
			 LIB_subINCREMENT_, &subIncrement,
		   NULL_);
  if (StatusBAD(status))
    sprintf (text, "Error inquiring CDF library.");
  else {
    sprintf (text, "%s from the CDF V%ld.%ld.%ld",
	     name, version, release, increment);
    if (subIncrement != ' ') sprintf (EofS(text), "%c", subIncrement);
    sprintf (EofS(text), " distribution.");
  }
  DisplayInfo (text);
  return;
}

/******************************************************************************
* FindUniqueMatch.
* Returns: NOMATCH if a match was not found,
*	   MATCHES if more than one match,
*	   `index' if only one match.
******************************************************************************/

int FindUniqueMatch (target, strings)
char *target;		/* Searching for this string. */
char *strings[];	/* Terminated by a NULL pointer. */
{
  int match, beyond; size_t targetL = strlen(target);
  for (match = 0; strings[match] != NULL; match++) {
     if (!strncmpIgCase(target,strings[match],targetL)) {
       for (beyond = match + 1; strings[beyond] != NULL; beyond++) {
	  if (!strncmpIgCase(target,strings[beyond],targetL)) return MATCHES;
       }
       return match;
     }
  }
  return NOMATCH;
}
