/******************************************************************************
*
*  NSSDC/CDF                                        CDFcompare (differences).
*
*  Version 2.6b, 29-Sep-95, Hughes STX.
*
*  Modification history:
*
*   V1.0   1-Jun-91, J Love     Original version (for CDF V2.1).
*   V1.1  25-Jun-91, J Love     CDF_EPOCH added as a data type.  Added QOP.
*                               Added PageInst.
*   V1.2   6-Aug-91, J Love     TRUE/FALSE.  Use 'Exit'/'ExitBAD'.  Use
*                               'CDFlib'.  Display message if no differences
*                               found.
*   V1.3  10-Aug-91, J Love     Optimized comparison of variable values.
*   V2.0  17-May-92, J Love     IBM PC & HP port.
*   V2.1  17-Sep-92, J Love     CDF V2.3 (shareable/NeXT/zVar).
*   V2.1a  6-Oct-92, J Love     Fixed freeing of `bufferX' when 1-dimensional.
*   V2.2  25-Jan-94, J Love     CDF V2.4.
*   V2.2a 22-Feb-94, J Love     Spelling lesson.
*   V2.3   9-Mar-94, J Love     Made parentheses enclosing zModes options (on
*                               all operating systems).
*   V2.4   6-Dec-94, J Love     CDF V2.5.
*   V2.4a 10-Jan-95, J Love     Uppercase file extensions on the Macintosh.
*   V2.4b 16-Mar-95, J Love     Display value(s) when a difference detected.
*   V2.4c  6-Apr-95, J Love     POSIX.
*   V2.5  25-May-95, J Love     EPOCH styles.  Fixed rVariables vs. zVariable
*                               comparison.
*   V2.5a 20-Jun-95, J Love     Fixed variable number matching (in zMode).
*   V2.6   1-Sep-95, J Love     CDFexport-related changes.  Hyper groups.
*   V2.6a 19-Sep-95, J Love	CHECKforABORTso.
*   V2.6b 29-Sep-95, J Love	Macintosh dialog filtering.  Outline default
*				button.  Less CHECKforABORTso.
*
******************************************************************************/

#include "cdfcmp.h"

/******************************************************************************
* Increased stack size for Borland C on IBM PC.
******************************************************************************/

#if defined(BORLANDC)
extern unsigned _stklen = BORLANDC_STACK_SIZE;
#endif

/******************************************************************************
* Global variables.
******************************************************************************/

CDFid id1, id2;
long rNumDims1, rNumDims2;
long rDimSizes1[CDF_MAX_DIMS], rDimSizes2[CDF_MAX_DIMS];
long zNumDims1, zNumDims2;
long zDimSizes1[CDF_MAX_DIMS], zDimSizes2[CDF_MAX_DIMS];
long encoding1, encoding2;
long majority1, majority2;
long format1, format2;
char copyright1[CDF_COPYRIGHT_LEN], copyright2[CDF_COPYRIGHT_LEN];
long rNumVars1, rNumVars2;
long zNumVars1, zNumVars2;
long numAttrs1, numAttrs2;
long CDFmaxRec1, CDFmaxRec2;
long version1, version2;
long release1, release2;
long increment1, increment2;
long attrScope1, attrScope2;
long attrMAXgrEntry1, attrMAXgrEntry2;
long attrNUMgrEntries1, attrNUMgrEntries2;
long attrMAXzEntry1, attrMAXzEntry2;
long attrNUMzEntries1, attrNUMzEntries2;
long varMaxRec1, varMaxRec2;
long varDataType1, varDataType2;
long varNumElems1, varNumElems2;
long varRecVary1, varRecVary2;
long varDimVarys1[CDF_MAX_DIMS], varDimVarys2[CDF_MAX_DIMS];
long varExtendRecs1, varExtendRecs2;
Logical mLog, pctLog;
Logical cmpEtc;
Logical cmpNumbers;
Logical cmpVars;
Logical cmpAttrs;
Logical negToPosFp0;
Logical locations;
long *attrNumMatches1, *attrNumMatches2;
long *rVarNumMatches1, *rVarNumMatches2;
long *zVarNumMatches1, *zVarNumMatches2;
Logical diffFound;
long zMode_1, zMode_2;
static char rVariable[] = "rVariable";
static char zVariable[] = "zVariable";
static char gEntry[] = "gEntry";
static char rEntry[] = "rEntry";
static char zEntry[] = "zEntry";
char oText[MAX_oTEXT_LEN+1];
Logical report[3];
Logical dumpStatistics;
Logical displayValue;
long cacheSize;                         /* Number of cache buffers for both
					   CDFs during a comparison. */

/******************************************************************************
* Main.
******************************************************************************/

MAIN {
#if defined(mac)
  strcpyX (pgmName, "CDFcompare", MAX_PROGRAM_NAME_LEN);
  MacExecuteSO (CompareCDFspecifications, CompareQOPs);
  if (FreeMemory(NULL,FatalError) > 0) DisplayWarning ("Abandoned buffers.");
  return EXIT_SUCCESS_;
#else
  Logical success;
  strcpyX (pgmName, "CDFcompare", MAX_PROGRAM_NAME_LEN);
  success = CompareCDFspecifications (argc, argv);
  if (FreeMemory(NULL,FatalError) > 0) DisplayWarning ("Abandoned buffers.");
  return BOO(success,EXIT_SUCCESS_,EXIT_FAILURE_);
#endif
}

/******************************************************************************
* CompareCDFspecifications..
******************************************************************************/

Logical CompareCDFspecifications (argC, argV)
int argC;
char *argV[];
{
  char CDFspec1[DU_MAX_PATH_LEN + 1], CDFspec2[DU_MAX_PATH_LEN + 1];
  char **dirs1 = NULL, **dirs2 = NULL;
  char **CDFs1 = NULL, **CDFs2 = NULL;
  int numCDFs1, numCDFs2;
  char CDFpath1[DU_MAX_PATH_LEN + 1], CDFpath2[DU_MAX_PATH_LEN + 1];
  char dir1[DU_MAX_DIR_LEN + 1], dir2[DU_MAX_DIR_LEN + 1];
  char CDF1[DU_MAX_NAME_LEN + 1], CDF2[DU_MAX_NAME_LEN + 1];
  int i, j;
  Logical qopError = FALSE;
  QOP *qop;
  static char *validQuals[] = {
    "log", "nolog", "attr", "noattr", "var", "novar", "number", "nonumber",
    "percent", "nopercent", "zmodes", "etc", "noetc", "neg2posfp0",
    "noneg2posfp0", "location", "nolocation", "report", "page", "nopage",
    "cache", "statistics", "nostatistics", "value", "novalue", NULL
  };
  static int optRequired[] = {
    FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
    TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, TRUE,
    FALSE, FALSE, FALSE, FALSE, 0
  };
  static char *reportTokens[] = { "errors", "warnings", "informationals" };

  /****************************************************************************
  * Get qualifiers/options/parameters.
  ****************************************************************************/

  switch (argC) {
    case 1:
      PageOLH ("cdfcmp.olh", argV[0]);
      return TRUE;
    default:
      qop = Qop (argC, argV, validQuals, optRequired);
      if (qop == NULL) return FALSE;

      /************************************************************************
      * Get CDF/paths to compare.
      ************************************************************************/

      switch (qop->Nparms) {
	case 0:
	case 1:
	  DisplayError ("Missing parameter(s).");
	  qopError = TRUE;
	  break;
	case 2:
	  strcpyX (CDFspec1, qop->parms[CDFSPEC1parm], DU_MAX_PATH_LEN);
	  strcpyX (CDFspec2, qop->parms[CDFSPEC2parm], DU_MAX_PATH_LEN);
#if defined(vms) || defined(dos)
	  Uppercase (CDFspec1);
	  Uppercase (CDFspec2);
#endif
	  break;
	default:
	  DisplayError ("Too many parameters.");
	  qopError = TRUE;
	  break;
      }

      /************************************************************************
      * Check for zMode qualifier.
      ************************************************************************/

      if (qop->qualEntered[ZMODESqual]) {
	char *modes = qop->qualOpt[ZMODESqual];
	if (modes[0] == '(' && modes[strlen(modes)-1] == ')') modes++;
	switch (modes[0]) {
	  case '0': zMode_1 = zMODEoff; break;
	  case '1': zMode_1 = zMODEon1; break;
	  case '2': zMode_1 = zMODEon2; break;
	  default: {
	    DisplayError ("Illegal zModes.");
	    qopError = TRUE;
	    break;
	  }
	}
	switch (modes[2]) {
	  case '0': zMode_2 = zMODEoff; break;
	  case '1': zMode_2 = zMODEon1; break;
	  case '2': zMode_2 = zMODEon2; break;
	  default: {
	    DisplayError ("Illegal zModes.");
	    qopError = TRUE;
	    break;
	  }
	}
      }
      else {
	zMode_1 = DEFAULTzModeCMP;
	zMode_2 = DEFAULTzModeCMP;
      }

      /************************************************************************
      * Check for `report' qualifier.  If absent, use defaults.
      ************************************************************************/

      if (qop->qualEntered[REPORTqual]) {
	if (!ParseOptionList(3,reportTokens,qop->qualOpt[REPORTqual],report)) {
	  DisplayError ("Illegal list of `report' options.");
	  qopError = TRUE;
	}
      }
      else {
	report[ERRORs] = REPORTerrorsDEFAULT;
	report[WARNs] = REPORTwarningsDEFAULT;
	report[INFOs] = REPORTinfosDEFAULT;
      }

      /************************************************************************
      * Check for `page', `log', `attr', `var', `number', `percent', `etc',
      * `neg2posfp0', `location', `value', and `statistics' qualifiers.
      ************************************************************************/

      qopError = qopError | !TFqualifier(qop,&pagingOn,PAGEqual,NOPAGEqual,
					 DEFAULTpageCMP,"page");
      qopError = qopError | !TFqualifier (qop,&mLog,LOGqual,NOLOGqual,
					  DEFAULTlogCMP,"log");
      qopError = qopError | !TFqualifier (qop,&cmpAttrs,ATTRSqual,NOATTRSqual,
					  DEFAULTvarCMP,"attr");
      qopError = qopError | !TFqualifier (qop,&cmpVars,VARSqual,NOVARSqual,
					  DEFAULTvarCMP,"var");
      qopError = qopError | !TFqualifier (qop,&cmpNumbers,NUMBERSqual,
					  NONUMBERSqual,DEFAULTnumberCMP,
					  "number");
      qopError = qopError | !TFqualifier (qop,&pctLog,PCTqual,NOPCTqual,
					  DEFAULTpctCMP,"percent");
      qopError = qopError | !TFqualifier (qop,&cmpEtc,ETCqual,NOETCqual,
					  DEFAULTetcCMP,"etc");
      qopError = qopError | !TFqualifier (qop,&negToPosFp0,NEG2POSqual,
					  NONEG2POSqual,DEFAULT_NEGtoPOSfp0,
					  "neg2posfp0");
      qopError = qopError | !TFqualifier (qop,&locations,LOCSqual,NOLOCSqual,
					  DEFAULTlocationCMP,"location");
      qopError = qopError | !TFqualifier (qop,&displayValue,VALUESqual,
					  NOVALUESqual,DEFAULTvalueCMP,
					  "value");
      qopError = qopError | !TFqualifier (qop,&dumpStatistics,STATSqual,
					  NOSTATSqual,DEFAULTstatsCMP,
					  "statistics");

      /************************************************************************
      * Check for `cache' qualifier.
      ************************************************************************/
      
      if (qop->qualEntered[CACHEqual]) {
	if (sscanf(qop->qualOpt[CACHEqual],"%ld",&cacheSize) != 1) {
	  DisplayError ("Illegal cache size for `.cdf' file.");
	  qopError = TRUE;
	}
      }
      else
	cacheSize = 0;

      /************************************************************************
      * Check for conflicting qualifiers.
      ************************************************************************/

      if (pctLog && (!mLog)) {
	DisplayError
	  ("Message logging must be specified with percentage logging.");
	qopError = TRUE;
      }

      /************************************************************************
      * Free QOP memory and check for an error.
      ************************************************************************/

      FreeMemory (qop, FatalError);
      if (qopError) return FALSE;

      break;
  }

  /****************************************************************************
  * Compare the CDFs.
  ****************************************************************************/

  if (!IsCDF(CDFspec1)) {
    if (!IsCDF(CDFspec2)) {
      /************************************************************************
      * Both specifications are directories/wildcards.
      ************************************************************************/
      numCDFs1 = CDFdirList (CDFspec1, &dirs1, &CDFs1);
      numCDFs2 = CDFdirList (CDFspec2, &dirs2, &CDFs2);
      if (numCDFs1 > 0)
	if (numCDFs2 > 0) {
	  for (i = 0; i < numCDFs1; i++) {
	     strcpyX (CDFpath1, dirs1[i], DU_MAX_PATH_LEN);
	     AppendToDir (CDFpath1, CDFs1[i]);
	     for (j = 0; j < numCDFs2; j++) {
		if (!strcmp(CDFs1[i],CDFs2[j])) {
		  strcpyX (CDFpath2, dirs2[j], DU_MAX_PATH_LEN);
		  AppendToDir (CDFpath2, CDFs2[j]);
		  CompareCDFs (CDFpath1, CDFpath2);
		  CleanupComparison ();
		  break;
		}
	     }
	     if (j == numCDFs2) {
	       char tempS[47+DU_MAX_PATH_LEN+1];
	       sprintf (tempS, "No matching CDF for \"%s\"", CDFpath1);
	       sprintf (EofS(tempS), " in second specification.");
	       OutputWithMargin (stdout, tempS, MAX_SCREENLINE_LEN, 0);
	     }
	  }
	  for (j = 0; j < numCDFs2; j++) {
	     strcpyX (CDFpath2, dirs2[j], DU_MAX_PATH_LEN);
	     AppendToDir (CDFpath2, CDFs2[j]);
	     for (i = 0; i < numCDFs1; i++) {
		if (!strcmp(CDFs2[j],CDFs1[i])) break;
	     }
	     if (i == numCDFs1) {
	       char tempS[47+DU_MAX_PATH_LEN+1];
	       sprintf (tempS, "No matching CDF for \"%s\"", CDFpath2);
	       sprintf (EofS(tempS), " in first specification.");
	       OutputWithMargin (stdout, tempS, MAX_SCREENLINE_LEN, 0);
	     }
	  }
	}
	else
	  OutputWithMargin (stdout, "No CDFs found for second specification.",
			    MAX_SCREENLINE_LEN, 0);
      else
	if (numCDFs2 > 0)
	  OutputWithMargin (stdout, "No CDFs found for first specification.",
			    MAX_SCREENLINE_LEN, 0);
	else
	  OutputWithMargin (stdout, "No CDFs for either specification.",
			    MAX_SCREENLINE_LEN, 0);
    }
    else {
      /************************************************************************
      * First specification is a directory/wildcard, second is a CDF path.
      ************************************************************************/
      numCDFs1 = CDFdirList (CDFspec1, &dirs1, &CDFs1);
      if (numCDFs1 > 0) {
	ParsePath (CDFspec2, dir2, CDF2);
	for (i = 0; i < numCDFs1; i++) {
	   if (!strcmp(CDFs1[i],CDF2)) {
	     strcpyX (CDFpath1, dirs1[i], DU_MAX_PATH_LEN);
	     AppendToDir (CDFpath1, CDFs1[i]);
	     CompareCDFs (CDFpath1, CDFspec2);
	     CleanupComparison ();
	     break;
	   }
	}
	if (i == numCDFs1) OutputWithMargin (stdout, "Matching CDF not found in first specification.", MAX_SCREENLINE_LEN, 0);
      }
      else
	OutputWithMargin (stdout, "No CDFs found for first specification.",
			  MAX_SCREENLINE_LEN, 0);
    }
  }
  else {
    if (!IsCDF(CDFspec2)) {
      /************************************************************************
      * First specification is a CDF path, second is a directory/wildcard.
      ************************************************************************/
      numCDFs2 = CDFdirList (CDFspec2, &dirs2, &CDFs2);
      if (numCDFs2 > 0) {
	ParsePath (CDFspec1, dir1, CDF1);
	for (i = 0; i < numCDFs2; i++)
	   if (!strcmp(CDF1,CDFs2[i])) {
	     strcpyX (CDFpath2, dirs2[i], DU_MAX_PATH_LEN);
	     AppendToDir (CDFpath2, CDFs2[i]);
	     CompareCDFs (CDFspec1, CDFpath2);
	     CleanupComparison ();
	     break;
	   }
	if (i == numCDFs2) OutputWithMargin (stdout, "Matching CDF not found in second specification.", MAX_SCREENLINE_LEN, 0);
      }
      else
	OutputWithMargin (stdout, "No CDFs found for second specification.",
			  MAX_SCREENLINE_LEN, 0);
    }
    else {
      /************************************************************************
      * Both specifications are CDF paths.
      ************************************************************************/
      CompareCDFs (CDFspec1, CDFspec2);
      CleanupComparison ();
    }
  }
  if (dirs1 != NULL) FreeMemory (dirs1, FatalError);
  if (dirs2 != NULL) FreeMemory (dirs2, FatalError);
  if (CDFs1 != NULL) FreeMemory (CDFs1, FatalError);
  if (CDFs2 != NULL) FreeMemory (CDFs2, FatalError);
  return TRUE;
}

