/******************************************************************************
*
*  NSSDC/CDF                                                    CDFexport/2.
*
*  Version 1.2b, 2-Sep-97, Hughes STX.
*
*  Modification history:
*
*   V1.0  15-Sep-95, J Love     Original version.
*   V1.0a 18-Sep-95, J Love	Macintosh event handling.
*   V1.0b 22-Sep-95, J Love	INITIALRECS/ALLOCATERECS in `AllocateRecords'.
*   V1.1   9-Sep-96, J Love	CDF V2.6.
*   V1.2  15-Nov-96, J Love	Added `simple' mode and batch mode.
*   V1.2a  8-Jan-96, J Love	Changed settings file messages.
*   V1.2b  2-Sep-97, J Love	Fixed `inclusive' filtering.
*
******************************************************************************/

#include "cdfxp.h"

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

#define SETTINGS_DELIM		'"'

#define COUNTx(max,first,last) \
BOO(max < first,0,BOO(max < last,max-first+1,last-first+1));

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

static Logical SetFilter PROTOARGs((char *field, struct ItemStruct *Item));
static Logical SetOutput PROTOARGs((char *field, struct ItemStruct *Item));
static Logical SetWidth PROTOARGs((char *field, struct ItemStruct *Item));
static Logical SetBlocking PROTOARGs((char *field, struct VarStruct *Var));
static Logical SetMonotonic PROTOARGs((char *field, struct VarStruct *Var));
static Logical SetSparseness PROTOARGs((char *field, struct VarStruct *Var));
static Logical SetCompression PROTOARGs((char *field, struct VarStruct *Var));
static Logical ScanValueField PROTOARGs((
  char *fieldPtr, long dataType, long numElems, char **commaPtr, void **value
));
static void WriteDelimitedString PROTOARGs((
  FILE *fp, int delim, char *string
));
static char *EndingDelimiter PROTOARGs((char *string));
static void RemoveDelimiters PROTOARGs((char *string));
static Logical AllocateRecords PROTOARGs((
  CDFid outID, struct ItemStruct *scalarHead, struct ItemStruct *hyperHead
));
static CDFstatus BuildWalkScreen PROTOARGs((
  struct ItemStruct *exportHead, long recordN, long numDims, long indices[],
  char *lineS[], int lineNs[], int colS[], int lenS[], struct VarStruct *Vars[]
));
static Logical PromptForRecordNumber PROTOARGs((long *recordN, long maxRec));
static Logical PromptForDimensionIndex PROTOARGs((long *index, long dimSize));
static Logical PromptForVariableSearch PROTOARGs((
  struct VarStruct *Var, int *searchType, void **searchValue
));
static CDFstatus SearchForVariableValue PROTOARGs((
  struct VarStruct *Var, long numDims, long dimSizes[], long *recordAt,
  long indicesAt[], long maxRec, int searchType, void *searchValue
));
static Logical AbortSearch PROTOARGs((void));

/******************************************************************************
* SaveSettings.
******************************************************************************/

void SaveSettings () {
  FILE *fp; long version, release, increment; char subIncr;
  struct ItemStruct *Item; CDFstatus status;
  status = CDFlib (GET_, LIB_VERSION_, &version,
			 LIB_RELEASE_, &release,
			 LIB_INCREMENT_, &increment,
			 LIB_subINCREMENT_, &subIncr,
		   NULL_);
  DisplayStatus (status, "inquiring CDF");
  if (StatusBAD(status)) return;
  fp = fopen (settingsFile, "w");
  if (fp == NULL) {
    DisplayMessage ("Error opening settings file.", BEEPWAIT1);
    return;
  }
  fprintf (fp, "CDF V%ld.%ld.%ld%c%s\n", version, release, increment, subIncr,
	   BOO(simpleMode," (SimpleMode)",""));
  for (Item = itemHead; Item != NULL; Item = Item->nextItem) {
     switch (Item->type) {
       case RECORDt:
	 fprintf (fp, "ITEM=Record,");
	 fprintf (fp, "%s,", BOO(Item->outputSetting,"YES","no"));
	 if (!simpleMode) {
	   if (Item->Record->min == NOminMax)
	     fprintf (fp, ",");
	   else
	     fprintf (fp, "%ld,", Item->Record->min + 1);
	   if (Item->Record->max == NOminMax)
	     fprintf (fp, ",");
	   else
	     fprintf (fp, "%ld,", Item->Record->max + 1);
	   fprintf (fp, "%s,", BOO(Item->filterSetting,
				   BOO(opt.exclusive,
				       BOO(Item->inclusive,"YeS","yEs"),
				       "YES"),
				   "no"));
	   fprintf (fp, "%d,", Item->width);
	 }
	 fprintf (fp, "\n");
	 break;
       case INDICESt:
	 fprintf (fp, "ITEM=Indices,");
	 fprintf (fp, "%s,", BOO(Item->outputSetting,"YES","no"));
	 if (!simpleMode) {
	   if (Item->Indices->minNumDims == NOminMax)
	     fprintf (fp, ",");
	   else {
	     char indices[MAXitemFieldLEN+1];
	     EncodeIndicesJustify (indices, Item->Indices->minNumDims,
				   Item->Indices->minIndices, 0);
	     fprintf (fp, "%s,", indices);
	   }
	   if (Item->Indices->maxNumDims == NOminMax)
	     fprintf (fp, ",");
	   else {
	     char indices[MAXitemFieldLEN+1];
	     EncodeIndicesJustify (indices, Item->Indices->maxNumDims,
				   Item->Indices->maxIndices, 0);
	     fprintf (fp, "%s,", indices);
	   }
	   fprintf (fp, "%s,", BOO(Item->filterSetting,
				   BOO(opt.exclusive,
				       BOO(Item->inclusive,"YeS","yEs"),
				       "YES"),
				   "no"));
	   fprintf (fp, "%d,", Item->width);
	 }
	 fprintf (fp, "\n");
	 break;
       case VARIABLEt:
	 fprintf (fp, "ITEM=Variable,");
	 WriteDelimitedString (fp, (int) SETTINGS_DELIM, Item->Var->name);
	 fprintf (fp, ",");
	 fprintf (fp, "%s,", BOO(Item->outputSetting,"YES","no"));
	 if (!simpleMode) {
	   if (Item->Var->min != NULL) {
	     if (STRINGdataType(Item->Var->dataType))
	       WriteDelimitedString (fp, (int) SETTINGS_DELIM,
				     (char *) Item->Var->min);
	     else {
	       char temp[MAX_nonSTRING_VALUE_LEN+1];
	       EncodeValueFormat (BOO(EPOCHdataType(Item->Var->dataType),
				      CDF_REAL8,Item->Var->dataType),
				  Item->Var->min, temp,
				  BOO(EPOCHdataType(Item->Var->dataType),
				      "%.13e",NULL),
				  0, MAX_nonSTRING_VALUE_LEN, 0);
	       fprintf (fp, "[%s]", temp);
	     }
	   }
	   fprintf (fp, ",");
	   if (Item->Var->max != NULL) {
	     if (STRINGdataType(Item->Var->dataType))
	       WriteDelimitedString (fp, (int) SETTINGS_DELIM,
				     (char *) Item->Var->max);
	     else {
	       char temp[MAX_nonSTRING_VALUE_LEN+1];
	       EncodeValueFormat (BOO(EPOCHdataType(Item->Var->dataType),
				      CDF_REAL8,Item->Var->dataType),
				  Item->Var->max, temp,
				  BOO(EPOCHdataType(Item->Var->dataType),
				      "%.13e",NULL),
				  0, MAX_nonSTRING_VALUE_LEN, 0);
	       fprintf (fp, "[%s]", temp);
	     }
	   }
	   fprintf (fp, ",");
	   fprintf (fp, "%s,", BOO(Item->filterSetting,
				   BOO(opt.exclusive,
				       BOO(Item->inclusive,"YeS","yEs"),
				       "YES"),
				   "no"));
	   if (Item->Var->fill != NULL) {
	     if (STRINGdataType(Item->Var->dataType))
	       WriteDelimitedString (fp, (int) SETTINGS_DELIM,
				     (char *) Item->Var->fill);
	     else {
	       char temp[MAX_nonSTRING_VALUE_LEN+1];
	       EncodeValueFormat (BOO(EPOCHdataType(Item->Var->dataType),
				      CDF_REAL8,Item->Var->dataType),
				  Item->Var->fill, temp,
				  BOO(EPOCHdataType(Item->Var->dataType),
				      "%.13e",NULL),
				  0, MAX_nonSTRING_VALUE_LEN, 0);
	       fprintf (fp, "[%s]", temp);
	     }
	   }
	   fprintf (fp, ",");
	   fprintf (fp, "%s,", monos[Item->Var->monotonic+1]);
	   if (Item->Var->format != NULL) {
	     WriteDelimitedString (fp, (int)SETTINGS_DELIM, Item->Var->format);
	   }
	   fprintf (fp, ",");
	   fprintf (fp, "%d,", Item->width);
	   fprintf (fp, "%s,", SparsenessToken(Item->Var->sRecordsType,
					       Item->Var->sArraysType,
					       Item->Var->sArraysParms));
	   fprintf (fp, "%s,", CompressionToken(Item->Var->cType,
					        Item->Var->cParms));
	   fprintf (fp, "%ld,", Item->Var->blocking);
	 }
	 fprintf (fp, "\n");
	 break;
     }
  }
  if (!simpleMode) {
    fprintf (fp, "USE_FILTERS=%s\n", BOO(opt.overallFilter,"YES","no"));
    fprintf (fp, "USE_FILLS=%s\n", BOO(opt.useFills,"YES","no"));
    fprintf (fp, "CDF_FORMAT=%s\n", BOO(opt.singleFile,"single","multi"));
    fprintf (fp, "CDF_ENCODING=%s\n", encodings[(int)opt.encoding]);
    if (CDFcType != NO_COMPRESSION)
      fprintf (fp, "CDF_COMPRESSION=%s\n",CompressionToken(CDFcType, CDFcParms));
    if (CDFchecksum != NO_CHECKSUM) 
      fprintf (fp, "CDF_CHECKSUM=%s\n",ChecksumToken(CDFchecksum));
    fprintf (fp, "EPOCH_STYLE=%s\n", epochStyles[opt.epochStyle]);
    fprintf (fp, "ORIENTATION=%s\n",
	     BOO(opt.horizontalMode,"horizontal","vertical"));
    fprintf (fp, "MAJORITY=%s\n", majorities[(int)opt.majority]);
    fprintf (fp, "SHOW_FILTERED=%s\n", BOO(opt.showFiltered,"YES","no"));
    fprintf (fp, "SPACING=%d\n", opt.spacing);
    fprintf (fp, "DELETE_EXISTING=%s\n", BOO(opt.deleteExisting,"YES","no"));
    fprintf (fp, "PREALLOCATE=%s\n", BOO(opt.preAllocate,"YES","no"));
  }
  fclose (fp);
  return;
}