/******************************************************************************
* CompareCDFs.
******************************************************************************/

Logical CompareCDFs (CDFpath1, CDFpath2)
char *CDFpath1;
char *CDFpath2;
{
  CDFstatus status;

  /****************************************************************************
  * Open CDFs and initialize for comparison.
  ****************************************************************************/

  sprintf (oText, "Comparing \"%s\" with \"%s\"", CDFpath1, CDFpath2);
  OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 0);

  diffFound = FALSE;

  status = CDFlib (OPEN_, CDF_, CDFpath1, &id1,
		   SELECT_, CDF_READONLY_MODE_, READONLYon,
			    CDF_zMODE_, zMode_1,
			    CDF_NEGtoPOSfp0_MODE_, BOO(negToPosFp0,
						       NEGtoPOSfp0on,
						       NEGtoPOSfp0off),
			    CDF_CACHESIZE_, cacheSize,
		   NULL_);
  if (!StatusHandler("CDF1",status)) return FALSE;
  
  status = CDFlib (GET_, rVARs_NUMDIMS_, &rNumDims1,
			 rVARs_DIMSIZES_, rDimSizes1,
			 CDF_ENCODING_, &encoding1,
			 CDF_MAJORITY_, &majority1,
			 CDF_FORMAT_, &format1,
			 CDF_COPYRIGHT_, copyright1,
			 CDF_NUMrVARS_, &rNumVars1,
			 CDF_NUMzVARS_, &zNumVars1,
			 CDF_NUMATTRS_, &numAttrs1,
			 rVARs_MAXREC_, &CDFmaxRec1,
			 CDF_VERSION_, &version1,
			 CDF_RELEASE_, &release1,
			 CDF_INCREMENT_, &increment1,
		   NULL_);
  if (!StatusHandler("CDF1",status)) return FALSE;

  status = CDFlib (OPEN_, CDF_, CDFpath2, &id2,
		   SELECT_, CDF_READONLY_MODE_, READONLYon,
			    CDF_zMODE_, zMode_2,
			    CDF_NEGtoPOSfp0_MODE_,BOO(negToPosFp0,
						      NEGtoPOSfp0on,
						      NEGtoPOSfp0off),
			    CDF_CACHESIZE_, cacheSize,
		   NULL_);
  if (!StatusHandler("CDF2",status)) return FALSE;
  
  status = CDFlib (GET_, rVARs_NUMDIMS_, &rNumDims2,
			 rVARs_DIMSIZES_, rDimSizes2,
			 CDF_ENCODING_, &encoding2,
			 CDF_MAJORITY_, &majority2,
			 CDF_FORMAT_, &format2,
			 CDF_COPYRIGHT_, copyright2,
			 CDF_NUMrVARS_, &rNumVars2,
			 CDF_NUMzVARS_, &zNumVars2,
			 CDF_NUMATTRS_, &numAttrs2,
			 rVARs_MAXREC_, &CDFmaxRec2,
			 CDF_VERSION_, &version2,
			 CDF_RELEASE_, &release2,
			 CDF_INCREMENT_, &increment2, 
		   NULL_);
  if (!StatusHandler("CDF2",status)) return FALSE;

  if (!AttrNumberMatches()) return FALSE;

  if (!VarNumberMatches(FALSE)) return FALSE;
  if (!VarNumberMatches(TRUE)) return FALSE;

  CHECKforABORTso

  /****************************************************************************
  * Compare CDFs.
  ****************************************************************************/

  if (!CompareGeneral()) return FALSE;
  CHECKforABORTso

  if (cmpAttrs) {
    if (!CompareAttributes()) return FALSE;
  }

  if (cmpVars) {
    if (!CompareZvsR()) return FALSE;
    if (!CompareVariables(FALSE)) return FALSE;
    if (!CompareVariables(TRUE)) return FALSE;
  }

  /****************************************************************************
  * Close CDFs.
  ****************************************************************************/

  if (dumpStatistics) {
    vSTATS vStats;
    status = CDFlib (SELECT_, CDF_, id1,
		     CLOSE_, CDFwithSTATS_, &vStats,
		     NULL_);
    if (!StatusHandler("CDF1",status)) return FALSE;
    DisplayStatistics ("CDF1", &vStats);
    status = CDFlib (SELECT_, CDF_, id2,
		     CLOSE_, CDFwithSTATS_, &vStats,
		     NULL_);
    if (!StatusHandler("CDF2",status)) return FALSE;
    DisplayStatistics ("CDF2", &vStats);
  }
  else {
    status = CDFlib (SELECT_, CDF_, id1,
		     CLOSE_, CDF_,
		     NULL_);
    if (!StatusHandler("CDF1",status)) return FALSE;
    status = CDFlib (SELECT_, CDF_, id2,
		     CLOSE_, CDF_,
		     NULL_);
    if (!StatusHandler("CDF2",status)) return FALSE;
  }

  /****************************************************************************
  * If no differences, display message and return.
  ****************************************************************************/

  if ( (!diffFound) && mLog) OutputWithMargin (stdout,
					       "No differences detected.",
					       MAX_SCREENLINE_LEN, 2);
  return TRUE;    /* Successful comparison. */
}

/******************************************************************************
* CompareGeneral.
******************************************************************************/

Logical CompareGeneral ()
{
  int i, j;

  if (rNumDims1 != rNumDims2) {
    diffFound = TRUE;
    sprintf (oText, "DIFFERENT number of rVariable dimensions (%ld vs. %ld)",
	     rNumDims1, rNumDims2);
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
    cmpVars = FALSE;
  }
  else {
    if (rNumDims1 > 0)
      for (i = 0; i < rNumDims1; i++)
	 if (rDimSizes1[i] != rDimSizes2[i]) {
	   diffFound = TRUE;
	   sprintf (oText, "DIFFERENT rVariable dimension sizes (");
	   for (j = 0; j < rNumDims1; j++) {
	      sprintf (EofS(oText), "%ld ", rDimSizes1[j]);
	   }
	   strcatX (oText, "vs.", MAX_oTEXT_LEN);
	   for (j = 0; j < rNumDims2; j++) {
	      sprintf (EofS(oText), " %ld", rDimSizes2[j]);
	   }
	   strcatX (oText, ")", MAX_oTEXT_LEN);
	   OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
	   cmpVars = FALSE;
	   break;
	 }
  }

  if (cmpEtc && encoding1 != encoding2) {
    diffFound = TRUE;
    sprintf (oText, "DIFFERENT encodings (%s vs. %s)",
	     EncodingToken(encoding1), EncodingToken(encoding2));
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
  }

  if (majority1 != majority2) {
    diffFound = TRUE;
    sprintf (oText, "DIFFERENT majorities (%s vs. %s)",
	     MajorityToken(majority1), MajorityToken(majority2));
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
  }

  if (cmpEtc && format1 != format2) {
    diffFound = TRUE;
    sprintf (oText, "DIFFERENT formats (%s vs. %s)",
	     FormatToken(format1), FormatToken(format2));
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
  }

  if (rNumVars1 != rNumVars2) {
    diffFound = TRUE;
    sprintf (oText, "DIFFERENT number of rVariables (%ld vs. %ld)",
	     rNumVars1, rNumVars2);
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
  }

  if (zNumVars1 != zNumVars2) {
    diffFound = TRUE;
    sprintf (oText, "DIFFERENT number of zVariables (%ld vs. %ld)",
	     zNumVars1, zNumVars2);
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
  }

  if (numAttrs1 != numAttrs2) {
    diffFound = TRUE;
    sprintf (oText, "DIFFERENT number of attributes (%ld vs. %ld)",
	     numAttrs1, numAttrs2);
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
  }

  if (CDFmaxRec1 != CDFmaxRec2) {
    diffFound = TRUE;
    sprintf (oText, "DIFFERENT maximum rVariable record (%ld vs. %ld)",
	     CDFmaxRec1 + 1, CDFmaxRec2 + 1);
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
  }

  if (cmpEtc && (version1 != version2 ||
		 release1 != release2 || increment1 != increment2)) {
    diffFound = TRUE;
    strcpyX (oText, "DIFFERENT creation libraries", MAX_oTEXT_LEN);
    sprintf (EofS(oText), " (V%ld.%ld.%ld vs. V%ld.%ld.%ld)",
	     version1, release1, increment1, version2, release2, increment2);
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
  }

  return TRUE;
}

/******************************************************************************
* AttrNumberMatches.  Determine attribute number matches.
******************************************************************************/

Logical AttrNumberMatches () {
  CDFstatus status;
  long attrN1, attrN2;
  char attrName[CDF_ATTR_NAME_LEN+1];

  if (numAttrs1 > 0)
    attrNumMatches1 = (long *) AllocateMemory ((size_t) (numAttrs1 *
							 sizeof(long)),
					       FatalError);
  else
    attrNumMatches1 = (long *) NULL;

  for (attrN1 = 0; attrN1 < numAttrs1; attrN1++) {
     status = CDFlib (SELECT_, CDF_, id1,
			       ATTR_, attrN1,
		      GET_, ATTR_NAME_, attrName,
		      NULL_);
     if (!StatusHandler("CDF1",status)) return FALSE;

     status = CDFlib (SELECT_, CDF_, id2,
		      GET_, ATTR_NUMBER_, attrName, &attrN2,
		      NULL_);
     if (status == NO_SUCH_ATTR)
       attrNumMatches1[(int)attrN1] = -1;
     else {
       if (!StatusHandler("CDF2",status)) return FALSE;
       attrNumMatches1[(int)attrN1] = attrN2;
     }
  }

  if (numAttrs2 > 0)
    attrNumMatches2 = (long *) AllocateMemory ((size_t) (numAttrs2 *
							 sizeof(long)),
					       FatalError);
  else
    attrNumMatches2 = (long *) NULL;

  for (attrN2 = 0; attrN2 < numAttrs2; attrN2++) {
     status = CDFlib (SELECT_, CDF_, id2,
			       ATTR_, attrN2,
		      GET_, ATTR_NAME_, attrName,
		      NULL_);
     if (!StatusHandler("CDF2",status)) return FALSE;

     status = CDFlib (SELECT_, CDF_, id1,
		      GET_, ATTR_NUMBER_, attrName, &attrN1,
		      NULL_);
     if (status == NO_SUCH_ATTR)
       attrNumMatches2[(int)attrN2] = -1;
     else {
       if (!StatusHandler("CDF1",status)) return FALSE;
       attrNumMatches2[(int)attrN2] = attrN1;
     }
  }

  return TRUE;
}

/******************************************************************************
* VarNumberMatches.  Determine variable number matches.
******************************************************************************/

Logical VarNumberMatches (Z)
Logical Z;
{
  CDFstatus status;
  long varN1, varN2;
  char varName[CDF_VAR_NAME_LEN+1];
  /****************************************************************************
  * Allocate matching number array for CDF1.
  ****************************************************************************/
  if (Zq(Z,zNumVars1,rNumVars1) > 0) {
    if (Z)
      zVarNumMatches1 = (long *) AllocateMemory ((size_t) (zNumVars1 *
							   sizeof(long)),
						 FatalError);
    else
      rVarNumMatches1 = (long *) AllocateMemory ((size_t) (rNumVars1 *
							  sizeof(long)),
						FatalError);
  }
  else
    if (Z)
      zVarNumMatches1 = NULL;
    else
      rVarNumMatches1 = NULL;
  /****************************************************************************
  * Find matching variable numbers for CDF1.
  ****************************************************************************/
  for (varN1 = 0; varN1 < Zq(Z,zNumVars1,rNumVars1); varN1++) {
     if (Zq(Z,zNumVars2,rNumVars2) > 0) {
       status = CDFlib (SELECT_, CDF_, id1,
				 Zq(Z,zVAR_,rVAR_), varN1,
			GET_, Zq(Z,zVAR_NAME_,rVAR_NAME_), varName,
			NULL_);
       if (!StatusHandler("CDF1",status)) return FALSE;
       status = CDFlib (SELECT_, CDF_, id2,
			GET_, Zq(Z,zVAR_NUMBER_,rVAR_NUMBER_), varName, &varN2,
			NULL_);
       if (status == NO_SUCH_VAR)
	 if (Z)
	   zVarNumMatches1[(int)varN1] = -1;
	 else
	   rVarNumMatches1[(int)varN1] = -1;
       else {
	 if (!StatusHandler("CDF2",status)) return FALSE;
	 if (Z)
	   zVarNumMatches1[(int)varN1] = varN2;
	 else
	   rVarNumMatches1[(int)varN1] = varN2;
       }
     }
     else
       if (Z)
	 zVarNumMatches1[(int)varN1] = -1;
       else
	 rVarNumMatches1[(int)varN1] = -1;
  }
  /****************************************************************************
  * Allocate matching number array for CDF2.
  ****************************************************************************/
  if (Zq(Z,zNumVars2,rNumVars2) > 0) {
    if (Z)
      zVarNumMatches2 = (long *) AllocateMemory ((size_t) (zNumVars2 *
							   sizeof(long)),
						 FatalError);
    else
      rVarNumMatches2 = (long *) AllocateMemory ((size_t) (rNumVars2 *
							  sizeof(long)),
						FatalError);
  }
  else
    if (Z)
      zVarNumMatches2 = NULL;
    else
      rVarNumMatches2 = NULL;
  /****************************************************************************
  * Find matching variable numbers for CDF2.
  ****************************************************************************/
  for (varN2 = 0; varN2 < Zq(Z,zNumVars2,rNumVars2); varN2++) {
     if (Zq(Z,zNumVars1,rNumVars1) > 0) {
       status = CDFlib (SELECT_, CDF_, id2,
				 Zq(Z,zVAR_,rVAR_), varN2,
			GET_, Zq(Z,zVAR_NAME_,rVAR_NAME_), varName,
			NULL_);
       if (!StatusHandler("CDF2",status)) return FALSE;
       status = CDFlib (SELECT_, CDF_, id1,
			GET_, Zq(Z,zVAR_NUMBER_,rVAR_NUMBER_), varName, &varN1,
			NULL_);
       if (status == NO_SUCH_VAR)
	 if (Z)
	   zVarNumMatches2[(int)varN2] = -1;
	 else
	   rVarNumMatches2[(int)varN2] = -1;
       else {
	 if (!StatusHandler("CDF1",status)) return FALSE;
	 if (Z)
	   zVarNumMatches2[(int)varN2] = varN1;
	 else
	   rVarNumMatches2[(int)varN2] = varN1;
       }
     }
     else
       if (Z)
	 zVarNumMatches2[(int)varN2] = -1;
       else
	 rVarNumMatches2[(int)varN2] = -1;
  }
  return TRUE;
}

/******************************************************************************
* CompareAttributes.
******************************************************************************/

Logical CompareAttributes ()
{
  CDFstatus status;
  long attrN1, attrN2;
  char attrName[CDF_ATTR_NAME_LEN+1];
  Logical cmpEntries;

  for (attrN1 = 0; attrN1 < numAttrs1; attrN1++) {
     if (attrNumMatches1[(int)attrN1] == -1) {
       status = CDFlib (SELECT_, CDF_, id1,
				 ATTR_, attrN1,
			GET_, ATTR_NAME_, attrName,
			NULL_);
       if (!StatusHandler("CDF1",status)) return FALSE;
       diffFound = TRUE;
       sprintf (oText, "Attribute \"%s\" does not exist in CDF2", attrName);
       OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 2);
     }
     else {
       status = CDFlib (SELECT_, CDF_, id1,
				 ATTR_, attrN1,
			GET_, ATTR_NAME_, attrName,
			      ATTR_SCOPE_, &attrScope1,
			NULL_);
       if (!StatusHandler("CDF1",status)) return FALSE;

       if (attrScope1 == GLOBAL_SCOPE) {
	 attrMAXzEntry1 = -1;
	 attrNUMzEntries1 = 0;
	 status = CDFlib (SELECT_, CDF_, id1,
			  GET_, ATTR_MAXgENTRY_, &attrMAXgrEntry1,
				ATTR_NUMgENTRIES_, &attrNUMgrEntries1,
			  NULL_);
       }
       else
	 status = CDFlib (SELECT_, CDF_, id1,
			  GET_, ATTR_MAXrENTRY_, &attrMAXgrEntry1,
				ATTR_NUMrENTRIES_, &attrNUMgrEntries1,
				ATTR_MAXzENTRY_, &attrMAXzEntry1,
				ATTR_NUMzENTRIES_, &attrNUMzEntries1,
			  NULL_);
       if (!StatusHandler("CDF1",status)) return FALSE;

       status = CDFlib (SELECT_, CDF_, id2,
				 ATTR_, attrNumMatches1[(int)attrN1],
			GET_, ATTR_SCOPE_, &attrScope2,
			NULL_);
       if (!StatusHandler("CDF2",status)) return FALSE;

       if (attrScope2 == GLOBAL_SCOPE) {
	 attrMAXzEntry2 = -1;
	 attrNUMzEntries2 = 0;
	 status = CDFlib (SELECT_, CDF_, id2,
			  GET_, ATTR_MAXgENTRY_, &attrMAXgrEntry2,
				ATTR_NUMgENTRIES_, &attrNUMgrEntries2,
			  NULL_);
       }
       else
	 status = CDFlib (SELECT_, CDF_, id2,
			  GET_, ATTR_MAXrENTRY_, &attrMAXgrEntry2,
				ATTR_NUMrENTRIES_, &attrNUMgrEntries2,
				ATTR_MAXzENTRY_, &attrMAXzEntry2,
				ATTR_NUMzENTRIES_, &attrNUMzEntries2,
			  NULL_);
       if (!StatusHandler("CDF2",status)) return FALSE;

       if (mLog) {
	 sprintf (oText, "Comparing attribute \"%s\"", attrName);
	 OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 2);
       }

       cmpEntries = TRUE;

       if (cmpNumbers && attrNumMatches1[(int)attrN1] != attrN1) {
	 diffFound = TRUE;
	 strcpyX (oText, "DIFFERENT numbers for attribute", MAX_oTEXT_LEN);
	 sprintf (EofS(oText), " \"%s\" (%ld vs. %ld)",
		  attrName, attrN1 + 1, attrNumMatches1[(int)attrN1] + 1);
	 OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
       }

       if (attrScope1 != attrScope2) {
	 diffFound = TRUE;
	 sprintf (oText, "DIFFERENT scopes for attribute \"%s\" (%s vs. %s)",
		  attrName, ScopeToken(attrScope1), ScopeToken(attrScope2));
	 OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
	 /* MAY STILL BE ABLE TO COMPARE ENTRIES. */
       }

       if (attrMAXgrEntry1 != attrMAXgrEntry2) {
	 diffFound = TRUE;
	 strcpyX (oText, "DIFFERENT maximum entry for attribute",
		  MAX_oTEXT_LEN);
	 sprintf (EofS(oText), " \"%s\" (%ld vs. %ld)",
		  attrName, attrMAXgrEntry1 + 1, attrMAXgrEntry2 + 1);
	 OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
       }

       if (attrNUMgrEntries1 != attrNUMgrEntries2) {
	 diffFound = TRUE;
	 strcpyX (oText, "DIFFERENT number of entries for attribute",
		  MAX_oTEXT_LEN);
	 sprintf (EofS(oText), " \"%s\" (%ld vs. %ld)",
		  attrName, attrNUMgrEntries1, attrNUMgrEntries2);
	 OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
       }

       if (attrMAXzEntry1 != attrMAXzEntry2) {
	 diffFound = TRUE;
	 strcpyX (oText, "DIFFERENT maximum zEntry for attribute",
		  MAX_oTEXT_LEN);
	 sprintf (EofS(oText), " \"%s\" (%ld vs. %ld)",
		  attrName, attrMAXzEntry1 + 1, attrMAXzEntry2 + 1);
	 OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
       }

       if (attrNUMzEntries1 != attrNUMzEntries2) {
	 diffFound = TRUE;
	 strcpyX (oText, "DIFFERENT number of zEntries for attribute",
		  MAX_oTEXT_LEN);
	 sprintf (EofS(oText), " \"%s\" (%ld vs. %ld)",
		  attrName, attrNUMzEntries1, attrNUMzEntries2);
	 OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
       }

       if (cmpEntries) CompareAttributeEntries (attrN1,
						attrNumMatches1[(int)attrN1],
						attrName);
     }
     CHECKforABORTso
  }

  for (attrN2 = 0; attrN2 < numAttrs2; attrN2++) {
     if (attrNumMatches2[(int)attrN2] == -1) { 
       status = CDFlib (SELECT_, CDF_, id2,
				 ATTR_, attrN2,
			GET_, ATTR_NAME_, attrName,
			NULL_);
       if (!StatusHandler("CDF2",status)) return FALSE;
       diffFound = TRUE;
       sprintf (oText, "Attribute \"%s\" does not exist in CDF1", attrName);
       OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 2);
     }
  }

  return TRUE;
}