/******************************************************************************
* RestoreSettings.
******************************************************************************/

void RestoreSettings () {
  char *equalPtr, *newlinePtr, *commaPtr, *fieldPtr, *linePtr, *delimPtr;
  FILE *fp; long valueL; struct ItemStruct *Item;
  char line[MAX_SETTINGS_LEN+1+1];              /* +1+1 for newline+NUL. */
  if (!IsReg(settingsFile)) {
    DisplayMessage ("Settings file does not exist.", BEEPWAIT1);
    return;
  }
  fp = fopen (settingsFile, "r");
  if (fp == NULL) {
    DisplayMessage ("Error opening settings file.", BEEPWAIT1);
    return;
  }
  linePtr = fgets (line, MAX_SETTINGS_LEN+1+1, fp);
  if (linePtr == NULL) {
    DisplayMessage ("Error reading settings file.", BEEPWAIT1);
    return;
  }
  while ((linePtr = fgets(line,MAX_SETTINGS_LEN+1+1,fp)) != NULL) {
    newlinePtr = strchr (line, '\n');
    if (newlinePtr != NULL)
      *newlinePtr = NUL;
    else {
      /* Line was longer than MAX_SETTINGS_LEN.  What should we do? */
    }
    equalPtr = strchr (line, '=');
    if (equalPtr == NULL) break;
    *equalPtr = NUL;
    /**************************************************************************
    * Check for item/variable.
    **************************************************************************/
    if (!strcmp(line,"ITEM")) {
      fieldPtr = equalPtr + 1;
      commaPtr = strchr (fieldPtr, ',');
      if (commaPtr == NULL) break;
      *commaPtr = NUL;
      /************************************************************************
      * Check for `Record' item.
      ************************************************************************/
      if (!strcmp(fieldPtr,"Record")) {
	/**********************************************************************
	* Check if `Record' is present in SelectionWindow.
	**********************************************************************/
	for (Item = itemHead; Item != NULL; Item = Item->nextItem) {
	   if (Item->type == RECORDt) break;
	}
	if (Item == NULL) continue;
	/**********************************************************************
	* Check for `Output' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	commaPtr = strchr (fieldPtr, ',');
	if (commaPtr == NULL) break;
	*commaPtr = NUL;
	if (!SetOutput(fieldPtr,Item)) break;
	/**********************************************************************
	* A settings file created in `simple' mode will end here.
	**********************************************************************/
	if (*(commaPtr+1) == NUL) continue;
	/**********************************************************************
	* Check for `Minimum' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	commaPtr = strchr (fieldPtr, ',');
	if (commaPtr == NULL) break;
	*commaPtr = NUL;
	if (NULstring(fieldPtr))
	  Item->Record->min = NOminMax;
	else
	  if (sscanf(fieldPtr,"%ld",&valueL) == 1)
	    if (valueL > 0)
	      Item->Record->min = valueL - 1;
	    else
	      break;
	  else
	    break;
	/**********************************************************************
	* Check for `Maximum' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	commaPtr = strchr (fieldPtr, ',');
	if (commaPtr == NULL) break;
	*commaPtr = NUL;
	if (NULstring(fieldPtr))
	  Item->Record->max = NOminMax;
	else
	  if (sscanf(fieldPtr,"%ld",&valueL) == 1)
	    if (valueL > 0)
	      Item->Record->max = valueL - 1;
	    else
	      break;
	  else
	    break;
	/**********************************************************************
	* Check that Minimum/Maximum fields are legal.
	**********************************************************************/
	if (Item->Record->min != NOminMax && Item->Record->max != NOminMax) {
	  if (Item->Record->min > Item->Record->max) {
	    Item->Record->min = NOminMax;
	    Item->Record->max = NOminMax;
	    break;
	  }
	}
	/**********************************************************************
	* Check for `Filter' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	commaPtr = strchr (fieldPtr, ',');
	if (commaPtr == NULL) break;
	*commaPtr = NUL;
	if (!SetFilter(fieldPtr,Item)) break;
	/**********************************************************************
	* Check for `Width' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	commaPtr = strchr (fieldPtr, ',');
	if (commaPtr == NULL) break;
	*commaPtr = NUL;
	if (!SetWidth(fieldPtr,Item)) break;
	continue;
      }
      /************************************************************************
      * Check for `Indices' item.
      ************************************************************************/
      if (!strcmp(fieldPtr,"Indices")) {
	/**********************************************************************
	* Check if `Indices' is present in SelectionWindow.
	**********************************************************************/
	for (Item = itemHead; Item != NULL; Item = Item->nextItem) {
	   if (Item->type == INDICESt) break;
	}
	if (Item == NULL) continue;
	/**********************************************************************
	* Check for `Output' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	commaPtr = strchr (fieldPtr, ',');
	if (commaPtr == NULL) break;
	*commaPtr = NUL;
	if (!SetOutput(fieldPtr,Item)) break;
	/**********************************************************************
	* A settings file created in `simple' mode will end here.
	**********************************************************************/
	if (*(commaPtr+1) == NUL) continue;
	/**********************************************************************
	* Check for `Minimum' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	if (*fieldPtr == '[') {
	  long numDims, indices[CDF_MAX_DIMS]; int dimN;
	  commaPtr = strchr (fieldPtr + 1, ']');
	  if (commaPtr == NULL) break;
	  commaPtr = strchr (commaPtr + 1, ',');
	  if (commaPtr == NULL) break;
	  *commaPtr = NUL;
	  if (DecodeRecordAndIndices(fieldPtr,NULL,&numDims,indices)) {
	    Item->Indices->minNumDims = numDims;
	    for (dimN = 0; dimN < numDims; dimN++) {
	       Item->Indices->minIndices[dimN] = indices[dimN];
	    }
	  }
	  else
	    break;
	}
	else
	  if (*fieldPtr == ',') {
	    Item->Indices->minNumDims = NOminMax;
	    commaPtr = fieldPtr;
	  }
	  else
	    break;
	/**********************************************************************
	* Check for `Maximum' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	if (*fieldPtr == '[') {
	  long numDims, indices[CDF_MAX_DIMS]; int dimN;
	  commaPtr = strchr (fieldPtr + 1, ']');
	  if (commaPtr == NULL) break;
	  commaPtr = strchr (commaPtr + 1, ',');
	  if (commaPtr == NULL) break;
	  *commaPtr = NUL;
	  if (DecodeRecordAndIndices(fieldPtr,NULL,&numDims,indices)) {
	    Item->Indices->maxNumDims = numDims;
	    for (dimN = 0; dimN < numDims; dimN++) {
	       Item->Indices->maxIndices[dimN] = indices[dimN];
	    }
	  }
	  else
	    break;
	}
	else
	  if (*fieldPtr == ',') {
	    Item->Indices->maxNumDims = NOminMax;
	    commaPtr = fieldPtr;
	  }
	  else
	    break;
	/**********************************************************************
	* Check for `Filter' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	commaPtr = strchr (fieldPtr, ',');
	if (commaPtr == NULL) break;
	*commaPtr = NUL;
	if (!SetFilter(fieldPtr,Item)) break;
	/**********************************************************************
	* Check for `Width' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	commaPtr = strchr (fieldPtr, ',');
	if (commaPtr == NULL) break;
	*commaPtr = NUL;
	if (!SetWidth(fieldPtr,Item)) break;
	continue;
      }
      /************************************************************************
      * Check for `Variable' item.
      ************************************************************************/
      if (!strcmp(fieldPtr,"Variable")) {
	/**********************************************************************
	* Parse variable name.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	if (*fieldPtr != SETTINGS_DELIM) break;
	delimPtr = EndingDelimiter (fieldPtr);
	if (delimPtr == NULL) break;
	commaPtr = delimPtr + 1;
	if (*commaPtr != ',') break;
	*commaPtr = NUL;
	RemoveDelimiters (fieldPtr);
	/**********************************************************************
	* Check if the variable is in the SelectionWindow.
	**********************************************************************/
	for (Item = itemHead; Item != NULL; Item = Item->nextItem) {
	   if (Item->type == VARIABLEt) {
	     if (!strcmp(fieldPtr,Item->Var->name)) break;
	   }
	}
	if (Item == NULL) continue;
	/**********************************************************************
	* Check for `Output' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	commaPtr = strchr (fieldPtr, ',');
	if (commaPtr == NULL) break;
	*commaPtr = NUL;
	if (!SetOutput(fieldPtr,Item)) break;
	/**********************************************************************
	* A settings file created in `simple' mode will end here.
	**********************************************************************/
	if (*(commaPtr+1) == NUL) continue;
	/**********************************************************************
	* Check for `Minimum' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	if (!ScanValueField(fieldPtr,Item->Var->dataType,
			    Item->Var->numElems,&commaPtr,
			    &(Item->Var->min))) break;
	/**********************************************************************
	* Check for `Maximum' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	if (!ScanValueField(fieldPtr,Item->Var->dataType,
			    Item->Var->numElems,&commaPtr,
			    &(Item->Var->max))) break;
	/**********************************************************************
	* Check for `Filter' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	commaPtr = strchr (fieldPtr, ',');
	if (commaPtr == NULL) break;
	*commaPtr = NUL;
	if (!SetFilter(fieldPtr,Item)) break;
	/**********************************************************************
	* Check for `Fill' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	if (!ScanValueField(fieldPtr,Item->Var->dataType,
			    Item->Var->numElems,&commaPtr,
			    &(Item->Var->fill))) break;
	/**********************************************************************
	* Check for `Monotonicity' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	commaPtr = strchr (fieldPtr, ',');
	if (commaPtr == NULL) break;
	*commaPtr = NUL;
	if (!SetMonotonic(fieldPtr,Item->Var)) break;
	/**********************************************************************
	* Check for `Format' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	if (*fieldPtr == SETTINGS_DELIM) {
	  delimPtr = EndingDelimiter (fieldPtr);
	  if (delimPtr == NULL) break;
	  commaPtr = delimPtr + 1;
	  if (*commaPtr != ',') break;
	  *commaPtr = NUL;
	  RemoveDelimiters (fieldPtr);
	  if (Item->Var->format != NULL) {
	    cdf_FreeMemory (Item->Var->format, FatalError);
	  }
	  Item->Var->format = (char *) cdf_AllocateMemory (strlen(fieldPtr) + 1,
						       FatalError);
	  strcpyX (Item->Var->format, fieldPtr, 0);
	}
	else {
	  commaPtr = fieldPtr;
	  if (Item->Var->format != NULL) {
	    cdf_FreeMemory (Item->Var->format, FatalError);
	    Item->Var->format = NULL;
	  }
	}
	/**********************************************************************
	* Check for `Width' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	commaPtr = strchr (fieldPtr, ',');
	if (commaPtr == NULL) break;
	*commaPtr = NUL;
	if (!SetWidth(fieldPtr,Item)) break;
	/**********************************************************************
	* Check for `Sparseness' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	commaPtr = strchr (fieldPtr, ',');
	if (commaPtr == NULL) break;
	*commaPtr = NUL;
	if (!SetSparseness(fieldPtr,Item->Var)) break;
	/**********************************************************************
	* Check for `Compression' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	commaPtr = strchr (fieldPtr, ',');
	if (commaPtr == NULL) break;
	*commaPtr = NUL;
	if (!SetCompression(fieldPtr,Item->Var)) break;
	/**********************************************************************
	* Check for `Blocking' field.
	**********************************************************************/
	fieldPtr = commaPtr + 1;
	commaPtr = strchr (fieldPtr, ',');
	if (commaPtr == NULL) break;
	*commaPtr = NUL;
	if (!SetBlocking(fieldPtr,Item->Var)) break;
	/**********************************************************************
	* Continue to next line...
	**********************************************************************/
	continue;
      }
      break;
    }
    /**************************************************************************
    * Check for `Use filters'.
    **************************************************************************/
    if (!strcmp(line,"USE_FILTERS")) {
      if (!strcmp(equalPtr+1,"YES")) {
	opt.overallFilter = TRUE;
	continue;
      }
      if (!strcmp(equalPtr+1,"no")) {
	opt.overallFilter = FALSE;
	continue;
      }
      break;
    }
    /**************************************************************************
    * Check for `Use fills'.
    **************************************************************************/
    if (!strcmp(line,"USE_FILLS")) {
      if (!strcmp(equalPtr+1,"YES")) {
	opt.useFills = TRUE;
	continue;
      }
      if (!strcmp(equalPtr+1,"no")) {
	opt.useFills = FALSE;
	continue;
      }
      break;
    }
    /**************************************************************************
    * Check for `CDF Format'.
    **************************************************************************/
    if (!strcmp(line,"CDF_FORMAT")) {
      if (!strcmp(equalPtr+1,"single")) {
	opt.singleFile = TRUE;
	continue;
      }
      if (!strcmp(equalPtr+1,"multi")) {
	opt.singleFile = FALSE;
	continue;
      }
      break;
    }
    /**************************************************************************
    * Check for `CDF Encoding'.
    **************************************************************************/
    if (!strcmp(line,"CDF_ENCODING")) {
      int encoding; Logical found;
      for (encoding = 0, found = FALSE; encoding <= MAX_ENCODING; encoding++) {
	 if (encodings[encoding] != NULL) {
	   if (!strcmp(equalPtr+1,encodings[encoding])) {
	     opt.encoding = encoding;
	     found = TRUE;
	     break;
	   }
	 }
      }
      if (found) continue;
      break;
    }
    /**************************************************************************
    * Check for `CDF Compression'.
    **************************************************************************/
    if (!strcmp(line,"CDF_COMPRESSION")) {
      long type; Logical found;
      long ct, cp[CDF_MAX_DIMS];
      type = WhichCompression(equalPtr+1, &ct, cp);
      if (type > -1) {
        CDFcType = ct;
        CDFcParms[0] = cp[0]; 
        found = TRUE;
      }
      if (found) continue;
      break;
    }
    /**************************************************************************
    * Check for `CDF Checksum'.
    **************************************************************************/
    if (!strcmp(line,"CDF_CHECKSUM")) {
      int type; Logical found;
      for (type = 0, found = FALSE; type <= MAX_CHECKSUM; type++) {
         if (checksums[type] != NULL) {
           if (strcmpIgCase(equalPtr+1,checksums[type]) != 0) {
             CDFchecksum = (long) type;
             found = TRUE;
             break;
           }
         }
      }
      if (found) continue;
      break;
    }
    /**************************************************************************
    * Check for `EPOCH Style'.
    **************************************************************************/
    if (!strcmp(line,"EPOCH_STYLE")) {
      int style; Logical found;
      for (style = EPOCH0_STYLE, found = FALSE;
	   style <= EPOCHx_STYLE; style++) {
	 if (!strcmp(equalPtr+1,epochStyles[style])) {
	   opt.epochStyle = style;
	   found = TRUE;
	   break;
	 }
      }
      if (found) continue;
      break;
    }
    /**************************************************************************
    * Check for `Listing Orientation'.
    **************************************************************************/
    if (!strcmp(line,"ORIENTATION")) {
      if (!strcmp(equalPtr+1,"horizontal")) {
	opt.horizontalMode = TRUE;
	continue;
      }
      if (!strcmp(equalPtr+1,"vertical")) {
	opt.horizontalMode = FALSE;
	continue;
      }
      break;
    }
    /**************************************************************************
    * Check for `Listing/CDF Majority'.
    **************************************************************************/
    if (!strcmp(line,"MAJORITY")) {
      if (!strcmp(equalPtr+1,"row")) {
	opt.majority = ROW_MAJOR;
	continue;
      }
      if (!strcmp(equalPtr+1,"column")) {
	opt.majority = COLUMN_MAJOR;
	continue;
      }
      if (!strcmp(equalPtr+1,"input")) {
	opt.majority = INPUT_MAJOR;
	continue;
      }
      break;
    }
    /**************************************************************************
    * Check for `Show Filtered Lines'.
    **************************************************************************/
    if (!strcmp(line,"SHOW_FILTERED")) {
      if (!strcmp(equalPtr+1,"YES")) {
	opt.showFiltered = TRUE;
	continue;
      }
      if (!strcmp(equalPtr+1,"no")) {
	opt.showFiltered = FALSE;
	continue;
      }
      break;
    }
    /**************************************************************************
    * Check for `Listing Spacing'.
    **************************************************************************/
    if (!strcmp(line,"SPACING")) {
      int spacing;
      if (sscanf(equalPtr+1,"%d",&spacing) == 1) {
	if (0 <= spacing) {
	  opt.spacing = spacing;
	  continue;
	}
	else
	  break;
      }
      else
	break;
    }
    /**************************************************************************
    * Check for `Delete Existing CDF'.
    **************************************************************************/
    if (!strcmp(line,"DELETE_EXISTING")) {
      if (!strcmp(equalPtr+1,"YES")) {
	opt.deleteExisting = TRUE;
	continue;
      }
      if (!strcmp(equalPtr+1,"no")) {
	opt.deleteExisting = FALSE;
	continue;
      }
      break;
    }
    /**************************************************************************
    * Check for `Preallocate Variable Records'.
    **************************************************************************/
    if (!strcmp(line,"PREALLOCATE")) {
      if (!strcmp(equalPtr+1,"YES")) {
	opt.preAllocate = TRUE;
	continue;
      }
      if (!strcmp(equalPtr+1,"no")) {
	opt.preAllocate = FALSE;
	continue;
      }
      break;
    }
    break;
  }
  fclose (fp);
  if (linePtr != NULL) {
    DisplayMessage ("Error parsing settings file.", BEEPWAIT1);
    return;
  }
  return;
}

/******************************************************************************
* SetFilter.
******************************************************************************/

static Logical SetFilter (field, Item)
char *field;
struct ItemStruct *Item;
{
  if (!strcmp(field,"YES")) {
    Item->filterSetting = TRUE;
    Item->inclusive = TRUE;
    return TRUE;
  }
  if (!strcmp(field,"YeS")) {
    Item->filterSetting = TRUE;
    Item->inclusive = TRUE;
    return TRUE;
  }
  if (!strcmp(field,"yEs")) {
    Item->filterSetting = TRUE;
    Item->inclusive = FALSE;
    return TRUE;
  }
  if (!strcmp(field,"no")) {
    Item->filterSetting = FALSE;
    Item->inclusive = TRUE;
    return TRUE;
  }
  return FALSE;
}

/******************************************************************************
* SetOutput.
******************************************************************************/

static Logical SetOutput (field, Item)
char *field;
struct ItemStruct *Item;
{
  if (!strcmp(field,"YES")) {
    Item->outputSetting = TRUE;
    return TRUE;
  }
  if (!strcmp(field,"no")) {
    Item->outputSetting = FALSE;
    return TRUE;
  }
  return FALSE;
}

/******************************************************************************
* SetWidth.
******************************************************************************/

static Logical SetWidth (field, Item)
char *field;
struct ItemStruct *Item;
{
  int width;
  if (sscanf(field,"%d",&width) != 1) return FALSE;
  if (width < 1) return FALSE;
  Item->width = width;
  return TRUE;
}

/******************************************************************************
* SetBlocking.
******************************************************************************/

static Logical SetBlocking (field, Var)
char *field;
struct VarStruct *Var;
{
  long blocking;
  if (sscanf(field,"%ld",&blocking) != 1) return FALSE;
  if (blocking < 0) return FALSE;
  Var->blocking = blocking;
  return TRUE;
}

/******************************************************************************
* SetMonotonic.
******************************************************************************/

static Logical SetMonotonic (field, Var)
char *field;
struct VarStruct *Var;
{
  if (Var->monotonic != NAmono) {
    if (!strcmp(field,"Increase")) {
      Var->monotonic = INCREASEmono;
      return TRUE;
    }
    if (!strcmp(field,"Decrease")) {
      Var->monotonic = DECREASEmono;
      return TRUE;
    }
    if (!strcmp(field,"False")) {
      Var->monotonic = FALSEmono;
      return TRUE;
    }
    if (!strcmp(field,"Unknown")) {
      Var->monotonic = UNKNOWNmono;
      return TRUE;
    }
    return FALSE;
  }
  return TRUE;
}

/******************************************************************************
* SetSparseness.
******************************************************************************/

static Logical SetSparseness (field, Var)
char *field;
struct VarStruct *Var;
{
  Var->sRecordsType = NO_SPARSERECORDS;
  if (strstr(field,"sRecords.PAD") != NULL) {
    Var->sRecordsType = PAD_SPARSERECORDS;
    return TRUE;
  }
  if (strstr(field,"sRecords.PREV") != NULL) {
    Var->sRecordsType = PREV_SPARSERECORDS;
    return TRUE;
  }
  return TRUE;
}

/******************************************************************************
* SetCompression.
******************************************************************************/

static Logical SetCompression (field, Var)
char *field;
struct VarStruct *Var;
{
  if (!strcmp(field,"None")) {
    Var->cType = NO_COMPRESSION;
    return TRUE;
  }
  if (!strcmp(field,"RLE.0")) {
    Var->cType = RLE_COMPRESSION;
    Var->cParms[0] = RLE_OF_ZEROs;
    return TRUE;
  }
  if (!strcmp(field,"HUFF.0")) {
    Var->cType = HUFF_COMPRESSION;
    Var->cParms[0] = OPTIMAL_ENCODING_TREES;
    return TRUE;
  }
  if (!strcmp(field,"AHUFF.0")) {
    Var->cType = AHUFF_COMPRESSION;
    Var->cParms[0] = OPTIMAL_ENCODING_TREES;
    return TRUE;
  }
  return TRUE;
}

/******************************************************************************
* ScanValueField.
******************************************************************************/

static Logical ScanValueField (fieldPtr, dataType, numElems, commaPtr, value)
char *fieldPtr;         /* In: Pointer to beginning of field. */
long dataType;          /* In: Data type of variable. */
long numElems;          /* In: Number of elements of variable. */
char **commaPtr;        /* Out: Pointer (to pointer) to ending comma. */
void **value;           /* Out: Pointer (to pointer) to value. */
{
  if (*fieldPtr == ',') {
    if (*value != NULL) {
      cdf_FreeMemory (*value, FatalError);
      *value = NULL;
    }
    *commaPtr = fieldPtr;
    return TRUE;
  }
  if (*fieldPtr == SETTINGS_DELIM) {
    char *delimPtr = EndingDelimiter (fieldPtr);
    if (delimPtr == NULL) return FALSE;
    *commaPtr = delimPtr + 1;
    if (**commaPtr != ',') return FALSE;
    **commaPtr = NUL;
    if (STRINGdataType(dataType)) {
      RemoveDelimiters (fieldPtr);
      if ((int) strlen(fieldPtr) == (int) numElems) {
	if (*value == NULL) {
	  size_t nBytes = (size_t) (numElems + 1);
	  *value = (char *) cdf_AllocateMemory (nBytes, FatalError);
	}
	strcpyX (*value, fieldPtr, 0);
      }
    }
    return TRUE;
  }
  if (*fieldPtr == '[') {
    void *newValue; long newNumElems;
    char *endPtr = strchr (fieldPtr + 1, ']');
    if (endPtr == NULL) return FALSE;
    *commaPtr = endPtr + 1;
    if (**commaPtr != ',') return FALSE;
    fieldPtr++;
    *endPtr = NUL;
    if (!STRINGdataType(dataType)) {
      if (!DecodeValues(fieldPtr,BOO(EPOCHdataType(dataType),
				     CDF_REAL8,dataType),
			&newNumElems,&newValue,0)) return FALSE;
      if (newNumElems != numElems) {
	cdf_FreeMemory (newValue, FatalError);
	return FALSE;
      }
      if (*value != NULL) cdf_FreeMemory (*value, FatalError);
      *value = newValue;
    }
    return TRUE;
  }
  return FALSE;
}

/******************************************************************************
* WriteDelimitedString.
******************************************************************************/

static void WriteDelimitedString (fp, delim, string)
FILE *fp;
int delim;
char *string;
{
  int i;
  fprintf (fp, "%c", (char) delim);
  for (i = 0; string[i] != NUL; i++) {
     if (string[i] == (char) delim) fprintf (fp, "%c", (char) delim);
     fprintf (fp, "%c", string[i]);
  }
  fprintf (fp, "%c", (char) delim);
  return;
}

/******************************************************************************
* EndingDelimiter.
******************************************************************************/

static char *EndingDelimiter (string)
char *string;
{
  int i; char delim;
  for (i = 1, delim = string[0]; string[i] != NUL; i++) {
     if (string[i] == delim) {
       if (string[i+1] != delim)
	 return &(string[i]);
       else
	 i++;
     }
  }
  return NULL;
}

/******************************************************************************
* RemoveDelimiters.
******************************************************************************/

static void RemoveDelimiters (string)
char *string;
{
  int to = 0, from = 1; char delim = string[0];
  for (;;) {
     if (string[from] == NUL) {
       string[to] = NUL;
       return;
     }
     if (string[from] == delim)
       if (string[from+1] == delim) {
	 string[to++] = delim;
	 from += 2;
       }
       else
	 from++;
     else
       string[to++] = string[from++];
  }
}

/******************************************************************************
* SetItemMonotonicities.
* Returns FALSE if a fatal error occurred.
******************************************************************************/

Logical SetItemMonotonicities () {
  struct ItemStruct *Item;
  DisplayMessage ("Setting variable monotonicities.", NOBEEPWAIT1);
  for (Item = itemHead; Item != NULL; Item = Item->nextItem) {
     switch (Item->type) {
       case RECORDt:
       case INDICESt:
	 break;
       case VARIABLEt:
	 if (Item->Var->monotonic != NAmono) {
	   if (!SetVarMonotonicity(Item->Var)) return FALSE;
	 }
	 break;
     }
  }
  DisplayMessage ("", NOWAIT);
  return TRUE;
}

/******************************************************************************
* SetVarMonotonicity.
* Returns FALSE if a fatal error occurred.
******************************************************************************/

Logical SetVarMonotonicity (Var)
struct VarStruct *Var;
{
  static long indices[CDF_MAX_DIMS] = { 0,0,0,0,0,0,0,0,0,0 };
  void *prevValue = NULL; CDFstatus status;
  Var->monotonic = UNKNOWNmono;
  status = CDFlib (SELECT_, BOO(Var->zVar,zVAR_,rVAR_), Var->varN,
			    BOO(Var->zVar,zVAR_SEQPOS_,
					  rVAR_SEQPOS_), 0L, indices,
		   NULL_);
  DisplayStatus (status, "setting variable monotonicity");
  if (StatusBAD(status)) return FALSE;
  status = CDFlib (GET_, BOO(Var->zVar,zVAR_SEQDATA_,
				       rVAR_SEQDATA_), Var->value,
		   NULL_);
  while (StatusOK(status)) {
    if (prevValue == NULL) {
      prevValue = cdf_AllocateMemory (Var->nValueBytes, FatalError);
      ASSIGNx (prevValue, Var->value, Var->dataType, Var->numElems);
    }
    else {
      switch (Var->monotonic) {
	case UNKNOWNmono:
	  if (GTx(Var->value,prevValue,Var->dataType,Var->numElems)) {
	    Var->monotonic = INCREASEmono;
	    break;
	  }
	  if (LTx(Var->value,prevValue,Var->dataType,Var->numElems)) {
	    Var->monotonic = DECREASEmono;
	    break;
	  }
	  Var->monotonic = FALSEmono;
	  cdf_FreeMemory (prevValue, FatalError);
	  return TRUE;
	case INCREASEmono:
	  if (LTx(Var->value,prevValue,Var->dataType,Var->numElems)) {
	    Var->monotonic = FALSEmono;
	    cdf_FreeMemory (prevValue, FatalError);
	    return TRUE;
	  }
	  break;
	case DECREASEmono:
	  if (GTx(Var->value,prevValue,Var->dataType,Var->numElems)) {
	    Var->monotonic = FALSEmono;
	    cdf_FreeMemory (prevValue, FatalError);
	    return TRUE;
	  }
	  break;
      }
      ASSIGNx (prevValue, Var->value, Var->dataType, Var->numElems);
    }
    status = CDFlib (GET_, BOO(Var->zVar,zVAR_SEQDATA_,
					 rVAR_SEQDATA_), Var->value,
		     NULL_);
  }
  if (prevValue != NULL) cdf_FreeMemory (prevValue, FatalError);
  if (status != END_OF_VAR) {
    DisplayStatus (status, "setting variable monotonicity");
    if (StatusBAD(status)) return FALSE;
  }
  return TRUE;
}

/******************************************************************************
* BuildExportList.
******************************************************************************/

void BuildExportList (exportHead, walking)
struct ItemStruct **exportHead;
Logical walking;
{
  struct ItemStruct *Item, *exportTail = NULL;
  *exportHead = NULL;
  for (Item = itemHead; Item != NULL; Item = Item->nextItem) {
     if ((walking && Item->outputSetting && Item->type == VARIABLEt) ||
	 (!walking && (Item->outputSetting ||
		       (opt.overallFilter && Item->filterSetting)))) {
       if (*exportHead == NULL)
	 *exportHead = exportTail = Item;
       else
	 exportTail = exportTail->nextExport = Item;
       Item->nextExport = NULL;
       Item->output = Item->outputSetting;
       Item->filter = BOO(opt.overallFilter,Item->filterSetting,FALSE);
     }
  }
  return;
}

/******************************************************************************
* RemoveExportItems.
******************************************************************************/

void RemoveExportItems (exportHead)
struct ItemStruct **exportHead;
{
  struct ItemStruct *Item = *exportHead, *prevItem = NULL;
  while (Item != NULL) {
    if (!Item->output && !Item->filter) {
      if (prevItem == NULL)
	*exportHead = Item->nextExport;
      else
	prevItem->nextExport = Item->nextExport;
    }
    else
      prevItem = Item;
    Item = Item->nextExport;
  }
  return;
}

/******************************************************************************
* FreeExportBuffers.
******************************************************************************/

void FreeExportBuffers (exportHead)
struct ItemStruct *exportHead;
{
  struct ItemStruct *Item;
  for (Item = exportHead; Item != NULL; Item = Item->nextExport) {
     if (Item->Var->buffer != NULL) cdf_FreeMemory (Item->Var->buffer,
						FatalError);
  }
  return;
}

/******************************************************************************
* FindFirstRecord.
******************************************************************************/

long FindFirstRecord (recX, scalarHead, filteringScalars, recCount)
long recX;
struct ItemStruct *scalarHead;
Logical filteringScalars;
long recCount;
{
  struct ItemStruct *Item; long firstX;
  if (!filteringScalars) return recX;
  for (firstX = recX; firstX < recCount; firstX++) {
     Logical passes = TRUE;
     for (Item = scalarHead; Item != NULL; Item = Item->nextScalar) {
	if (Item->filter) {
	  Byte *value = Item->Var->buffer +
		        (size_t) (firstX * Item->Var->nValueBytes);
	  if (!VarPassesMin(Item,value) || !VarPassesMax(Item,value)) {
	    passes = FALSE;
	    break;
	  }
	}
     }
     if (passes) return firstX;
  }
  return NO_RECORD;
}

/******************************************************************************
* FindLastRecord.
******************************************************************************/

long FindLastRecord (firstX, scalarHead, filteringScalars, recCount)
long firstX;
struct ItemStruct *scalarHead;
Logical filteringScalars;
long recCount;
{
  struct ItemStruct *Item; long recX;
  if (!filteringScalars) return (recCount - 1);
  for (recX = firstX + 1; recX < recCount; recX++) {
     for (Item = scalarHead; Item != NULL; Item = Item->nextScalar) {
	if (Item->filter) {
	  Byte *value = Item->Var->buffer +
		        (size_t) (recX * Item->Var->nValueBytes);
	  if (!VarPassesMin(Item,value) ||
	      !VarPassesMax(Item,value)) return (recX - 1);
	}
     }
  }
  return (recCount - 1);
}

/******************************************************************************
* FilterBuffer.
******************************************************************************/

void FilterBuffer (Item, buffer, nValues)
struct ItemStruct *Item;
Byte *buffer;
long nValues;
{
  long valueN; Byte *value;
  for (valueN = 0, value = buffer; valueN < nValues;
       valueN++, value += Item->Var->nValueBytes) {
     if (!VarPassesMin(Item,value) || !VarPassesMax(Item,value)) {
       if (USEFILL(Item->Var,opt))
	 ASSIGNx (value, Item->Var->fill,
		  Item->Var->dataType, Item->Var->numElems);
       else
	 ASSIGNx (value, Item->Var->pad,
		  Item->Var->dataType, Item->Var->numElems);
     }
  }
  return;
}

/******************************************************************************
* FilterHypers.
******************************************************************************/

void FilterHypers (hyperHead, nValues)
struct ItemStruct *hyperHead;
long nValues;
{
  long valueN; Byte *value; struct ItemStruct *Item;
  for (valueN = 0; valueN < nValues; valueN++) {
     for (Item = hyperHead; Item != NULL; Item = Item->nextHyper) {
	if (Item->filter) {
	  value = VALUEinBUFFER (Item->Var, valueN);
	  if (!VarPassesMin(Item,value) || !VarPassesMax(Item,value)) {
	    for (Item = hyperHead; Item != NULL; Item = Item->nextHyper) {
	       if (Item->output) {
		 value = VALUEinBUFFER (Item->Var, valueN);
		 if (USEFILL(Item->Var,opt))
		   ASSIGNx (value, Item->Var->fill, Item->Var->dataType,
			    Item->Var->numElems);
		 else
		   ASSIGNx (value, Item->Var->pad, Item->Var->dataType,
			    Item->Var->numElems);
	       }
	    }
	    break;
	  }
	}
     }
  }
  return;
}

/******************************************************************************
* AbortListing.
* Does nothing if in batch mode.
******************************************************************************/

Logical AbortListing (oFp, line)
FILE *oFp;
char *line;
{
  int key;
  static char keyDefsBlank[] = "\n\n";
  if (BATCH(batchMode)) return FALSE;
  if (read_input(
#if defined(CURSESui)
		 EWmsg->wid,
#endif
			    &key,PASSTHRUri,FALSE)) {
    if (key == ABORTkey_EXPORT) {
      NEWkeyDEFS (EWkey, keyDefsBlank, batchMode)
      DisplayMessage ("Aborting at user's request.", NOBEEPWAIT1);
      DisplayMessage ("Listing file is not complete.", BEEPWAIT);
      if (fclose(oFp) == EOF) {
	DisplayMessage ("Error closing listing file.", BEEPWAIT);
      }
      cdf_FreeMemory (line, FatalError);
      return TRUE;
    }
    else
      EditWindow (BEEPew, EWmsg);
  }
  return FALSE;
}

/******************************************************************************
* AbortCDF.
* Does nothing if in batch mode.
******************************************************************************/

Logical AbortCDF (exportHead)
struct ItemStruct *exportHead;
{
  int key;
  static char keyDefsBlank[] = "\n\n";
  if (BATCH(batchMode)) return FALSE;
  if (read_input(
#if defined(CURSESui)
		 EWmsg->wid,
#endif
			    &key,PASSTHRUri,FALSE)) {
    if (key == ABORTkey_EXPORT) {
      NEWkeyDEFS (EWkey, keyDefsBlank, batchMode)
      DisplayMessage ("Aborting at user's request.", NOBEEPWAIT1);
      DisplayMessage ("CDF is not complete.", BEEPWAIT);
      FreeExportBuffers (exportHead);
      return TRUE;
    }
    else
      EditWindow (BEEPew, EWmsg);
  }
  return FALSE;
}

/******************************************************************************
* PreAllocateRecords.
******************************************************************************/

Logical PreAllocateRecords (inID, outID, scalarHead, hyperHead, firstRec,
			    lastRec)
CDFid inID;
CDFid outID;
struct ItemStruct *scalarHead;
struct ItemStruct *hyperHead;
long *firstRec;
long *lastRec;
{
  long nRecords = *lastRec - *firstRec + 1, newFirstRec, newLastRec, nHypers;
  long nValues, hyperN, recX, firstX, lastX, recF, recL;
  CDFstatus status; struct ItemStruct *Item; struct HyperStruct hyper;
  size_t nBytes, *nValueBytes; Byte ***handles; struct GroupStruct groups;
  int filterCount, filterX;
  AOSs1A (allocMsg,"Allocating variable records...")
  /****************************************************************************
  * Count number of scalars still being filtered.  Note that monotonic
  * scalars will have already been considered when the first/last record was
  * determined (and filtering turned off for them).
  ****************************************************************************/
  DisplayPctComplete (NO_PCT, allocMsg[0]);
  for (Item = scalarHead, filterCount = 0;
       Item != NULL; Item = Item->nextScalar) if (Item->filter) filterCount++;
  /****************************************************************************
  * If no scalars are being filtered, allocate records based on the current
  * first/last records.
  ****************************************************************************/
  if (filterCount == 0) {
    for (Item = scalarHead; Item != NULL; Item = Item->nextScalar) {
       if (Item->output) {
	 Item->Var->oRecords = COUNTx(Item->Var->maxRec,*firstRec,*lastRec);
       }
    }
    for (Item = hyperHead; Item != NULL; Item = Item->nextHyper) {
       if (Item->output) {
	 Item->Var->oRecords = COUNTx(Item->Var->maxRec,*firstRec,*lastRec);
       }
    }
    if (!AllocateRecords(outID,scalarHead,hyperHead)) return FALSE;
    return TRUE;
  }
  /****************************************************************************
  * Allocate buffers for the scalars.
  ****************************************************************************/
  nBytes = filterCount * sizeof(Byte **);
  handles = (Byte ***) cdf_AllocateMemory (nBytes, FatalError);
  nBytes = filterCount * sizeof(size_t);
  nValueBytes = (size_t *) cdf_AllocateMemory (nBytes, FatalError);
  for (Item = scalarHead, filterX = 0;
       Item != NULL; Item = Item->nextScalar) {
     if (Item->filter) {
       handles[filterX] = &(Item->Var->buffer);
       nValueBytes[filterX++] = Item->Var->nValueBytes;
     }
  }
  AllocateBuffers (nRecords, 0L, NULL, &groups, filterCount, 0, handles,
		   nValueBytes, ROWmajor(inMajority), 1, FatalError);
  cdf_FreeMemory (handles, FatalError);
  cdf_FreeMemory (nValueBytes, FatalError);
  /****************************************************************************
  * Initialize new first/last records and output record counts (for those
  * variables being output).
  ****************************************************************************/
  newFirstRec = *lastRec + 1;
  newLastRec = *firstRec - 1;
  for (Item = scalarHead; Item != NULL; Item = Item->nextScalar) {
     if (Item->output) Item->Var->oRecords = 0;
  }
  for (Item = hyperHead; Item != NULL; Item = Item->nextHyper) {
     if (Item->output) Item->Var->oRecords = 0;
  }
  /****************************************************************************
  * Read values for scalars being filtered and count the number of records
  * that pass the filters.  Also update the first/last records if needed.
  ****************************************************************************/
  InitHyperParms (&hyper, &groups, 0L, &nHypers, &nValues);
  for (hyperN = 0; hyperN < nHypers; hyperN++) {
     /*************************************************************************
     * Hyper read scalar values.
     *************************************************************************/
     for (Item = scalarHead; Item != NULL; Item = Item->nextScalar) {
	if (Item->filter) {
	  status = HYPERget (inID, Item->Var->varN, Item->Var->zVar,
			     *firstRec + hyper.recNumber, hyper.recCount,
			     dimIndices_0, dimCounts_1, Item->Var->buffer);
	  DisplayStatus (status, readingCDF);
	  if (StatusBAD(status)) {
	    for (Item = scalarHead; Item != NULL; Item = Item->nextScalar) {
	       cdf_FreeMemory (Item->Var->buffer, FatalError);
	    }
	    return FALSE;
	  }
	}
     }
     /*************************************************************************
     * Until no more records...
     *************************************************************************/
     for (recX = 0;;) {
	/**********************************************************************
	* Determine next range of records in this hyper read that pass the
	* filters.
	**********************************************************************/
	firstX = FindFirstRecord (recX, scalarHead, TRUE, hyper.recCount);
	if (firstX == NO_RECORD) break;
	lastX = FindLastRecord (firstX, scalarHead, TRUE, hyper.recCount);
	/**********************************************************************
	* Calculate the absolute record number of this range and set the new
	* first/last records accordingly.
	**********************************************************************/
	recF = *firstRec + hyper.recNumber + firstX;
	recL = *firstRec + hyper.recNumber + lastX;
	newFirstRec = MINIMUM(newFirstRec,recF);
	newLastRec = MAXIMUM(newLastRec,recL);
	/**********************************************************************
	* Add to the output record count of those variables being output.
	**********************************************************************/
	for (Item = scalarHead; Item != NULL; Item = Item->nextScalar) {
	   if (Item->output) {
	     Item->Var->oRecords += COUNTx(Item->Var->maxRec,recF,recL);
	   }
	}
	for (Item = hyperHead; Item != NULL; Item = Item->nextHyper) {
	   if (Item->output) {
	     Item->Var->oRecords += COUNTx(Item->Var->maxRec,recF,recL);
	   }
	}
	/**********************************************************************
	* Increment to check for the next range of records.
	**********************************************************************/
	recX = lastX + 1;
	if (recX == hyper.recCount) break;
     }
     /*************************************************************************
     * Increment to next hyper.
     *************************************************************************/
     IncrHyperParms (&hyper, &groups, 0L, ROWmajor(inMajority), &nValues);
  }
  /****************************************************************************
  * Free memory used.
  ****************************************************************************/
  for (Item = scalarHead; Item != NULL; Item = Item->nextScalar) {
     if (Item->Var->buffer != NULL) 
       cdf_FreeMemory (Item->Var->buffer, FatalError);
  }
  /****************************************************************************
  * Check/set the new first/last records.
  ****************************************************************************/
  if (newLastRec < newFirstRec) {
    DisplayMessage ("No records in valid range.", BEEPWAIT1);
    return FALSE;
  }
  *firstRec = newFirstRec;
  *lastRec = newLastRec;
  /****************************************************************************
  * Allocate the records.
  ****************************************************************************/
  if (!AllocateRecords(outID,scalarHead,hyperHead)) return FALSE;
  return TRUE;
}

/******************************************************************************
* AllocateRecords.
******************************************************************************/

static Logical AllocateRecords (outID, scalarHead, hyperHead)
CDFid outID;
struct ItemStruct *scalarHead;
struct ItemStruct *hyperHead;
{
  CDFstatus status; struct ItemStruct *Item; int varCount = 0, varNum = 0;
  AOSs1A (allocMsg,"Allocating variable records.")
#if defined(vms)
  long rItem = rVAR_INITIALRECS_;
  long zItem = zVAR_INITIALRECS_;
#else
  long rItem = rVAR_ALLOCATERECS_;
  long zItem = zVAR_ALLOCATERECS_;
#endif
  /****************************************************************************
  * Count number of variables being allocated.
  ****************************************************************************/
  for (Item = scalarHead; Item != NULL; Item = Item->nextScalar) {
     if (Item->output && Item->Var->oRecords > 0) varCount++;
  }
  for (Item = hyperHead; Item != NULL; Item = Item->nextHyper) {
     if (Item->output && Item->Var->oRecords > 0) varCount++;
  }
  /****************************************************************************
  * Select output CDF.
  ****************************************************************************/
  status = CDFlib (SELECT_, CDF_, outID,
		   NULL_);
  DisplayStatus (status, "selecting output CDF");
  if (StatusBAD(status)) return FALSE;
  /****************************************************************************
  * Allocate records for scalar variables being output.  Also check that one
  * or more records will be written to the variable.
  ****************************************************************************/
  for (Item = scalarHead; Item != NULL; Item = Item->nextScalar) {
     if (Item->output && Item->Var->oRecords > 0) {
       struct VarStruct *Var = Item->Var;
       if (Var->sRecordsType == NO_SPARSERECORDS &&
	   Var->sArraysType == NO_SPARSEARRAYS &&
	   Var->cType == NO_COMPRESSION) {
         status = CDFlib (SELECT_, BOO(Var->zVar,zVAR_,rVAR_), Var->varNo,
			  PUT_, BOO(Var->zVar,zItem,rItem), Var->oRecords,
			  NULL_);
         DisplayStatus (status, "allocating output records");
         if (StatusBAD(status)) return FALSE;
       }
       DisplayPctComplete (PCT(varNum,varCount,1,1), allocMsg[0]);
       varNum++;
     }
  }
  /****************************************************************************
  * Allocate records for hyper variables being output.  Also check that one
  * or more records will be written to the variable.
  ****************************************************************************/
  for (Item = hyperHead; Item != NULL; Item = Item->nextHyper) {
     if (Item->output && Item->Var->oRecords > 0) {
       struct VarStruct *Var = Item->Var;
       if (Var->sRecordsType == NO_SPARSERECORDS &&
	   Var->sArraysType == NO_SPARSEARRAYS &&
	   Var->cType == NO_COMPRESSION) {
         status = CDFlib (SELECT_, BOO(Var->zVar,zVAR_,rVAR_), Var->varNo,
			  PUT_, BOO(Var->zVar,zItem,rItem), Var->oRecords,
			  NULL_);
         DisplayStatus (status, "allocating output records");
         if (StatusBAD(status)) return FALSE;
       }
       DisplayPctComplete (PCT(varNum,varCount,1,1), allocMsg[0]);
       varNum++;
     }
  }
  return TRUE;
}

/******************************************************************************
* ToWalk.
* Returns FALSE if a fatal error occurred.
******************************************************************************/

Logical ToWalk () {
  CDFstatus status; struct ItemStruct *exportHead, *Item;
  long numDims, dimSizes[CDF_MAX_DIMS], indices[CDF_MAX_DIMS], recordN = 0;
  int varCount = 0, itemN = 0, varX, searchType; long maxRec = NO_RECORD;
  static Logical first = TRUE; void *searchValue;
  struct VarStruct *Vars[MAXwalkingVARs];
  static int lineNs[1+CDF_MAX_DIMS+MAXwalkingVARs];
  static int colS[1+CDF_MAX_DIMS+MAXwalkingVARs];
  static int lenS[1+CDF_MAX_DIMS+MAXwalkingVARs];
  AOSs15 (lineS, BLANKs78, BLANKs78, BLANKs78, BLANKs78, BLANKs78, BLANKs78,
	  BLANKs78, BLANKs78, BLANKs78, BLANKs78, BLANKs78, BLANKs78, BLANKs78,
	  BLANKs78, BLANKs78)
  static int exitChars[] = {
    EXITkey_FSI, INCREMENTkey_EDIT, DECREMENTkey_EDIT, HELPkey_FSI,
    NEXTFIELDkey_EXPORT, PREVFIELDkey_EXPORT, SWITCHkey_EXPORT,
    OPTIONSkey_EXPORT, ENTERkey_FSI, NUL
  };
  static struct ItemWindowStruct IW = {
    0, 0, SCREEN_WIDTH, " Walking... ", 0, NULL, 0, lineS, 0, lineNs, colS,
    lenS, 2 + MAXwalkingVARs, 0, NULL, exitChars, REFRESHkey_FSI, FALSE, NUL,
    NUL
  };
  static char keyDefsBlank[] = "\n\n";
  static char keyDefsAbort[] = "Abort: ________\n\n";
  static char keyDefsRI[] = "Enter:  ________  Next field: __________  Increment: _______    Help: ________\nVariables: _____  Prev field: __________  Decrement: _________  Exit: ________\n";
  static char keyDefsVAR[] = "Search: ________       Next variable: ___________  Help: ________\nRecord/indices: _____  Prev variable: ___________  Exit: ________\n";
  /***************************************************************************
  * First time...
  ***************************************************************************/
  if (first) {
    char *p1 = keyDefsRI;
    EncodeKeyDefinitions (1, &p1, ENTERkey_FSI, NEXTFIELDkey_EXPORT,
			  INCREMENTkey_EXPORT, HELPkey_FSI, SWITCHkey_EXPORT,
			  PREVFIELDkey_EXPORT, DECREMENTkey_EXPORT,
			  EXITkey_FSI);
    p1 = keyDefsVAR;
    EncodeKeyDefinitions (1, &p1, ENTERkey_FSI, DECREMENTkey_EXPORT,
			  HELPkey_FSI, SWITCHkey_EXPORT, INCREMENTkey_EXPORT,
			  EXITkey_FSI);
    p1 = keyDefsAbort;
    EncodeKeyDefinitions (1, &p1, ABORTkey_EXPORT);
    first = FALSE;
  }
  /****************************************************************************
  * Build export list.  This should contain only variables being output.
  ****************************************************************************/
  BuildExportList (&exportHead, LogicalTRUE);
  /****************************************************************************
  * Check that the dimensionalities are the same.
  ****************************************************************************/
  if (!SameDimensionalities(&numDims,dimSizes,exportHead)) {
    static char msg[] = {
      "Walking allowed only if the variable dimensionalities are the same."
    };
    DisplayMessage (msg, BEEPWAIT2);
    return TRUE;
  }    
  /****************************************************************************
  * Check if enough room for each variable.
  ****************************************************************************/
  for (Item = exportHead; Item != NULL; Item = Item->nextExport) {
     varCount++;
     maxRec = MAXIMUM (maxRec, Item->Var->maxRec);
     if (varCount == MAXwalkingVARs && Item->nextExport != NULL) {
       Item->nextExport = NULL;
       DisplayMessage ("Not enough room for all the variables.", NOBEEPWAIT1);
       break;
     }
  }
  /****************************************************************************
  * Check if valid selections.
  ****************************************************************************/
  if (varCount == 0) {
    DisplayMessage ("No variables selected for walking.", BEEPWAIT2);
    return TRUE;
  }
  if (maxRec == NO_RECORD) {
    DisplayMessage ("No variable records exist.", BEEPWAIT2);
    return TRUE;
  }
  /****************************************************************************
  * Display a "loading" walking window.
  ****************************************************************************/
  strcpyX (lineS[0], "Loading...", 0);
  IW.NiLines = 1;
  ItemWindow (NEWiw, &IW, 0);
  NEWkeyDEFS (EWkey, keyDefsBlank, batchMode)
  /****************************************************************************
  * Display variable values and "walk" until exit requested.
  ****************************************************************************/
  IW.nItems = (int) (1 + numDims + varCount);
  ARRAYtoVALUE (indices, 0, numDims)
  IW.NiLines = varCount + 2;
  for (;;) {
     /*************************************************************************
     * Update with variable values...
     *************************************************************************/
     status = BuildWalkScreen (exportHead, recordN, numDims, indices,
			       lineS, lineNs, colS, lenS, Vars);
     if (StatusBAD(status)) {
       NEWkeyDEFS (EWkey, keyDefsBlank, batchMode)
       DisplayStatus (status, readingCDF);
       ItemWindow (DELETEiw, &IW);
       return (!NoMoreAccess(NULL));
     }
     ItemWindow (UPDATEiw, &IW, itemN);
     if (INCLUSIVE(0,IW.itemN,numDims)) {
       NEWkeyDEFS (EWkey, keyDefsRI, batchMode)
     }
     else {
       NEWkeyDEFS (EWkey, keyDefsVAR, batchMode)
     }
     /*************************************************************************
     * ...and do as the user commands.
     *************************************************************************/
     ItemWindow (READiw, &IW);
     switch (IW.key) {
       /***********************************************************************
       * Enter...
       ***********************************************************************/
       case ENTERkey_FSI:
	 /*********************************************************************
	 * ...the record number.
	 *********************************************************************/
	 if (IW.itemN == 0) {
	   PromptForRecordNumber (&recordN, maxRec);
	   NEWkeyDEFS (EWkey, keyDefsRI, batchMode)
	   break;
	 }
	 /*********************************************************************
	 * ...a dimension index.
	 *********************************************************************/
	 if (INCLUSIVE(1,IW.itemN,numDims)) {
	   int dimN = IW.itemN - 1;
	   PromptForDimensionIndex (&indices[dimN], dimSizes[dimN]);
	   NEWkeyDEFS (EWkey, keyDefsRI, batchMode)
	   break;
	 }
	 /*********************************************************************
	 * ...a variable value.
	 *********************************************************************/
	 varX = (int) (IW.itemN - 1 - numDims);
	 if (PromptForVariableSearch(Vars[varX],&searchType,&searchValue)) {
	   double timeMark = SystemClock ();
	   DisplayMessage ("Searching...", NOWAIT);
	   NEWkeyDEFS (EWkey, keyDefsAbort, batchMode)
	   status = SearchForVariableValue (Vars[varX], numDims, dimSizes,
					    &recordN, indices, maxRec,
					    searchType, searchValue);
	   if (StatusBAD(status)) {
	     NEWkeyDEFS (EWkey, keyDefsBlank, batchMode)
	     DisplayStatus (status, readingCDF);
	     cdf_FreeMemory (searchValue, FatalError);
	     ItemWindow (DELETEiw, &IW);
	     return (!NoMoreAccess(NULL));
	   }
	   cdf_FreeMemory (searchValue, FatalError);
	   for (;;) if (1.0 <= SystemClock() - timeMark) break;
	   DisplayMessage ("", NOWAIT);
	 }
	 NEWkeyDEFS (EWkey, keyDefsVAR, batchMode)
	 break;
       /***********************************************************************
       * Switch between record/indices and variables...
       ***********************************************************************/
       case SWITCHkey_EXPORT:
	 itemN = BOO(INCLUSIVE(0,IW.itemN,numDims),(int)(numDims + 1),0);
	 break;
       /***********************************************************************
       * Next/previous field...
       ***********************************************************************/
       case NEXTFIELDkey_EXPORT:
       case PREVFIELDkey_EXPORT: {
	 if (INCLUSIVE(0,IW.itemN,numDims))
	   itemN = BOO(IW.key == NEXTFIELDkey_EXPORT,
		       BOO(IW.itemN < numDims,IW.itemN + 1,0),
		       BOO(IW.itemN > 0,IW.itemN - 1,(int)numDims));
	 else
	   ItemWindow (BEEPiw, &IW);
	 break;
       }
       /***********************************************************************
       * Increment/decrement...
       ***********************************************************************/
       case INCREMENTkey_EXPORT:
       case DECREMENTkey_EXPORT: {
	 Logical increase = (IW.key == INCREMENTkey_EXPORT);
	 /*********************************************************************
	 * ...the record number.
	 *********************************************************************/
	 if (IW.itemN == 0) {
	   recordN = BOO(increase,
			 BOO(recordN == maxRec,0,recordN + 1),
			 BOO(recordN == 0,maxRec,recordN - 1));
	   break;
	 }
	 /*********************************************************************
	 * ...a dimension index.
	 *********************************************************************/
	 if (INCLUSIVE(1,IW.itemN,numDims)) {
	   int dimN = IW.itemN - 1;
	   indices[dimN] = BOO(increase,
			       BOO(indices[dimN] == dimSizes[dimN] - 1,
				   0,indices[dimN] + 1),
			       BOO(indices[dimN] == 0,
				   dimSizes[dimN] - 1,indices[dimN] - 1));
	   break;
	 }
	 /*********************************************************************
	 * ...to the next/previous variable.
	 *********************************************************************/
	 itemN = BOO(IW.key == DECREMENTkey_EXPORT,
		     BOO(IW.itemN < IW.nItems - 1,
			 IW.itemN + 1,(int)(1 + numDims)),
		     BOO(IW.itemN > 1 + numDims,
			 IW.itemN - 1,IW.nItems - 1));
	 break;
       }
       case OPTIONSkey_EXPORT:
	 OptionMenu ();
	 break;
       case EXITkey_FSI:
	 NEWkeyDEFS (EWkey, keyDefsBlank, batchMode)
	 ItemWindow (DELETEiw, &IW);
	 return TRUE;
       case HELPkey_FSI:
	 OnlineHelpWindow ("cdfxp.ilh", BOO(INCLUSIVE(0,IW.itemN,numDims),
					    WALKriHelpID,WALKvarHelpID));
	 break;
     }
  }
}

/******************************************************************************
* BuildWalkScreen.
******************************************************************************/

static CDFstatus BuildWalkScreen (exportHead, recordN, numDims, indices,
				  lineS, lineNs, colS, lenS, Vars)
struct ItemStruct *exportHead;
long recordN;
long numDims;
long indices[];
char *lineS[];
int lineNs[];
int colS[];
int lenS[];
struct VarStruct *Vars[];
{
  CDFstatus status, pStatus = CDF_OK; int dimN, lineN, itemN = 0, varX;
  struct ItemStruct *Item; char *rightField;
  /****************************************************************************
  * Encode record/indices line.
  ****************************************************************************/
  MakeNUL (lineS[0]);
  CatToString (lineS[0], "Record/Indices.......", WALKleftLEN, LEFT_JUSTIFY,
	       dots);
  strcatX (lineS[0], " ", 0);
  colS[itemN] = strlen(lineS[0]);
  sprintf (EofS(lineS[0]), "%ld", (long) (recordN + 1));
  lenS[itemN] = strlen(&(lineS[0][colS[itemN]]));
  lineNs[itemN++] = 0;
  strcatX (lineS[0], ":[", 0);
  for (dimN = 0; dimN < numDims; dimN++) {
     if (dimN > 0) strcatX (lineS[0], ",", 0);
     colS[itemN] = strlen(lineS[0]);
     sprintf (EofS(lineS[0]), "%ld", (long) (indices[dimN] + 1));
     lenS[itemN] = strlen(&(lineS[0][colS[itemN]]));
     lineNs[itemN++] = 0;
  }
  strcatX (lineS[0], "]", 0);
  /****************************************************************************
  * A blank line is next.
  ****************************************************************************/
  MakeNUL (lineS[1]);
  /****************************************************************************
  * Encode the variable lines.
  ****************************************************************************/
  for (Item = exportHead, lineN = 2, varX = 0;
       Item != NULL; Item = Item->nextExport, lineN++, varX++) {
     EncodeString ((long) strlen(Item->Var->name), Item->Var->name,
		   lineS[lineN], -WALKleftLEN, WALKleftLEN);
     strcatX (lineS[lineN], " ", 0);
     status = CDFlib (SELECT_, VAR(Item->Var->zVar), Item->Var->varN,
			       BOO(Item->Var->zVar,zVAR_RECNUMBER_,
						   rVARs_RECNUMBER_), recordN,
			       BOO(Item->Var->zVar,zVAR_DIMINDICES_,
						   rVARs_DIMINDICES_), indices,
		      GET_, VAR_DATA(Item->Var->zVar), Item->Var->value,
		      NULL_);
     if (!sX(status,&pStatus)) return pStatus;
     colS[itemN] = strlen(lineS[lineN]);
     rightField = EofS(lineS[lineN]);
     EncodeValuesFormat (Item->Var->dataType, Item->Var->numElems,
			 Item->Var->value, rightField, Item->Var->format, 0,
			 WALKrightLEN, opt.epochStyle);
     RemoveLeadingBlanks (rightField);
     lenS[itemN] = strlen(rightField);
     lineNs[itemN++] = lineN;
     Vars[varX] = Item->Var;
  }
  return pStatus;
}

/******************************************************************************
* PromptForRecordNumber.
******************************************************************************/

static Logical PromptForRecordNumber (recordN, maxRec)
long *recordN;
long maxRec;
{
  static char value[MAXrecordNumberLEN+1]; long newRecordN;
  sprintf (value, "%ld", (long) (*recordN + 1));
  if (!PromptFor(value,MAXrecordNumberLEN,strlen(value),
		 "Enter the record number...",RECORDhelpID)) return FALSE;
  if (NULstring(value)) return FALSE;
  if (sscanf(value,"%ld",&newRecordN) != 1) {
    DisplayMessage ("Error decoding record number.", BEEPWAIT1);
    return FALSE;
  }
  if (!INCLUSIVE(1,newRecordN,maxRec+1)) {
    DisplayMessage ("Record number out of range.", BEEPWAIT1);
    return FALSE;
  }
  *recordN = newRecordN - 1L;
  return TRUE;
}

/******************************************************************************
* PromptForDimensionIndex.
******************************************************************************/

static Logical PromptForDimensionIndex (index, dimSize)
long *index;
long dimSize;
{
  static char value[MAXdimensionIndexLEN+1]; long newIndex;
  sprintf (value, "%ld", (long) (*index + 1));
  if (!PromptFor(value,MAXdimensionIndexLEN,strlen(value),
		 "Enter the dimension index...",INDEXhelpID)) return FALSE;
  if (NULstring(value)) return FALSE;
  if (sscanf(value,"%ld",&newIndex) != 1) {
    DisplayMessage ("Error decoding dimension index.", BEEPWAIT1);
    return FALSE;
  }
  if (!INCLUSIVE(1,newIndex,dimSize)) {
    DisplayMessage ("Dimension index out of range.", BEEPWAIT1);
    return FALSE;
  }
  *index = newIndex - 1L;
  return TRUE;
}

/******************************************************************************
* PromptForVariableSearch.
******************************************************************************/

static Logical PromptForVariableSearch (Var, searchType, searchValue)
struct VarStruct *Var;
int *searchType;
void **searchValue;
{
  static char value[MAXvalueLEN+1]; long numElems; int iniChar;
  static Logical first = TRUE; Logical done = FALSE;
  static int exitChars[] = { ENTERkey_FSI, EXITkey_FSI, HELPkey_FSI, NUL };
  AOSs1 (iLines, "EQUAL   LESS   LESS/EQUAL   GREATER   GREATER/EQUAL")
  static int iLineNs[] = { 0,0,0,0,0 };
  static int iCols[] = { 0,8,15,28,38 };
  static int iLens[] = { 5,4,10,7,13 };
  static struct ItemWindowStruct IW = {
    21, 0, 80, " Select search type... ", 0, NULL, 1, iLines, 5, iLineNs,
    iCols, iLens, 1, 0, NULL, exitChars, REFRESHkey_FSI, FALSE, NUL, NUL
  };
  static char keyDefs[] = {
    "Enter: ________    Help: ________    Exit: ________\n\n"
  };
  static char keyDefsBlank[] = "";
  /****************************************************************************
  * First time...
  ****************************************************************************/
  if (first) {
    char *p1 = keyDefs;
    EncodeKeyDefinitions (1, &p1, ENTERkey_FSI, HELPkey_FSI, EXITkey_FSI);
    first = FALSE;
  }
  /****************************************************************************
  * Prompt for type of search.
  ****************************************************************************/
  NEWkeyDEFS (EWkey, keyDefsBlank, batchMode)
  ItemWindow (NEWiw, &IW, 0);
  NEWkeyDEFS (EWkey, keyDefs, batchMode)
  while (!done) {
     ItemWindow (READiw, &IW);
     switch (IW.key) {
       case ENTERkey_FSI:
	 switch (IW.itemN) {
	   case 0: *searchType = EQsearch; break;
	   case 1: *searchType = LTsearch; break;
	   case 2: *searchType = LEsearch; break;
	   case 3: *searchType = GTsearch; break;
	   case 4: *searchType = GEsearch; break;
	 }
	 NEWkeyDEFS (EWkey, keyDefsBlank, batchMode)
	 ItemWindow (DELETEiw, &IW);
	 done = TRUE;
	 break;
       case HELPkey_FSI:
	 OnlineHelpWindow ("cdfxp.ilh", SEARCHTYPEhelpID);
	 break;
       case EXITkey_FSI:
	 NEWkeyDEFS (EWkey, keyDefsBlank, batchMode)
	 ItemWindow (DELETEiw, &IW);
	 return FALSE;
     }
  }
  /****************************************************************************
  * Prompt for value.
  ****************************************************************************/
  EncodeValuesFormat (Var->dataType, Var->numElems, Var->value, value,
		      Var->format, 0, MAXvalueLEN, opt.epochStyle);
  RemoveLeadingBlanks (value);
  iniChar = strlen(value) - BOO(STRINGdataType(Var->dataType),1,0);
  if (!PromptFor(value,MAXvalueLEN,iniChar,
		 "Enter the search value...",VALUEhelpID)) return FALSE;
  if (NULstring(value)) return FALSE;
  if (!DecodeValues(value,Var->dataType,&numElems,
		    searchValue,opt.epochStyle)) {
    DisplayMessage ("Error decoding value.", BEEPWAIT1);
    return FALSE;
  }
  if (numElems != Var->numElems) {
    DisplayMessage ("Wrong number of elements.", BEEPWAIT1);
    cdf_FreeMemory (*searchValue, FatalError);
    return FALSE;
  }
  return TRUE;
}

/******************************************************************************
* SearchForVariableValue.
******************************************************************************/

static CDFstatus SearchForVariableValue (Var, numDims, dimSizes, recordAt,
				         indicesAt, maxRec, searchType,
					 searchValue)
struct VarStruct *Var;
long numDims;
long dimSizes[];
long *recordAt;
long indicesAt[];
long maxRec;
int searchType;
void *searchValue;
{
  CDFstatus status, pStatus = CDF_OK;
  long recordN, indices[CDF_MAX_DIMS], indicesFirst[CDF_MAX_DIMS], pct;
  long indicesLast[CDF_MAX_DIMS], nValuesPerRecord, dimCounts[CDF_MAX_DIMS];
  long nValues, valueN, nDimValues[CDF_MAX_DIMS], valueOffset, lastPct = -1L;
  size_t nBytes; void *value; int dimN;
  Logical outRowMajor = ROWmajor(MAJORITYtoOUT(opt.majority,inMajority));
  static Logical (*entryPoints[])PROTOARGs((void *, void *, long, long)) = {
    EQx, LTx, LEx, GTx, GEx
  };
  /****************************************************************************
  * Initialize...
  ****************************************************************************/
  recordN = BOO(Var->recVary,*recordAt,maxRec);
  for (dimN = 0; dimN < numDims; dimN++) {
     indices[dimN] = BOO(Var->dimVarys[dimN],indicesAt[dimN],0);
     indicesFirst[dimN] = 0;
     indicesLast[dimN] = BOO(Var->dimVarys[dimN],dimSizes[dimN] - 1,0);
     dimCounts[dimN] = indicesLast[dimN] - indicesFirst[dimN] + 1;
  }
  /****************************************************************************
  * Calculate the number of values from the current record/indices to the end
  * of the variable's values.
  ****************************************************************************/
  for (nValuesPerRecord = 1, dimN = 0; dimN < numDims; dimN++) {
     nValuesPerRecord *= dimCounts[dimN];
  }
  if (numDims > 0) {
    if (outRowMajor) {
      nDimValues[(int)(numDims-1)] = 1;
      for (dimN = (int) (numDims - 2); dimN >= 0; dimN--) {
	 nDimValues[dimN] = dimCounts[dimN+1] * nDimValues[dimN+1];
      }
    }
    else {
      nDimValues[0] = 1;
      for (dimN = 1; dimN < numDims; dimN++) {
	 nDimValues[dimN] = dimCounts[dimN-1] * nDimValues[dimN-1];
      }
    }
  }
  for (valueOffset = 0, dimN = 0; dimN < numDims; dimN++) {
     valueOffset += (indices[dimN] * nDimValues[dimN]);
  }
  nValues = nValuesPerRecord - valueOffset;
  nValues += (nValuesPerRecord * (maxRec - recordN));
  /****************************************************************************
  * Increment past the current record/indices.  Return if there is only one
  * value to search.
  ****************************************************************************/
  if (nValues > 1) {
     IncrRecordIndicesFirstLast (outRowMajor, &recordN, numDims,
				 indicesFirst, indicesLast, indices);
     nValues--;
  }
  else
    return pStatus;
  /****************************************************************************
  * Select the variable.
  ****************************************************************************/
  status = CDFlib (SELECT_, VAR(Var->zVar), Var->varN,
		   NULL_);
  if (!sX(status,&pStatus)) return pStatus;
  /****************************************************************************
  * Allocate memory to hold the value.
  ****************************************************************************/
  nBytes = (size_t) (Var->numElems * CDFelemSize(Var->dataType));
  value = (void *) cdf_AllocateMemory (nBytes, FatalError);
  /****************************************************************************
  * For each value to be searched...
  ****************************************************************************/
  for (valueN = 0; valueN < nValues; valueN++) {
     /*************************************************************************
     * Read the value.
     *************************************************************************/
     status = CDFlib (SELECT_, BOO(Var->zVar,zVAR_RECNUMBER_,
					     rVARs_RECNUMBER_), recordN,
		      NULL_);
     if (!sX(status,&pStatus)) {
       cdf_FreeMemory (value, FatalError);
       return pStatus;
     }
     if (numDims > 0) {
       status = CDFlib (SELECT_, BOO(Var->zVar,zVAR_DIMINDICES_,
					       rVARs_DIMINDICES_), indices,
		        NULL_);
       if (!sX(status,&pStatus)) {
         cdf_FreeMemory (value, FatalError);
         return pStatus;
       }
     }
     status = CDFlib (GET_, VAR_DATA(Var->zVar), value,
		      NULL_);
     if (!sX(status,&pStatus)) {
       cdf_FreeMemory (value, FatalError);
       return pStatus;
     }
     /*************************************************************************
     * If equal, set the record/indices and return.
     *************************************************************************/
     if ((entryPoints[searchType])(value,searchValue,
				   Var->dataType,Var->numElems)) {
       if (Var->recVary) *recordAt = recordN;
       for (dimN = 0; dimN < numDims; dimN++) {
	  if (Var->dimVarys[dimN]) indicesAt[dimN] = indices[dimN];
       }
       cdf_FreeMemory (value, FatalError);
       return pStatus;
     }
     /*************************************************************************
     * Update percentage complete.
     *************************************************************************/
     pct = (100L * valueN) / nValues;
     if (pct > lastPct) {
       static char msg[] = "Searching...                                                                  ";
       sprintf (&msg[75], "%2ld%%", pct);
       DisplayMessage (msg, NOWAIT);
       lastPct = pct;
     }
     /*************************************************************************
     * Check if search should be aborted.
     *************************************************************************/
     if (AbortSearch()) {
       cdf_FreeMemory (value, FatalError);
       return pStatus;
     }
     /*************************************************************************
     * Increment to the next record/indices based on the majority selected.
     *************************************************************************/
     IncrRecordIndicesFirstLast (outRowMajor, &recordN, numDims,
				 indicesFirst, indicesLast, indices);
  }
  /****************************************************************************
  * The value was not found...
  ****************************************************************************/
  DisplayMessage ("Matching value not found.", BEEPWAIT1);
  cdf_FreeMemory (value, FatalError);
  return pStatus;
}

/******************************************************************************
* AbortSearch.
******************************************************************************/

static Logical AbortSearch () {
  int key;
  if (read_input(
#if defined(CURSESui)
		 EWmsg->wid,
#endif
			    &key,PASSTHRUri,FALSE)) {
    if (key == ABORTkey_EXPORT) {
      DisplayMessage ("Aborting at user's request.", NOBEEPWAIT1);
      return TRUE;
    }
    else
      EditWindow (BEEPew, EWmsg);
  }
  return FALSE;
}


syntax highlighted by Code2HTML, v. 0.9.1