/******************************************************************************
* CompareAttributeEntries.
******************************************************************************/

Logical CompareAttributeEntries (attrN1, attrN2, attrName)
long attrN1;
long attrN2;
char *attrName;
{
  CDFstatus status;
  long entryN;

  if (mLog) OutputWithMargin (stdout, "Comparing entries...",
			      MAX_SCREENLINE_LEN, 4);

  status = CDFlib (SELECT_, CDF_, id1,
			    ATTR_, attrN1,
		   NULL_);
  if (!StatusHandler("CDF1",status)) return FALSE;

  status = CDFlib (SELECT_, CDF_, id2,
			    ATTR_, attrN2,
		   NULL_);
  if (!StatusHandler("CDF2",status)) return FALSE;

  if (attrScope1 == GLOBAL_SCOPE) {
    for (entryN = 0;
	 entryN <= MAXIMUM(attrMAXgrEntry1,attrMAXgrEntry2);
	 entryN++) CompareEntry (attrName, entryN, entryN, NULL, gENTRYt);
  }
  else {
    CompareVscopeEntries (attrName, FALSE);
    CompareVscopeEntries (attrName, TRUE);
  }

  return TRUE;
}

/******************************************************************************
* CompareVscopeEntries.
******************************************************************************/

Logical CompareVscopeEntries (attrName, Z)
char *attrName;
Logical Z;
{
  CDFstatus status;
  long entryN1;
  long entryN2;
  char varName[CDF_VAR_NAME_LEN+1];
  char *v = Zq(Z,zVariable,rVariable);
  char *e = Zq(Z,zEntry,rEntry);

  /****************************************************************************
  * Check each entry in CDF1 up to the maximum entry for CDF1.
  ****************************************************************************/
  for (entryN1 = 0; entryN1 <= Zq(Z,attrMAXzEntry1,attrMAXgrEntry1); entryN1++) {
     if (entryN1 < Zq(Z,zNumVars1,rNumVars1)) {
       if (Zq(Z,zVarNumMatches1[(int)entryN1],
		rVarNumMatches1[(int)entryN1]) == -1) {
	 diffFound = TRUE;
	 sprintf (oText, "No corresponding %s in CDF2 for %s number", v, e);
	 sprintf (EofS(oText), " %ld of attribute \"%s\" in CDF1",
		  entryN1 + 1, attrName);
	 OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
       }
       else {
	 status = CDFlib (SELECT_, CDF_, id1,
				   Zq(Z,zVAR_,rVAR_), entryN1,
			  GET_, Zq(Z,zVAR_NAME_,rVAR_NAME_), varName,
			  NULL_);
	 if (!StatusHandler("CDF1",status)) return FALSE;
	 CompareEntry (attrName, entryN1, Zq(Z,zVarNumMatches1[(int)entryN1],
					       rVarNumMatches1[(int)entryN1]),
		       varName, Zq(Z,zENTRYt,rENTRYt));
       }
     }
     else {
       CompareEntry (attrName, entryN1, entryN1, NULL, Zq(Z,zENTRYt,rENTRYt));
     }
  }

  for (entryN2 = 0; entryN2 <= Zq(Z,attrMAXzEntry2,attrMAXgrEntry2); entryN2++)
     if (entryN2 < Zq(Z,zNumVars2,rNumVars2)) {
       if (Zq(Z,zVarNumMatches2[(int)entryN2],
		rVarNumMatches2[(int)entryN2]) == -1) {
	 diffFound = TRUE;
	 sprintf (oText, "No corresponding %s in CDF1 for %s number", v, e);
	 sprintf (EofS(oText), " %ld of attribute \"%s\" in CDF2",
		  entryN2 + 1, attrName);
	 OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
       }
     }
     else
       CompareEntry (attrName, entryN2, entryN2, NULL, Zq(Z,zENTRYt,rENTRYt));

  return TRUE;
}

/******************************************************************************
* CompareEntry.    It is assumed that the attribute has already been selected
*                  in the two CDFs.
******************************************************************************/

Logical CompareEntry (attrName, entryN1, entryN2, varName, entryType)
char *attrName;
long entryN1;
long entryN2;
char *varName;          /* If != NULL, then a variable name associated with
			   the entries. */
int entryType;
{
  CDFstatus status, status1, status2;
  long entryDataType1, entryDataType2;
  long entryNumElems1, entryNumElems2;
  void *entryValue1, *entryValue2;
  char varNameField[VAR_NAME_FIELD_LEN];
  char entryNumField[ENTRY_NUM_FIELD_LEN];
  long Nbytes;
  char *v = E3(entryType,NULL,rVariable,zVariable);
  char *e = E3(entryType,gEntry,rEntry,zEntry);
  Logical badStatus1 = FALSE, badStatus2 = FALSE, noSuch = FALSE;

  if (varName == NULL) {
    varNameField[0] = '\0';
    sprintf (entryNumField, "%ld", entryN1 + 1);
  }
  else {
    strcpyX (varNameField, " (", VAR_NAME_FIELD_LEN);
    strcatX (varNameField, v, VAR_NAME_FIELD_LEN);
    strcatX (varNameField, " \"", VAR_NAME_FIELD_LEN);
    strcatX (varNameField, varName, VAR_NAME_FIELD_LEN);
    strcatX (varNameField, "\")", VAR_NAME_FIELD_LEN);
    if (entryN1 == entryN2)
      sprintf (entryNumField, "%ld", entryN1 + 1);
    else
      sprintf (entryNumField, "%ld/%ld", entryN1 + 1, entryN2 + 1);
  }

  status1 = CDFlib (SELECT_, CDF_, id1,
		    CONFIRM_, E3(entryType,gENTRY_EXISTENCE_,
					   rENTRY_EXISTENCE_,
					   zENTRY_EXISTENCE_), entryN1,
		    SELECT_, E3(entryType,gENTRY_,rENTRY_,zENTRY_), entryN1,
		    GET_, E3(entryType,gENTRY_DATATYPE_,
				       rENTRY_DATATYPE_,
				       zENTRY_DATATYPE_), &entryDataType1,
			  E3(entryType,gENTRY_NUMELEMS_,
				       rENTRY_NUMELEMS_,
				       zENTRY_NUMELEMS_), &entryNumElems1,
		    NULL_);

  status2 = CDFlib (SELECT_, CDF_, id2,
		    CONFIRM_, E3(entryType,gENTRY_EXISTENCE_,
					   rENTRY_EXISTENCE_,
					   zENTRY_EXISTENCE_), entryN2,
		    SELECT_, E3(entryType,gENTRY_,rENTRY_,zENTRY_), entryN2,
		    GET_, E3(entryType,gENTRY_DATATYPE_,
				       rENTRY_DATATYPE_,
				       zENTRY_DATATYPE_), &entryDataType2,
			  E3(entryType,gENTRY_NUMELEMS_,
				       rENTRY_NUMELEMS_,
				       zENTRY_NUMELEMS_), &entryNumElems2,
		    NULL_);

  if (status1 == NO_SUCH_ENTRY && status2 == NO_SUCH_ENTRY) return TRUE;

  if (status1 == NO_SUCH_ENTRY) {
    diffFound = TRUE;
    sprintf (oText, "No %s number %ld%s for attribute \"%s\" in CDF1",
	     e, entryN1 + 1, varNameField, attrName);
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
    noSuch = TRUE;
  }
  else
    badStatus1 = !StatusHandler("CDF1",status1);

  if (status2 == NO_SUCH_ENTRY) {
    diffFound = TRUE;
    sprintf (oText, "No %s number %ld%s for attribute \"%s\" in CDF2",
	     e, entryN2 + 1, varNameField, attrName);
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
    noSuch = TRUE;
  }
  else
    badStatus2 = !StatusHandler("CDF2",status2);

  if (noSuch) return TRUE;
  if (badStatus1 || badStatus2) return FALSE;

  if (entryDataType1 != entryDataType2) {
    diffFound = TRUE;
    strcpyX (oText, "DIFFERENT data types for", MAX_oTEXT_LEN);
    sprintf (EofS(oText), " %s number %s%s of attribute \"%s\" ",
	     e, entryNumField, varNameField, attrName);
    sprintf (EofS(oText), "(CDF_%s vs. CDF_%s)",
	     DataTypeToken(entryDataType1), DataTypeToken(entryDataType2));
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
    /* DON'T RETURN IF THIS DIFFERENCE FOUND - COMPARE STILL POSSIBLE. */
  }

  if (!EquivalentDataTypes(entryDataType1,entryDataType2)) {
    diffFound = TRUE;
    strcpyX (oText, "NON-EQUIVALENT data types for", MAX_oTEXT_LEN);
    sprintf (EofS(oText), " %s number %s%s of attribute \"%s\" ",
	     e, entryNumField, varNameField, attrName);
    sprintf (EofS(oText), "(CDF_%s vs. CDF_%s)",
	     DataTypeToken(entryDataType1), DataTypeToken(entryDataType2));
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
    return TRUE;
  }

  if (entryNumElems1 != entryNumElems2) {
    diffFound = TRUE;
    sprintf (oText, "DIFFERENT number of elements for %s number %s",
	     e, entryNumField);
    sprintf (EofS(oText), "%s of attribute \"%s\" (%ld vs. %ld)",
	     varNameField, attrName, entryNumElems1, entryNumElems2);
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
    return TRUE;
  }

  Nbytes = entryNumElems1 * CDFelemSize(entryDataType1);

  entryValue1 = AllocateMemory ((size_t) Nbytes, FatalError);
  entryValue2 = AllocateMemory ((size_t) Nbytes, FatalError);

  status = CDFlib (SELECT_, CDF_, id1,
		   GET_, E3(entryType,gENTRY_DATA_,
				      rENTRY_DATA_,
				      zENTRY_DATA_), entryValue1,
		   NULL_);
  if (!StatusHandler("CDF1",status)) return FALSE;

  status = CDFlib (SELECT_, CDF_, id2,
		   GET_, E3(entryType,gENTRY_DATA_,
				      rENTRY_DATA_,
				      zENTRY_DATA_), entryValue2,
		   NULL_);
  if (!StatusHandler("CDF2",status)) return FALSE;

  if (memcmp(entryValue1,entryValue2,(size_t)Nbytes) != 0) {
    diffFound = TRUE;
    sprintf (oText,
	     "DIFFERENT value(s) for %s number %s%s of attribute \"%s\"",
	     e, entryNumField, varNameField, attrName);
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
    if (displayValue) {
      WriteOut (stdout, "      CDF1 value(s)> ");
      WriteEntryValue (stdout, entryDataType1, entryNumElems1, entryValue1,
		       21, MAX_SCREENLINE_LEN-1);
      WriteOut (stdout, "\n");
      WriteOut (stdout, "      CDF2 value(s)> ");
      WriteEntryValue (stdout, entryDataType2, entryNumElems2, entryValue2,
		       21, MAX_SCREENLINE_LEN-1);
      WriteOut (stdout, "\n");
    }
  }

  FreeMemory (entryValue1, FatalError);
  FreeMemory (entryValue2, FatalError);

  CHECKforABORTso
  return TRUE;
}

/******************************************************************************
* CompareZvsR.
******************************************************************************/

Logical CompareZvsR () {
  CDFstatus status;
  long varN1, varN2;
  char varName1[CDF_VAR_NAME_LEN+1];
  /****************************************************************************
  * If there are rVariables in CDF1 and zVariables in CDF2, see if any of the
  * rVariables in CDF1 have the same name as a zVariable in CDF2.
  ****************************************************************************/
  if (rNumVars1 > 0 && zNumVars2 > 0) {
    for (varN1 = 0; varN1 < rNumVars1; varN1++) {
       status = CDFlib (SELECT_, CDF_, id1,
				 rVAR_, varN1,
			GET_, rVAR_NAME_, varName1,
			NULL_);
       if (!StatusHandler("CDF1",status)) return FALSE;
       status = CDFlib (SELECT_, CDF_, id2,
			GET_, zVAR_NUMBER_, varName1, &varN2,
			NULL_);
       switch (status) {
	 case CDF_OK:
	   sprintf (oText, "zVariable \"%s\" in CDF2 is an rVariable in CDF1",
		    varName1);
	   OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 2);
	   break;
	 case NO_SUCH_VAR:
	   break;
	 default:
	   if (!StatusHandler("CDF2",status)) return FALSE;
	   break;
       }
       CHECKforABORTso
    }
  }
  /****************************************************************************
  * If there are zVariables in CDF1 and rVariables in CDF2, see if any of the
  * zVariables in CDF1 have the same name as an rVariable in CDF2.
  ****************************************************************************/
  if (zNumVars1 > 0 && rNumVars2 > 0) {
    for (varN1 = 0; varN1 < zNumVars1; varN1++) {
       status = CDFlib (SELECT_, CDF_, id1,
				 zVAR_, varN1,
			GET_, zVAR_NAME_, varName1,
			NULL_);
       if (!(StatusHandler("CDF1",status))) return FALSE;
       status = CDFlib (SELECT_, CDF_, id2,
			GET_, rVAR_NUMBER_, varName1, &varN2,
			NULL_);
       switch (status) {
	 case CDF_OK:
	   sprintf (oText, "zVariable \"%s\" in CDF1 is an rVariable in CDF2",
		    varName1);
	   OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 2);
	   break;
	 case NO_SUCH_VAR:
	   break;
	 default:
	   if (!StatusHandler("CDF2",status)) return FALSE;
	   break;
       }
       CHECKforABORTso
    }
  }
  return TRUE;
}

/******************************************************************************
* CompareVariables.
******************************************************************************/

Logical CompareVariables (Z)
Logical Z;
{
  CDFstatus status;
  long varN1, varN2;
  char varName[CDF_VAR_NAME_LEN + 1];
  Logical cmpVarValues;
  int i, j;
  char *v = Zq(Z,zVariable,rVariable);

  for (varN1 = 0; varN1 < Zq(Z,zNumVars1,rNumVars1); varN1++) {
     if (Zq(Z,zVarNumMatches1[(int)varN1],rVarNumMatches1[(int)varN1]) == -1) {
       status = CDFlib (SELECT_, CDF_, id1,
				 Zq(Z,zVAR_,rVAR_), varN1,
			GET_, Zq(Z,zVAR_NAME_,rVAR_NAME_), varName,
			NULL_);
       if (!StatusHandler("CDF1",status)) return FALSE;
       diffFound = TRUE;
       sprintf (oText, "%s \"%s\" does not exist in CDF2", v, varName);
       OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 2);
     }
     else {
       status = CDFlib (SELECT_, CDF_, id1,
				 Zq(Z,zVAR_,rVAR_), varN1,
			GET_, Zq(Z,zVAR_NAME_,rVAR_NAME_), varName,
			      Zq(Z,zVAR_DATATYPE_,
				   rVAR_DATATYPE_), &varDataType1,
			      Zq(Z,zVAR_NUMELEMS_,
				   rVAR_NUMELEMS_), &varNumElems1,
			      Zq(Z,zVAR_RECVARY_,rVAR_RECVARY_), &varRecVary1,
			      Zq(Z,zVAR_DIMVARYS_,
				   rVAR_DIMVARYS_), varDimVarys1,
			      Zq(Z,zVAR_MAXREC_,rVAR_MAXREC_), &varMaxRec1,
			      Zq(Z,zVAR_EXTENDRECS_,
				   rVAR_EXTENDRECS_), &varExtendRecs1,
			NULL_);
       if (!StatusHandler("CDF1",status)) return FALSE;

       if (Z) {
	 status = CDFlib (SELECT_, CDF_, id1,
			  GET_, zVAR_NUMDIMS_, &zNumDims1,
				zVAR_DIMSIZES_, zDimSizes1,
			  NULL_);
	 if (!StatusHandler("CDF1",status)) return FALSE;
       }

       status = CDFlib (SELECT_, CDF_, id2,
				 Zq(Z,zVAR_,rVAR_),
					Zq(Z,zVarNumMatches1[(int)varN1],
					     rVarNumMatches1[(int)varN1]),
			GET_, Zq(Z,zVAR_DATATYPE_,
				   rVAR_DATATYPE_), &varDataType2,
			      Zq(Z,zVAR_NUMELEMS_,
				   rVAR_NUMELEMS_), &varNumElems2,
			      Zq(Z,zVAR_RECVARY_,rVAR_RECVARY_), &varRecVary2,
			      Zq(Z,zVAR_DIMVARYS_,rVAR_DIMVARYS_),varDimVarys2,
			      Zq(Z,zVAR_MAXREC_,rVAR_MAXREC_), &varMaxRec2,
			      Zq(Z,zVAR_EXTENDRECS_,
				   rVAR_EXTENDRECS_), &varExtendRecs2,
			NULL_);
       if (!StatusHandler("CDF2",status)) return FALSE;

       if (Z) {
	 status = CDFlib (SELECT_, CDF_, id2,
			  GET_, zVAR_NUMDIMS_, &zNumDims2,
				zVAR_DIMSIZES_, zDimSizes2,
			  NULL_);
	 if (!StatusHandler("CDF2",status)) return FALSE;
       }

       if (mLog) {
	 sprintf (oText, "Comparing %s \"%s\"", v, varName);
	 OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 2);
       }

       cmpVarValues = TRUE;

       if (cmpNumbers)
	 if (Zq(Z,zVarNumMatches1[(int)varN1],
		  rVarNumMatches1[(int)varN1]) != varN1) {
	   diffFound = TRUE;
	   sprintf (oText, "DIFFERENT numbers for %s \"%s\" (%ld vs. %ld)",
		    v, varName, varN1+1, Zq(Z,zVarNumMatches1[(int)varN1],
					      rVarNumMatches1[(int)varN1])+1);
	   OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
	 }

       if (varDataType1 != varDataType2) {
	 diffFound = TRUE;
	 strcpyX (oText, "DIFFERENT data types for", MAX_oTEXT_LEN);
	 sprintf (EofS(oText), " %s \"%s\" (CDF_%s vs. CDF_%s)",
		  v, varName, DataTypeToken(varDataType1),
		  DataTypeToken(varDataType2));
	 OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
	 /* STILL MIGHT BE ABLE TO COMPARE VARIABLES. */
       }

       if ( !EquivalentDataTypes(varDataType1,varDataType2)) {
	 diffFound = TRUE;
	 strcpyX (oText, "Non-equivalent data types for", MAX_oTEXT_LEN);
	 sprintf (EofS(oText), " %s \"%s\" (CDF_%s vs. CDF_%s)",
		  v, varName, DataTypeToken(varDataType1),
		  DataTypeToken(varDataType2));
	 OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
	 cmpVarValues = FALSE;
       }

       if (varNumElems1 != varNumElems2) {
	 diffFound = TRUE;
	 strcpyX (oText, "DIFFERENT number of elements for", MAX_oTEXT_LEN);
	 sprintf (EofS(oText), " %s \"%s\" (%ld vs. %ld)",
		  v, varName, varNumElems1, varNumElems2);
	 OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
	 cmpVarValues = FALSE;
       }

       if (Z) {
	 if (zNumDims1 != zNumDims2) {
	   diffFound = TRUE;
	   sprintf (oText, "DIFFERENT number of dimensions (%ld vs. %ld)",
		    zNumDims1, zNumDims2);
	   sprintf (EofS(oText), " for zVariable \"%s\"", varName);
	   OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
	   cmpVarValues = FALSE;
	 }
	 else {
	   if (zNumDims1 > 0) {
	     for (i = 0; i < zNumDims1; i++) {
		if (zDimSizes1[i] != zDimSizes2[i]) {
		  diffFound = TRUE;
		  strcpyX (oText,"DIFFERENT dimension sizes (",MAX_oTEXT_LEN);
		  for (j = 0; j < zNumDims1; j++) {
		     sprintf (EofS(oText), "%ld ", rDimSizes1[j]);
		  }
		  strcatX (oText, "vs.", MAX_oTEXT_LEN);
		  for (j = 0; j < zNumDims2; j++) {
		     sprintf (EofS(oText), " %ld", rDimSizes2[j]);
		  }
		  sprintf (EofS(oText), ") for zVariable \"%s\"", varName);
		  OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
		  cmpVarValues = FALSE;
		  break;
		}
	     }
	   }
	 }
       }

       if (varRecVary1 != varRecVary2) {
	 diffFound = TRUE;
	 strcpyX (oText, "DIFFERENT record variances for", MAX_oTEXT_LEN);
	 sprintf (EofS(oText), " %s \"%s\" (%s vs. %s)",
		  v, varName, TFvarianceToken(varRecVary1),
		  TFvarianceToken(varRecVary2));
	 OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
       }

       if (Z) {
	 if (zNumDims1 == zNumDims2)
	   for (i = 0; i < zNumDims1; i++)
	     if (varDimVarys1[i] != varDimVarys2[i]) {
	       diffFound = TRUE;
	       strcpyX (oText,"DIFFERENT dimension variances for zVariable \"",
			MAX_oTEXT_LEN);
	       strcatX (oText, varName, MAX_oTEXT_LEN);
	       strcatX (oText, "\" (", MAX_oTEXT_LEN);
	       for (j = 0; j < zNumDims1; j++)
		  sprintf (EofS(oText),"%s",TFvarianceToken(varDimVarys1[j]));
	       strcatX (oText, " vs. ", MAX_oTEXT_LEN);
	       for (j = 0; j < zNumDims2; j++)
		  sprintf (EofS(oText),"%s",TFvarianceToken(varDimVarys2[j]));
	       strcatX (oText, ")", MAX_oTEXT_LEN);
	       OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
	       break;
	     }
       }
       else {
	 for (i = 0; i < rNumDims1; i++)
	   if (varDimVarys1[i] != varDimVarys2[i]) {
	     diffFound = TRUE;
	     strcpyX (oText, "DIFFERENT dimension variances for rVariable \"",
		      MAX_oTEXT_LEN);
	     strcatX (oText, varName, MAX_oTEXT_LEN);
	     strcatX (oText, "\" (", MAX_oTEXT_LEN);
	     for (j = 0; j < rNumDims1; j++) {
		sprintf (EofS(oText), "%s", TFvarianceToken(varDimVarys1[j]));
	     }
	     strcatX (oText, " vs. ", MAX_oTEXT_LEN);
	     for (j = 0; j < rNumDims2; j++) {
		sprintf (EofS(oText), "%s", TFvarianceToken(varDimVarys2[j]));
	     }
	     strcatX (oText, ")", MAX_oTEXT_LEN);
	     OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
	     break;
	   }
       }

       if (varMaxRec1 != varMaxRec2) {
	 diffFound = TRUE;
	 sprintf (oText,"DIFFERENT maximum record for %s \"%s\" (%ld vs. %ld)",
		  v, varName, varMaxRec1 + 1, varMaxRec2 + 1);
	 OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
       }

       if (varExtendRecs1 != varExtendRecs2) {
	 diffFound = TRUE;
	 sprintf (oText,"DIFFERENT extend records for %s \"%s\" (%ld vs. %ld)",
		  v, varName, varExtendRecs1, varExtendRecs2);
	 OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
       }

       if (cmpVarValues) {
	 CompareVariableValues
		(varN1, Zq(Z,zVarNumMatches1[(int)varN1],
			     rVarNumMatches1[(int)varN1]), varName, Z);
       }
     }
     CHECKforABORTso
  }

  for (varN2 = 0; varN2 < Zq(Z,zNumVars2,rNumVars2); varN2++)
     if (Zq(Z,zVarNumMatches2[(int)varN2],rVarNumMatches2[(int)varN2]) == -1) {
       status = CDFlib (SELECT_, CDF_, id2,
				 Zq(Z,zVAR_,rVAR_), varN2,
			GET_, Zq(Z,zVAR_NAME_,rVAR_NAME_), varName,
			NULL_);
       if (!StatusHandler("CDF2",status)) return FALSE;
       diffFound = TRUE;
       sprintf (oText, "%s \"%s\" does not exist in CDF1", v, varName);
       OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 2);
     }

  return TRUE;
}

/******************************************************************************
* CompareVariableValues.
******************************************************************************/

Logical CompareVariableValues (varN1, varN2, varName, Z)
long varN1;
long varN2;
char *varName;
Logical Z;
{
  long numDims, dimSizes[CDF_MAX_DIMS], recVary, dimVarys[CDF_MAX_DIMS];
  long nHypers, hyperN, nValuesPerRec, nValuesPerDim[CDF_MAX_DIMS];
  char *v = Zq(Z,zVariable,rVariable);
  void *padValue1, *padValue2;
  CDFstatus status1, status2;
  long maxRec; size_t nValueBytes[2];
  Logical rowMajor1 = ROWmajor(majority1);
  long recNumber, dimIndices[CDF_MAX_DIMS], valueN, NvalueBytes;
  long valueDiffCount, recordDiffCount, lastRecordDiff, nValues;
  struct GroupStruct groups; struct HyperStruct hyper;
  Byte *value1, *value2, *buffer1, *buffer2, **handles[2];
  int dimN, dimNt;
   static char hyperMsg[] = {
     "      comparing values with hyper reads..."
   };
   static char hyperSwitchMsg[] = {
     "      comparing values with hyper reads/switching..."
   };
   static char hyperSingleMsg[] = {
     "      comparing values with hyper/single reads..."
   };
  /****************************************************************************
  * Select variables.
  ****************************************************************************/
  status1 = CDFlib (SELECT_, CDF_, id1,
			     Zq(Z,zVAR_,rVAR_), varN1,
		    NULL_);
  if (!StatusHandler("CDF1",status1)) return FALSE;
  status2 = CDFlib (SELECT_, CDF_, id2,
			     Zq(Z,zVAR_,rVAR_), varN2,
		    NULL_);
  if (!StatusHandler("CDF2",status2)) return FALSE;
  /****************************************************************************
  * Compare pad values.
  ****************************************************************************/
  NvalueBytes = varNumElems1 * CDFelemSize(varDataType1);
  padValue1 = AllocateMemory ((size_t) NvalueBytes, FatalError);
  padValue2 = AllocateMemory ((size_t) NvalueBytes, FatalError);
  status1 = CDFlib (SELECT_, CDF_, id1,
		    GET_, Zq(Z,zVAR_PADVALUE_,rVAR_PADVALUE_), padValue1,
		    NULL_);
  status2 = CDFlib (SELECT_, CDF_, id2,
		    GET_, Zq(Z,zVAR_PADVALUE_,rVAR_PADVALUE_), padValue2,
		    NULL_);
  if (status1 != NO_PADVALUE_SPECIFIED) {
    if (!StatusHandler("CDF1",status1)) return FALSE;
    if (status2 != NO_PADVALUE_SPECIFIED) {
      if (!StatusHandler("CDF2",status2)) return FALSE;
      if (memcmp(padValue1,padValue2,(size_t)NvalueBytes) != 0) {
	diffFound = TRUE;
	sprintf (oText, "DIFFERENT pad values for %s \"%s\"", v, varName);
	OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
	if (displayValue) {
	  WriteOut (stdout, "      CDF1 value> ");
	  EncodeValuesFormat (varDataType1, varNumElems1, padValue1, oText,
			      NULL, 0, MAX_SCREENLINE_LEN - 19, EPOCH0_STYLE);
	  WriteOut (stdout, oText);
	  WriteOut (stdout, "\n");
	  WriteOut (stdout, "      CDF2 value> ");
	  EncodeValuesFormat (varDataType2, varNumElems2, padValue2, oText,
			      NULL, 0, MAX_SCREENLINE_LEN - 19, EPOCH0_STYLE);
	  WriteOut (stdout, oText);
	  WriteOut (stdout, "\n");
	}
      }
    }
    else {
      diffFound = TRUE;
      sprintf (oText, "No pad value for %s \"%s\" in CDF2", v, varName);
      OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
    }
  }
  else {
    if (status2 != NO_PADVALUE_SPECIFIED) {
      if (!StatusHandler("CDF2",status2)) return FALSE;
      diffFound = TRUE;
      sprintf (oText, "No pad value for %s \"%s\" in CDF1", v, varName);
      OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 4);
    }
  }
  FreeMemory (padValue1, FatalError);
  FreeMemory (padValue2, FatalError);
  CHECKforABORTso
  /****************************************************************************
  * Hyper groups...
  ****************************************************************************/
  maxRec = MAXIMUM(varMaxRec1,varMaxRec2);
  recVary = (varRecVary1 || varRecVary2);
  numDims = Zq(Z,zNumDims1,rNumDims1);
  for (dimN = 0; dimN < numDims; dimN++) {
     dimVarys[dimN] = (varDimVarys1[dimN] || varDimVarys2[dimN]);
     if (dimVarys[dimN])
       dimSizes[dimN] = Zq(Z,zDimSizes1[dimN],rDimSizes1[dimN]);
     else
       dimSizes[dimN] = 1;
  }
  for (dimN = 0, nValuesPerRec = 1; dimN < numDims; dimN++) {
     if (dimVarys[dimN]) nValuesPerRec *= dimSizes[dimN];
  }
  for (dimN = 0; dimN < numDims; dimN++) {
     nValuesPerDim[dimN] = 1;
     if (rowMajor1) {
       for (dimNt = dimN + 1; dimNt < numDims; dimNt++) {
	  nValuesPerDim[dimN] *= dimSizes[dimNt];
       }
     }
     else {
       for (dimNt = dimN - 1; dimNt >= 0; dimNt--) {
	  nValuesPerDim[dimN] *= dimSizes[dimNt];
       }
     }
  }
  handles[0] = &buffer1;
  handles[1] = &buffer2;
  nValueBytes[0] = (size_t) NvalueBytes;
  nValueBytes[1] = (size_t) NvalueBytes;
  valueDiffCount = 0;
  recordDiffCount = 0;
  lastRecordDiff = -1;
  /****************************************************************************
  * Check for zero or one dimensions or the same majorities (if two or more
  * dimensions).
  ****************************************************************************/
  if (numDims < 2 || majority1 == majority2) {
    if (mLog) {
      WriteOut (stdout, hyperMsg);
      WriteOut (stdout, BOO(pctLog,"..0%","\n"));
    }
    AllocateBuffers (maxRec + 1, numDims, dimSizes, &groups, 0, 2, handles,
		     nValueBytes, rowMajor1, MINnHYPERS, FatalError);
    InitHyperParms (&hyper, &groups, numDims, &nHypers, &nValues);
    for (hyperN = 0; hyperN < nHypers; hyperN++) {
       status1 = HYPER_READ (id1, Z, hyper, buffer1);
       if (!StatusHandler("CDF1",status1)) return FALSE;
       if (pctLog) WriteOutPct (PCT(hyperN,nHypers,1,3));
       CHECKforABORTso
       status2 = HYPER_READ (id2, Z, hyper, buffer2);
       if (!StatusHandler("CDF2",status2)) return FALSE;
       if (pctLog) WriteOutPct (PCT(hyperN,nHypers,2,3));
       CHECKforABORTso
       if (memcmp(buffer1,buffer2,(size_t)(nValues*NvalueBytes))) {
	 for (valueN = 0, value1 = buffer1, value2 = buffer2;
	      valueN < nValues; valueN++,
	      value1 += (size_t)NvalueBytes, value2 += (size_t)NvalueBytes) {
	    if (memcmp(value1,value2,(size_t)NvalueBytes)) {
	      AtRecordIndices (&hyper, valueN, rowMajor1, numDims,
			       nValuesPerRec, nValuesPerDim, &recNumber,
			       dimIndices);
	      valueDiffCount++;
	      if (recNumber != lastRecordDiff) {
		recordDiffCount++;
		lastRecordDiff = recNumber;
	      }
	      ReportValueDifference (recNumber, recVary, numDims, dimIndices,
				     dimVarys, v, varName, value1, value2,
				     hyperMsg, PCT(hyperN,nHypers,2,3));
	    }
	 }
       }
       if (pctLog) WriteOutPct (PCT(hyperN,nHypers,3,3));
       IncrHyperParms (&hyper, &groups, numDims, rowMajor1, &nValues);
       CHECKforABORTso
    }
    FreeMemory (buffer1, FatalError);
    FreeMemory (buffer2, FatalError);
    if (pctLog) WriteOut (stdout, "\n");
    ReportValueDifferenceTotals (recordDiffCount, valueDiffCount, varName, v);
    return TRUE;
  }
  /****************************************************************************
  * There must be two or more dimensions with different majorities.  Attempt
  * to allocate two buffers.
  ****************************************************************************/
  AllocateBuffers (maxRec + 1, numDims, dimSizes, &groups, 0, 2, handles,
		   nValueBytes, rowMajor1, MINnHYPERS, FatalError);
  /****************************************************************************
  * Check if full records are being read.  If so, switch the majority in one
  * of the buffers before the comparison.
  ****************************************************************************/
  if (HyperFullRecord(&groups,numDims)) {
    long nBytesPerRecord = nValuesPerRec * NvalueBytes, recX;
    if (mLog) {
      WriteOut (stdout, hyperSwitchMsg);
      WriteOut (stdout, BOO(pctLog,"..0%","\n"));
    }
    InitHyperParms (&hyper, &groups, numDims, &nHypers, &nValues);
    for (hyperN = 0; hyperN < nHypers; hyperN++) {
       status2 = HYPER_READ (id2, Z, hyper, buffer1);
       if (!StatusHandler("CDF2",status2)) return FALSE;
       if (pctLog) WriteOutPct (PCT(hyperN,nHypers,1,4));
       CHECKforABORTso
       for (recX = 0; recX < hyper.recCount; recX++) {
	  size_t offset = (size_t) (recX * nBytesPerRecord);
	  if (rowMajor1)
	    COLtoROW (buffer1 + offset, buffer2 + offset, numDims,
		      dimSizes, NvalueBytes);
	  else
	    ROWtoCOL (buffer1 + offset, buffer2 + offset, numDims,
		      dimSizes, NvalueBytes);
       }
       if (pctLog) WriteOutPct (PCT(hyperN,nHypers,2,4));
       CHECKforABORTso
       status1 = HYPER_READ (id1, Z, hyper, buffer1);
       if (!StatusHandler("CDF1",status1)) return FALSE;
       if (pctLog) WriteOutPct (PCT(hyperN,nHypers,3,4));
       CHECKforABORTso
       if (memcmp(buffer1,buffer2,(size_t)(nValues*NvalueBytes))) {
	 for (valueN = 0, value1 = buffer1, value2 = buffer2;
	      valueN < nValues; valueN++,
	      value1 += (size_t)NvalueBytes, value2 += (size_t)NvalueBytes) {
	    if (memcmp(value1,value2,(size_t)NvalueBytes)) {
	      AtRecordIndices (&hyper, valueN, rowMajor1, numDims,
			       nValuesPerRec, nValuesPerDim, &recNumber,
			       dimIndices);
	      valueDiffCount++;
	      if (recNumber != lastRecordDiff) {
		recordDiffCount++;
		lastRecordDiff = recNumber;
	      }
	      ReportValueDifference (recNumber, recVary, numDims, dimIndices,
				     dimVarys, v, varName, value1, value2,
				     hyperSwitchMsg, PCT(hyperN,nHypers,3,4));
	    }
	 }
       }
       if (pctLog) WriteOutPct (PCT(hyperN,nHypers,4,4));
       IncrHyperParms (&hyper, &groups, numDims, rowMajor1, &nValues);
       CHECKforABORTso
    }
    FreeMemory (buffer1, FatalError);
    FreeMemory (buffer2, FatalError);
    if (pctLog) WriteOut (stdout, "\n");
    ReportValueDifferenceTotals (recordDiffCount, valueDiffCount, varName, v);
    return TRUE;
  }
  /****************************************************************************
  * Less than full records are being read/written.  Hyper read into the
  * buffer but then use single writes to reverse the majority.
  ****************************************************************************/
  FreeMemory (buffer2, FatalError);
  value2 = AllocateMemory ((size_t) NvalueBytes, FatalError);
  if (mLog) {
    WriteOut (stdout, hyperSingleMsg);
    WriteOut (stdout, BOO(pctLog,"..0%","\n"));
  }
  InitHyperParms (&hyper, &groups, numDims, &nHypers, &nValues);
  for (hyperN = 0; hyperN < nHypers; hyperN++) {
     status1 = HYPER_READ (id1, Z, hyper, buffer1);
     if (!StatusHandler("CDF1",status1)) return FALSE;
     if (pctLog) WriteOutPct (PCT(hyperN,nHypers,1,2));
     CHECKforABORTso
     status2 = CDFlib (SELECT_, CDF_, id2,
				Zq(Z,zVAR_RECNUMBER_,
				     rVARs_RECNUMBER_), hyper.recNumber,
		       NULL_);
     if (!StatusHandler("CDF2",status2)) return FALSE;
     for (dimN = 0; dimN < numDims; dimN++) {
	dimIndices[dimN] = hyper.dimIndices[dimN];
     }
     for (valueN = 0, value1 = buffer1; valueN < nValues;
	  valueN++, value1 += (size_t) NvalueBytes) {
	status2 = CDFlib (SELECT_, Zq(Z,zVAR_DIMINDICES_,
					rVARs_DIMINDICES_), dimIndices,
			  GET_, Zq(Z,zVAR_DATA_,rVAR_DATA_), value2,
			  NULL_);
	if (!StatusHandler("CDF2",status2)) return FALSE;
	if (memcmp(value1,value2,(size_t)NvalueBytes)) {
	  valueDiffCount++;
	  if (hyper.recNumber != lastRecordDiff) {
	    recordDiffCount++;
	    lastRecordDiff = hyper.recNumber;
	  }
	  ReportValueDifference (hyper.recNumber, recVary, numDims, dimIndices, 
				 dimVarys, v, varName, value1, value2,
				 hyperSingleMsg, PCT(hyperN,nHypers,1,2));
	}
	if (rowMajor1)
	  INCRindicesROW (numDims, dimSizes, dimIndices);
	else
	  INCRindicesCOL (numDims, dimSizes, dimIndices);
     }
     if (pctLog) WriteOutPct (PCT(hyperN,nHypers,2,2));
     IncrHyperParms (&hyper, &groups, numDims, rowMajor1, &nValues);
     CHECKforABORTso
  }
  FreeMemory (buffer1, FatalError);
  FreeMemory (value2, FatalError);
  if (pctLog) WriteOut (stdout, "\n");
  ReportValueDifferenceTotals (recordDiffCount, valueDiffCount, varName, v);
  return TRUE;
}

/******************************************************************************
* CleanupComparison.
******************************************************************************/

void CleanupComparison ()
{
  CDFclose (id1);
  CDFclose (id2);

  if (attrNumMatches1 != NULL) FreeMemory (attrNumMatches1, FatalError);
  if (attrNumMatches2 != NULL) FreeMemory (attrNumMatches2, FatalError);

  if (rVarNumMatches1 != NULL) FreeMemory (rVarNumMatches1, FatalError);
  if (rVarNumMatches2 != NULL) FreeMemory (rVarNumMatches2, FatalError);

  if (zVarNumMatches1 != NULL) FreeMemory (zVarNumMatches1, FatalError);
  if (zVarNumMatches2 != NULL) FreeMemory (zVarNumMatches2, FatalError);

  return;
}


/******************************************************************************
* SameVarys.
******************************************************************************/

Logical SameVarys (numDims, recVary1, recVary2, dimVarys1, dimVarys2)
long numDims;
long recVary1;
long recVary2;
long dimVarys1[];
long dimVarys2[];
{
  int dimNum;

  if (recVary1 != recVary2) return FALSE;

  for (dimNum = 0; dimNum < numDims; dimNum++)
     if (dimVarys1[dimNum] != dimVarys2[dimNum]) return FALSE;

  return TRUE;
}


/******************************************************************************
* EquivalentDataTypes.
******************************************************************************/

Logical EquivalentDataTypes (dataType1, dataType2)
long dataType1;
long dataType2;
{
  static long realDataType[] = {0,1,2,0,3,0,0,0,0,0,
				0,4,5,0,6,0,0,0,0,0,
				0,7,8,0,0,0,0,0,0,0,
				0,8,0,0,0,0,0,0,0,0,
				0,1,0,0,7,8,0,0,0,0,
				0,9,9,0,0,0,0,0,0,0};
  if (realDataType[(int)dataType1] == realDataType[(int)dataType2])
    return TRUE;
  else
    return FALSE;
}

/******************************************************************************
* CalcIndicesFromOffset.
******************************************************************************/

void CalcIndicesFromOffset (vOffset, rowMajor, numDims, dimSizes, dimVarys,
			    dimIndices)
long vOffset;           /* Value offset into array (not byte offset). */
Logical rowMajor;
long numDims;
long dimSizes[];
long dimVarys[];
long dimIndices[];
{
  long products[CDF_MAX_DIMS];
  int dimN, dimNt;
  if (rowMajor) {
    for (dimN = 0; dimN < numDims; dimN++) {
       products[dimN] = 1;
       for (dimNt = dimN + 1; dimNt < numDims; dimNt++)
	  if (dimVarys[dimNt]) products[dimN] *= dimSizes[dimNt];
    }
    for (dimN = 0; dimN < numDims; dimN++)
       if (dimVarys[dimN]) {
	 dimIndices[dimN] = vOffset / products[dimN];
	 vOffset %= products[dimN];
       }
       else
	 dimIndices[dimN] = 0;
  }
  else {
    for (dimN = (int) (numDims - 1); dimN >= 0; dimN--) {
       products[dimN] = 1;
       for (dimNt = dimN - 1; dimNt >= 0; dimNt--)
	  if (dimVarys[dimNt]) products[dimN] *= dimSizes[dimNt];
    }
    for (dimN = (int) (numDims - 1); dimN >= 0; dimN--)
       if (dimVarys[dimN]) {
	 dimIndices[dimN] = vOffset / products[dimN];
	 vOffset %= products[dimN];
       }
       else
	 dimIndices[dimN] = 0;
  }
  return;
}

/******************************************************************************
* StatusHandler.
******************************************************************************/

Logical StatusHandler (which, status)
char *which;
CDFstatus status;
{
  char text[CDF_STATUSTEXT_LEN+1];            /* Explanation text. */

  if (StatusERROR(status)) {
    if (report[ERRORs]) {
      if (pctLog) WriteOut (stdout, "\n");
      CDFlib (SELECT_, CDF_STATUS_, status,
	      GET_, STATUS_TEXT_, text,
	      NULL_);
      sprintf (oText, "ERROR, %s> %s", which, text);
      OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 0);
    }
    return FALSE;
  }

  if (StatusWARN(status)) {
    if (report[WARNs]) {
      if (pctLog) WriteOut (stdout, "\n");
      CDFlib (SELECT_, CDF_STATUS_, status,
	      GET_, STATUS_TEXT_, text,
	      NULL_);
      sprintf (oText, "WARNING, %s> %s", which, text);
      OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 0);
    }
    return TRUE;
  }

  if (StatusINFO(status)) {
    if (report[INFOs]) {
      if (pctLog) WriteOut (stdout, "\n");
      CDFlib (SELECT_, CDF_STATUS_, status,
	      GET_, STATUS_TEXT_, text,
	      NULL_);
      sprintf (oText, "INFO, %s> %s", which, text);
      OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 0);
    }
    return TRUE;
  }

  return TRUE;          /* CDF_OK */
}

/******************************************************************************
* AtRecordIndices.
******************************************************************************/

void AtRecordIndices (hyper, valueN, rowMajor, numDims, nValuesPerRec,
		      nValuesPerDim, recNumber, dimIndices)
struct HyperStruct *hyper;
long valueN;
Logical rowMajor;
long numDims;
long nValuesPerRec;
long nValuesPerDim[];
long *recNumber;
long dimIndices[];
{
  int dimN;
  *recNumber = (valueN / nValuesPerRec) + hyper->recNumber;
  valueN %= nValuesPerRec;
  if (rowMajor) {
    for (dimN = 0; dimN < numDims; dimN++) {
       dimIndices[dimN] = hyper->dimIndices[dimN];
       dimIndices[dimN] += (valueN / nValuesPerDim[dimN]);
       valueN %= nValuesPerDim[dimN];
    }
  }
  else {
    for (dimN = (int) (numDims - 1); dimN >= 0; dimN--) {
       dimIndices[dimN] = hyper->dimIndices[dimN];
       dimIndices[dimN] += (valueN / nValuesPerDim[dimN]);
       valueN %= nValuesPerDim[dimN];
    }
  }
  return;
}

/******************************************************************************
* ReportValueDifference.
******************************************************************************/

void ReportValueDifference (recNumber, recVary, numDims, dimIndices, dimVarys,
			    v, varName, value1, value2, typeMsg, lastPct)
long recNumber;
long recVary;
long numDims;
long dimIndices[];
long dimVarys[];
char *v;
char *varName;
Byte *value1;
Byte *value2;
char *typeMsg;
int lastPct;
{
  if (locations) {
    char where[MAX_RECORD_INDICES_LEN+1];
    EncodeRecordIndices (where, recNumber, recVary, numDims, dimIndices,
			 dimVarys);
    sprintf (oText, "DIFFERENT value for %s \"%s\" at %s", v, varName, where);
    if (pctLog) WriteOut (stdout, "\n");
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 6);
    if (displayValue) {
      WriteOut (stdout, "        CDF1 value> ");
      EncodeValuesFormat (varDataType1, varNumElems1, value1, oText, NULL, 0,
			  MAX_SCREENLINE_LEN - 21, EPOCH0_STYLE);
      WriteOut (stdout, oText);
      WriteOut (stdout, "\n");
      WriteOut (stdout, "        CDF2 value> ");
      EncodeValuesFormat (varDataType2, varNumElems2, value2, oText, NULL, 0,
			  MAX_SCREENLINE_LEN - 21, EPOCH0_STYLE);
      WriteOut (stdout, oText);
      WriteOut (stdout, "\n");
    }
    if (pctLog) {
      WriteOut (stdout, typeMsg);
      WriteOut (stdout, "    ");
      WriteOutPct (lastPct);
    }
  }
  return;
}

/******************************************************************************
* ReportValueDifferenceTotals.
******************************************************************************/

void ReportValueDifferenceTotals (recordDiffCount, valueDiffCount, varName, v)
long recordDiffCount;
long valueDiffCount;
char *varName;
char *v;
{
  if (recordDiffCount > 0) {
    diffFound = TRUE;
    sprintf (oText, "DIFFERENCES in %ld record%s for %s \"%s\"",
	     recordDiffCount, (recordDiffCount > 1 ? "s" : ""), v, varName);
    sprintf (EofS(oText), " (%ld value%s total)", valueDiffCount,
	     (valueDiffCount > 1 ? "s" : ""));
    OutputWithMargin (stdout, oText, MAX_SCREENLINE_LEN, 6);
  }
  return;
}

/******************************************************************************
* ConvertQOPs.
*    Returns TRUE if execution should continue.
******************************************************************************/

#if defined(mac)
Logical CompareQOPs (argC, argV)
int *argC;
char **argV[];
{
  DialogPtr dialogP;
  DialogRecord dRecord;
  WindowPtr behind = (WindowPtr) -1;
  ControlHandle controlHs[MAXIMUMin+1];
  Rect iRect;
  short itemN, iType, i;
  static Logical first = TRUE;
  char cancelTitle[] = "Cancel";

  static Logical pageOutput = DEFAULTpageCVT;
  static Logical compareAttrs = DEFAULTattrCMP;
  static Logical compareVars = DEFAULTvarCMP;
  static Logical compareNums = DEFAULTnumberCMP;
  static Logical compareEtc = DEFAULTetcCMP;
  static Logical negToPos = DEFAULT_NEGtoPOSfp0;
  static Logical reportInfos = REPORTinfosDEFAULT;
  static Logical reportWarns = REPORTwarningsDEFAULT;
  static Logical reportErrors = REPORTerrorsDEFAULT;
  static Logical logMsg = DEFAULTlogCMP;
  static Logical dispPct = DEFAULTpctCMP;
  static Logical dispLocs = DEFAULTlocationCMP;
  static Logical dispStats = DEFAULTstatsCMP;
  static Logical dispValues = DEFAULTvalueCMP;
  static int zMode1 = DEFAULTzModeCMP;
  static int zMode2 = DEFAULTzModeCMP;
  static Str255 CDF1text = "\p";
  static Str255 CDF2text = "\p";
  static Str255 cacheText = "\p";

  /****************************************************************************
  * Create the dialog and get the control handles.
  ****************************************************************************/

  dialogP = GetNewDialog (QOPri, &dRecord, behind);
  
  for (itemN = 1; itemN <= MAXIMUMin; itemN++) {
     GetDItem (dialogP, itemN, &iType, (Handle *) &controlHs[itemN], &iRect);
  }

  /****************************************************************************
  * Set the control values.
  ****************************************************************************/

  SetIText ((Handle) controlHs[CDF1TEXTin], CDF1text);
  SetIText ((Handle) controlHs[CDF2TEXTin], CDF2text);
  SetIText ((Handle) controlHs[CACHEin], cacheText);

  if (pageOutput) SetCtlValue (controlHs[PAGEin], 1);
  if (compareAttrs) SetCtlValue (controlHs[ATTRin], 1);
  if (compareVars) SetCtlValue (controlHs[VARin], 1);
  if (compareNums) SetCtlValue (controlHs[NUMin], 1);
  if (compareEtc) SetCtlValue (controlHs[ETCin], 1);
  if (negToPos) SetCtlValue (controlHs[NEGZin], 1);
  if (reportInfos) SetCtlValue (controlHs[INFOin], 1);
  if (reportWarns) SetCtlValue (controlHs[WARNin], 1);
  if (reportErrors) SetCtlValue (controlHs[ERRORin], 1);
  if (logMsg) SetCtlValue (controlHs[LOGin], 1);
  if (dispPct) SetCtlValue (controlHs[PCTin], 1);
  if (dispLocs) SetCtlValue (controlHs[LOCin], 1);
  if (dispStats) SetCtlValue (controlHs[STATSin], 1);
  if (dispValues) SetCtlValue (controlHs[VALUEin], 1);

  SetCtlValue (controlHs[ZMODE1inBASE+zMode1], 1);
  SetCtlValue (controlHs[ZMODE2inBASE+zMode2], 1);

  SetDItem (dialogP, (short) ODBin, (short) userItem,
	    (Handle) OutlineDefaultButton, &iRect);

  /****************************************************************************
  * Change the "Quit" button to a "Cancel" button after the first time.
  ****************************************************************************/

  if (first)
    first = FALSE;
  else
    SetCTitle (controlHs[CANCELin], CtoPstr(cancelTitle));

  /****************************************************************************
  * Display the dialog and wait for user actions.
  ****************************************************************************/
    
  ShowWindow ((WindowPtr) dialogP);
  SetCursor (ARROW_CURSOR);

  for (;;) {
    ModalDialog (FilterDialogQOPso, &itemN);
    switch (itemN) {
      /************************************************************************
      * Ok.
      ************************************************************************/
      case OKin: {
	int n;
	char tempS3[3+1];

	/**********************************************************************
	* Get the value of each control.
	**********************************************************************/

	GetIText ((Handle) controlHs[CDF1TEXTin], CDF1text);
	GetIText ((Handle) controlHs[CDF2TEXTin], CDF2text);
	GetIText ((Handle) controlHs[CACHEin], cacheText);

	pageOutput = GetCtlValue (controlHs[PAGEin]);
	compareAttrs = GetCtlValue (controlHs[ATTRin]);
	compareVars = GetCtlValue (controlHs[VARin]);
	compareNums = GetCtlValue (controlHs[NUMin]);
	compareEtc = GetCtlValue (controlHs[ETCin]);
	negToPos = GetCtlValue (controlHs[NEGZin]);
	reportInfos = GetCtlValue (controlHs[INFOin]);
	reportWarns = GetCtlValue (controlHs[WARNin]);
	reportErrors = GetCtlValue (controlHs[ERRORin]);
	logMsg = GetCtlValue (controlHs[LOGin]);
	dispPct = GetCtlValue (controlHs[PCTin]);
	dispLocs = GetCtlValue (controlHs[LOCin]);
	dispStats = GetCtlValue (controlHs[STATSin]);
	dispValues = GetCtlValue (controlHs[VALUEin]);
	
	for (zMode1 = 0; zMode1 < 3; zMode1++) {
	   if (GetCtlValue(controlHs[ZMODE1inBASE+zMode1])) break;
	}
	for (zMode2 = 0; zMode2 < 3; zMode2++) {
	   if (GetCtlValue(controlHs[ZMODE2inBASE+zMode2])) break;
	}
	
	/**********************************************************************
	* Build argc/argv.
	**********************************************************************/

	*argC = 16 + BOO(NULpString(CDF1text),0,1) +
		     BOO(NULpString(CDF2text),0,1) +
		     BOO(NULpString(cacheText),0,2);
	*argV = (char **) AllocateMemory (*argC * sizeof(char *), FatalError);
	
	n = 0;
	MAKEstrARGv (argV, n, pgmName)

	if (!NULpString(CDF1text)) {
	  PtoCstr (CDF1text);
	  MAKEstrARGv (argV, n, (char *) CDF1text)
	  CtoPstr ((char *) CDF1text);
	}

	if (!NULpString(CDF2text)) {
	  PtoCstr (CDF2text);
	  MAKEstrARGv (argV, n, (char *) CDF2text)
	  CtoPstr ((char *) CDF2text);
	}

	MAKEbooARGv (argV, n, pageOutput, "-page", "-nopage")
	MAKEbooARGv (argV, n, compareAttrs, "-attr", "-noattr")
	MAKEbooARGv (argV, n, compareVars, "-var", "-novar")
	MAKEbooARGv (argV, n, compareNums, "-number", "-nonumber")
	MAKEbooARGv (argV, n, compareEtc, "-etc", "-noetc")
	MAKEbooARGv (argV, n, negToPos, "-neg2posfp0", "-noneg2posfp0")
	MAKEbooARGv (argV, n, logMsg, "-log", "-nolog")
	MAKEbooARGv (argV, n, dispPct, "-percent", "-nopercent")
	MAKEbooARGv (argV, n, dispLocs, "-location", "-nolocation")
	MAKEbooARGv (argV, n, dispStats, "-statistics", "-nostatistics")
	MAKEbooARGv (argV, n, dispValues, "-value", "-novalue")
	
	MAKEstrARGv (argV, n, "-zmode")
	sprintf (tempS3, "%d,%d", zMode1, zMode2);
	MAKEstrARGv (argV, n, tempS3)

	MAKEstrARGv (argV, n, "-report")
	MAKEstrARGv (argV, n, StatusCodeReportOptions(reportErrors,
						      reportWarns,
						      reportInfos))

	if (!NULpString(cacheText)) {
	  MAKEstrARGv (argV, n, "-cache")
	  PtoCstr (cacheText);
	  MAKEstrARGv (argV, n, (char *) cacheText)
	  CtoPstr ((char *) cacheText);
	}

	/**********************************************************************
	* Close the dialog and return.
	**********************************************************************/
	CloseDialog (dialogP);
	return TRUE;
      }
      /************************************************************************
      * Help.
      ************************************************************************/
      case HELPin: {
	int n;
	*argC = 1;
	*argV = (char **) AllocateMemory (*argC * sizeof(char *), FatalError);
	n = 0;
	MAKEstrARGv (argV, n, pgmName)
	CloseDialog (dialogP);
	return TRUE;
      }
      /************************************************************************
      * Cancel.
      ************************************************************************/
      case CANCELin:
	CloseDialog (dialogP);
	return FALSE;
      /************************************************************************
      * Select CDF/1 specification.
      ************************************************************************/
      case CDF1SELECTin: {
	StandardFileReply reply;
	char path[DU_MAX_PATH_LEN+1];
	StandardGetFile (FilterForCDFs, -1, NULL, &reply);
	if (reply.sfGood && !reply.sfIsFolder && !reply.sfIsVolume) {
	  BuildMacPath (&reply.sfFile, path, TRUE);
	  CDF1text[0] = strlen (path);
	  strcpyX ((char *) &CDF1text[1], path, 255);
	  SetIText ((Handle) controlHs[CDF1TEXTin], CDF1text);
	}
	break;
      }
      /************************************************************************
      * Select CDF/2 specification.
      ************************************************************************/
      case CDF2SELECTin: {
	StandardFileReply reply;
	char path[DU_MAX_PATH_LEN+1];
	StandardGetFile (FilterForCDFs, -1, NULL, &reply);
	if (reply.sfGood && !reply.sfIsFolder && !reply.sfIsVolume) {
	  BuildMacPath (&reply.sfFile, path, TRUE);
	  CDF2text[0] = strlen (path);
	  strcpyX ((char *) &CDF2text[1], path, 255);
	  SetIText ((Handle) controlHs[CDF2TEXTin], CDF2text);
	}
	break;
      }
      /************************************************************************
      * Check boxes.
      ************************************************************************/
      case PAGEin:
      case ATTRin:
      case VARin:
      case NUMin:
      case ETCin:
      case NEGZin:
      case INFOin:
      case WARNin:
      case ERRORin:
      case LOGin:
      case PCTin:
      case LOCin:
      case STATSin:
      case VALUEin:
	SetCtlValue (controlHs[itemN], BOO(GetCtlValue(controlHs[itemN]),0,1));
	break;
      /************************************************************************
      * Radio buttons.
      ************************************************************************/
      case ZMODE1inBASE+0:
      case ZMODE1inBASE+1:
      case ZMODE1inBASE+2:
	for (i = 0; i < 3; i++) SetCtlValue (controlHs[ZMODE1inBASE+i], 0);
	SetCtlValue (controlHs[itemN], 1);
	break;
      case ZMODE2inBASE+0:
      case ZMODE2inBASE+1:
      case ZMODE2inBASE+2:
	for (i = 0; i < 3; i++) SetCtlValue (controlHs[ZMODE2inBASE+i], 0);
	SetCtlValue (controlHs[itemN], 1);
	break;
    }
  }
}
#endif
