Latest napi.w

Nick Maliszewskyj nickm at rrdjazz.nist.gov
Fri Sep 19 13:38:50 BST 1997


Hello all,

	With Freddie's blessing, here is the most recent version of the
NeXus API in nuweb form.

						Nick
%  Copyleft (c) 1997 by Mark Koennecke at PSI, Switzerland.
%                       Przemek Klosowski at NIST, USA
%
%                    Some changes made 6/97 
%                    Nick Maliszewskyj, NIST Center for Neutron Research
%
%  This software is distributed in the hope that it will be useful,
%  but WITHOUT ANY WARRANTY; without even the implied warranty of
%  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%  GNU General Public License for more details.
%
%  You may already have a copy of the GNU General Public License; if
%  not, write to the Free Software Foundation, Inc., 675 Mass Ave,
%  Cambridge, MA 02139, USA.
%

\documentclass[12pt]{article}

\setlength{\oddsidemargin}{-.1in}
\setlength{\evensidemargin}{0in}
\setlength{\topmargin}{0in}
\addtolength{\topmargin}{-\headheight}
\addtolength{\topmargin}{-\headsep}
\setlength{\textheight}{8.9in}
\setlength{\textwidth}{6.2in}
\setlength{\marginparwidth}{0.5in}

\begin{document}
\title{The NEXUS Application Programmer's Interface}

\author{
  Mark K\"onnecke,$^1$\thanks{Mark.Koennecke@@psi.ch}
  Przemek Klosowski,$^2$\thanks{przemek.klosowski@@nist.gov} \\
  Nicholas C. Maliszewskyj,$^3$\thanks{nick.maliszewskyj@@nist.gov}
  Freddie Akeroyd$^4$\thanks{faa@@isise.rl.ac.uk}\\ \\
  $^1$Labor f\"ur Neutronenstreuung,
  Paul Scherrer Institut, \\
  CH-5232 Villigen PSI,
  Switzerland.\\
  $^2$U. of Maryland \& NIST. \\      
  $^3$NIST Center for Neutron Research.\\
  $^4$ISIS Facility, Rutherford Appleton Laboratory.
}

\maketitle
\tableofcontents

\vskip.3in
\centerline{\large\bf Abstract}
\vskip.2in
\begin{center}
\parbox{.8\textwidth}{

NEXUS is a proposed portable data exchange format for the neutron and
X-ray scattering communities. This document supplements the NEXUS
proposal (described in a separate publication) by defining a simplified
programming interface for reading and writing hierarchical data format
(HDF) files compliant with the NEXUS standard.

}
\end{center}

\section{Introduction}
\label{chap:intro}

This file defines an Application Programmer's Interface (API) to HDF
library as used for the NEXUS data format. It encapsulates a subset of
HDF and provides some helper routines to simplify creating and reading
NEXUS data files.

The API is designed to be modal; there is a hidden state that
determines which groups and datasets (Vgroups and SDSes) are open at
any given moment, and subsequent operations are implicitly performed
on these entities. This reduces the number of parameters to pass
around in API calls, at the cost of forcing certain pre-approved {\em
mode d'emploi}. This mode d'emploi will be familiar to most: it is very
similar to navigating a directory hierarchy. In our case HDF--VGroups are
the directories, which can hold other directories and data items which are
restricted to being HDF--scientific data sets (SDS).

The API comprises the following functional groups:

\begin{enumerate}
\item General initialization and shutdown: opening and closing the file,
      creating or opening an existing group or dataset, and closing them.
\item Reading and writing data and attributes to previously opened datasets.
\item Routines to obtain meta-data and to iterate over component datasets and
      attributes.
\item Handling of linking and group hierarchy.
\end{enumerate}

\section{Implementation}

\subsection{Data Structures}

The approach used in this version is to maintain a datastructure for each
open file. This datastructure will hold a stack of Vgroups traversed and
will thus allow stepping back and forth in the Nexus file structure. The
stack is implemented using an array. The datastructure looks like this: 

@d data @{
typedef struct __NexusFile {
  int iNXID;
  char iAccess[2];
  int32 iVID;
  int32 iSID;
  int32 iCurrentVG;
  int32 iCurrentSDS;
  struct iStack {
    int32 iVref;
    int iNDir;
    int iCurDir;
    int32 *iRefDir;
    int32 *iTagDir;
  } iStack[NXMAXSTACK];
  int iStackPtr;
  struct iStack iAtt;
} NexusFile, *pNexusFile;
@}

The fields in more detail: \begin{itemize}
\item iNXID is a test value against memory corruption.
\item iAccess is the Vgroup access mode.
\item iVID is the file ID to use for the Vgroup interface.
\item iSID is the file ID to use for the SDS interface.
\item iCurrentVG is the ID of the current open Vgroup. Is 0, if there is 
 no Vgroup open.
\item iCurrentSDS is the ID of the current open SDS. Is 0, if there is 
 no SDS open.
\item iStack is an array of structures describing the contents of
Vgroups in the current path
\item iStackPtr is the pointer to the current directory in iStack.
\item iVref is the reference number of the Vgroup in the current path
\item iNDir, iCurDir, iRefDir, iTagDir are data fields used during an
directory search with NXgetnextentry. They are:
\begin{itemize}
\item iNDir: number of directory entries.
\item iCurDir current directory entry.
\item iRefDir array of reference numbers.
\item iTagDir array of tag numbers.
\end{itemize}
\item iAtt is a structure just like iStack, reserved for SDS attributes
\end{itemize} 

The other datastructure used is NXlink. It contains the information
necessary to make a link between Vgroups and SDS's. This is easy:

@d datal @{
typedef struct {
          int32 iTag;
          int32 iRef;
        } NXlink;
@}

Before diving into code, it may be helpful to highlight a few peculiarities
of the HDF interface which can lead to considerable confusion. The first is
that there is no single ID to refer to a given file: the Vgroup interface and
the SDS interface each use separate ID's which have to be initialized at file
opening and used appropriately. The second is that there is a proliferation
of integer ID's associated with each HDF--object. A reference ID identifies
each object within an HDF file. A tag denotes the type of the object. When
objects are opened (or ``attached''), an additional ID is returned which
serves as a handle for subsequent operations. This final ID becomes invalid
once the object is closed. In the end, a great deal of bookkeeping is needed
to keep track of all these identifiers. \label{ididid}

\subsection{General initialization and shutdown}
\label{ss:gen}


The routines in this group are for opening and closing the NEXUS/HDF file,
creating or opening an existing group or dataset,  and closing them.

@d Dec1 @{
NXstatus  NXopen(char * filename, NXaccess access_method, NXhandle* pHandle);
NXstatus  NXclose(NXhandle* pHandle);
  
NXstatus  NXmakegroup (NXhandle handle, char* Vgroup, char* NXclass);
NXstatus  NXopengroup (NXhandle handle, char* Vgroup, char* NXclass);
NXstatus  NXclosegroup(NXhandle handle);
  
NXstatus  NXmakedata (NXhandle handle, char* label, int datatype, 
	              int rank, int dim[]);
NXstatus  NXopendata (NXhandle handle, char* label);
NXstatus  NXclosedata(NXhandle handle);
@}

\subsubsection{NXopen}
NXopen opens the HDF--file, creating and initializing a NexusFile structure
in the process. The returned handle is actually a pointer to this structure.
The principal danger of this approach is that somebody might perform arithmetic
on the handle and thereby corrupt the system. In order to test for this, the
NexusFile structure contains the NXID field which can be checked against a
predefined value. The permitted access modes are: {\tt NXACC\_CREATE}, 
{\tt NXACC\_RDWR}, and {\tt NXACC\_READ}.

@D NXopen @{
NXstatus 
NXopen (char *filename, NXaccess am, NXhandle * pHandle)
{

  pNexusFile pNew = NULL;
  char pBuffer[512];
  int iRet;

  *pHandle = NULL;
  /* get memory */
  pNew = (pNexusFile) malloc (sizeof (NexusFile));
  if (!pNew) {
    NXIReportError (NXpData, "ERROR: no memory to create File datastructure");
    return NX_ERROR;
  }
  memset (pNew, 0, sizeof (NexusFile));

  /* start SDS interface */
  pNew->iSID = SDstart (filename, am);
  if (pNew->iSID <= 0) {
    sprintf (pBuffer, "ERROR: cannot open file: %s", filename);
    NXIReportError (NXpData, pBuffer);
    free (pNew);
    return NX_ERROR;
  }
  /* 
   * Otherwise we try to create the file two times which makes HDF
   * Throw up on us.
   */
  if (am == NXACC_CREATE) {
    am = NXACC_RDWR;
  }
/*
 * need to create global attributes         file_name file_time NeXus_version 
 * at some point for new files
 */

  /* Set Vgroup access mode */
  if (am == NXACC_READ) {
    strcpy (pNew->iAccess, "r");
  } else {
    strcpy (pNew->iAccess, "w");
  }

  /* start Vgroup API */
  pNew->iVID = Hopen (filename, am, 100);
  if (pNew->iVID <= 0) {
    sprintf (pBuffer, "ERROR: cannot open file: %s", filename);
    NXIReportError (NXpData, pBuffer);
    free (pNew);
    return NX_ERROR;
  }
  Vstart (pNew->iVID);
  pNew->iNXID = NXSIGNATURE;
  pNew->iStack[0].iVref = 0;	/* root! */

  *pHandle = (NXhandle) pNew;
  return NX_OK;
}
@}

{\tt NXfopen} is a helper function for the FORTRAN interface.

@D NXopen @{

NXstatus  
NXfopen(char * filename, NXaccess* am, NexusFile* pHandle)
{
  int ret;
  NXhandle fileid;
  ret = NXopen(filename, *am, &fileid);
  if (ret == NX_OK) {
    memcpy(pHandle, fileid, sizeof(NexusFile));
  } else {
    memset(pHandle, 0, sizeof(NexusFile));
  }
  return ret;
}
@}


\subsubsection{NXclose}
NXclose closes an Nexus file and deletes all associated datastructures from
memory. The data handle will no longer be valid after this procedure is called.
@D NXclose @{
NXstatus
NXclose (NXhandle * fid)
{
  pNexusFile pFile = NULL;
  int iRet;

  pFile = NXIassert (fid);
  iRet = 0;
  /* close links into vGroups or SDS */
  if (pFile->iCurrentVG != 0) {
    Vdetach (pFile->iCurrentVG);
  }
  if (pFile->iCurrentSDS != 0) {
    iRet = SDendaccess (pFile->iCurrentSDS);
  }
  if (iRet < 0) {
    NXIReportError (NXpData, "ERROR: ending access to SDS");
  }
  /* close the SDS and Vgroup API's */
  Vend (pFile->iVID);
  iRet = SDend (pFile->iSID);
  if (iRet < 0) {
    NXIReportError (NXpData, "ERROR: HDF cannot close SDS interface");
  }
  iRet = Hclose (pFile->iVID);
  if (iRet < 0) {
    NXIReportError (NXpData, "ERROR: HDF cannot close HDF file");
  }

  /* Return to a pristine state */
  NXIKillDir (pFile);
  *fid = NULL;
  return NX_OK;
}
@}

{\tt NXfclose} is a helper function for the FORTRAN interface.

@D NXclose @{
NXstatus
NXfclose (NexusFile * pHandle)
{
  NXhandle h;
  int ret;
  h = pHandle;
  ret = NXclose (&h);
  memset (pHandle, 0, sizeof (NexusFile));
  return ret;
}
@}

\subsubsection{NXmakegroup} 

NXmakegroup creates a new Vgroup at the current level in the Vgroup
hierarchy. The new Vgroup will be assigned the label {\tt name} and
class descriptor {\tt class}. As further references to each Vgroup are
maintained through its name and class, care must be taken to prevent
the creation of two Vgroups with the same name and class at the same
level. It is important to note that the newly created Vgroup must be
subsequently opened by NXopengroup before it can be accessed.  
@D NXmakeG @{ 
NXstatus 
NXmakegroup (NXhandle fid, char *name, char *nxclass)
{
  pNexusFile pFile;
  int32 iNew, iRet;
  char pBuffer[256];

  pFile = NXIassert (fid);
  /* 
   * Make sure that a group with the same name and nxclass does not
   * already exist.
   */
  if ((iRet = NXIFindVgroup (pFile, name, nxclass)) >= 0) {
    sprintf (pBuffer, "ERROR: Vgroup %s, class %s already exists",
	     name, nxclass);
    NXIReportError (NXpData, pBuffer);
    return NX_ERROR;
  }
  /* create and configure the group */
  iNew = Vattach (pFile->iVID, -1, "w");
  if (iNew < 0) {
    NXIReportError (NXpData, "ERROR: HDF could not create Vgroup");
    return NX_ERROR;
  }
  Vsetname (iNew, name);
  Vsetclass (iNew, nxclass);

  /* Insert it into the hierarchy, when appropriate */
  iRet = 0;
  if (pFile->iCurrentVG != 0) {
    iRet = Vinsert (pFile->iCurrentVG, iNew);
  }
  Vdetach (iNew);
  if (iRet < 0) {
    NXIReportError (NXpData, "ERROR: HDF failed to insert Vgroup");
    return NX_ERROR;
  }
  return NX_OK;
}
@}

\subsubsection{NXopengroup}
NXopengroup opens an existing Vgroup for reading or writing.
This routine maintains the Vgroup stack. There are several possible
situations. The first is that we are at root level (iCurrentVG = 0). The
Vgroup must be found and attached to. Than the Stack has to be incremented.
The next situation is, that we are already in a Vgroup. Than we have to find
the new Vgroup, detach the current Vgroup and attach to the new one, thereby
incrementing the stack.

@d NXopenG @{
/*------------------------------------------------------------------------*/
NXstatus
NXopengroup (NXhandle fid, char *name, char *nxclass)
{
  pNexusFile pFile;
  int32 iNew, iRef;
  char pBuffer[256];

  pFile = NXIassert (fid);

  iRef = NXIFindVgroup (pFile, name, nxclass);
  if (iRef < 0) {
    sprintf (pBuffer, "ERROR: Vgroup %s, class %s NOT found", name, nxclass);
    NXIReportError (NXpData, pBuffer);
    return NX_ERROR;
  }
  /* are we at root level ? */
  if (pFile->iCurrentVG == 0) {
    pFile->iCurrentVG = Vattach (pFile->iVID, iRef, pFile->iAccess);
    pFile->iStackPtr++;
    pFile->iStack[pFile->iStackPtr].iVref = iRef;
  } else {
    Vdetach (pFile->iCurrentVG);
    pFile->iStackPtr++;
    pFile->iStack[pFile->iStackPtr].iVref = iRef;
    pFile->iCurrentVG = Vattach (pFile->iVID,
				 pFile->iStack[pFile->iStackPtr].iVref,
				 pFile->iAccess);
  }
  NXIKillDir (pFile);
  return NX_OK;
}
@}
 
\subsubsection{NXclosegroup}

NXclosegroup closes an open Vgroup and ascends one level back up the Vgroup
hierarchy, clearing the indices for child Vgroups and datasets in the process.

@d NXcloseG @{
NXstatus
NXclosegroup (NXhandle fid)
{
  pNexusFile pFile;

  pFile = NXIassert (fid);

  /* first catch the trivial case: we are at root and cannot get 
     deeper into a negative directory hierarchy (anti-directory)
   */
  if (pFile->iCurrentVG == 0) {
    NXIKillDir (pFile);
    return NX_OK;
  } else {			/* Sighhh. Some work to do */
    /* close the current VG and decrement stack */
    Vdetach (pFile->iCurrentVG);
    NXIKillDir (pFile);
    pFile->iStackPtr--;
    if (pFile->iStackPtr <= 0) {	/* we hit root */
      pFile->iStackPtr = 0;
      pFile->iCurrentVG = 0;
    } else {
      /* attach to the lower Vgroup */
      pFile->iCurrentVG = Vattach (pFile->iVID,
				   pFile->iStack[pFile->iStackPtr].iVref,
				   pFile->iAccess);
    }
  }
  return NX_OK;
}
@}

\subsubsection{NXmakedata} 

NXmakedata creates a new scientific datset. As arguments it takes the
usual filehandle and an integer denoting the datatype. This needs to
be one of the HDF defined type identifiers. Commonly used types are:
DFNT\_FLOAT for 32 bit floats, DFNT\_INT32 for 32-bit integers,
DFNT\_UINT8 for unsigned bytes (or characters). Rank is the
dimensionality of the data. The parameter dims is an integer array
which has rank values. Each value denotes the length of the data in
the dimensions. The first dimension can be set to {\tt SD\_UNLIMITED},
or zero, in which case data may be appended to the data set along that
dimension and the HDF API will adjust the size of the SDS accordingly.
Note that this function does not explicitly open the dataset.

@D NXmakeD @{
NXstatus
NXmakedata (NXhandle fid, char *name, int datatype, int rank,
	    int dimensions[])
{
  pNexusFile pFile;
  int32 iNew;
  char pBuffer[256];
  int i, iRet;

  pFile = NXIassert (fid);


  if ((iNew = NXIFindSDS (fid, name)) >= 0) {
    sprintf (pBuffer, "ERROR: SDS %s already exists at this level", name);
    NXIReportError (NXpData, pBuffer);
    return NX_ERROR;
  }
  /* Do some argument checking */
  switch (datatype) {
  case DFNT_FLOAT32:    break;
  case DFNT_FLOAT64:    break;
  case DFNT_INT8:       break;
  case DFNT_UINT8:      break;
  case DFNT_INT16:      break;
  case DFNT_UINT16:     break;
  case DFNT_INT32:      break;
  case DFNT_UINT32:     break;
  default:
    sprintf (pBuffer, "ERROR: unknown datatype specified for SDS %s",
	     name);
    NXIReportError (NXpData, pBuffer);
    return NX_ERROR;
  }

  if (rank <= 0) {
    sprintf (pBuffer, "ERROR: invalid rank specified for SDS %s",
	     name);
    NXIReportError (NXpData, pBuffer);
    return NX_ERROR;
  }
  for (i = 0; i < rank; i++) {
    if (dimensions[i] <= 0) {
      /* Make an exception for the "unlimited" dimension */
      if (!((i==0) && (dimensions[i] == 0))) {
        sprintf (pBuffer,
	         "ERROR: invalid dimension %d, value %d given for SDS %s",
	         i, dimensions[i], name);
        NXIReportError (NXpData, pBuffer);
        return NX_ERROR;
      }
    }
  }


  /* behave nicely, if there is still an SDS open */
  if (pFile->iCurrentSDS != 0) {
    SDendaccess (pFile->iCurrentSDS);
    pFile->iCurrentSDS = 0;
  }
  /* Do not allow creation of SDS's at the root level */
  if (pFile->iCurrentVG == 0) {
    sprintf (pBuffer, "ERROR: SDS creation at root level is not permitted");
    NXIReportError (NXpData, pBuffer);
    return NX_ERROR;
  }
  /* dataset creation */
  iNew = SDcreate (pFile->iSID, name, (int32) datatype, (int32)rank, 
	(int32 *)dimensions);
  if (iNew < 0) {
    sprintf (pBuffer, "ERROR: cannot create SDS %s, check arguments",
	     name);
    NXIReportError (NXpData, pBuffer);
    return NX_ERROR;
  }
  /* link into Vgroup, if in one */
  if (pFile->iCurrentVG != 0) {
    iRet = Vaddtagref (pFile->iCurrentVG, DFTAG_SDG, SDidtoref (iNew));
  }
  iRet = SDendaccess (iNew);
  if (iRet < 0) {
    NXIReportError (NXpData, "ERROR: HDF cannot end access to SDS");
    return NX_ERROR;
  }
  return NX_OK;
}
@}

\subsubsection{NXopendata}
NXopendata opens an existing scientific data set for further manipulation,
(i.e. reading and writing of data or retrieving its attributes).
@D NXopenD @{
NXstatus
NXopendata (NXhandle fid, char *name)
{
  pNexusFile pFile;
  int32 iNew;
  char pBuffer[256];
  int iRet;

  pFile = NXIassert (fid);

  /* First find the reference number of the SDS */
  iNew = NXIFindSDS (fid, name);
  if (iNew < 0) {
    sprintf (pBuffer, "ERROR: SDS %s not found at this level", name);
    NXIReportError (NXpData, pBuffer);
    return NX_ERROR;
  }
  /* Be nice: properly close the old open SDS silently if there is
   * still an SDS open.
   */
  if (pFile->iCurrentSDS) {
    iRet = SDendaccess (pFile->iCurrentSDS);
    if (iRet < 0) {
      NXIReportError (NXpData, "ERROR: HDF cannot end access to SDS");
    }
  }
  /* clear pending attribute directories first */
  NXIKillAttDir (pFile);

  /* open the SDS */
  iNew = SDreftoindex (pFile->iSID, iNew);
  pFile->iCurrentSDS = SDselect (pFile->iSID, iNew);
  if (pFile->iCurrentSDS < 0) {
    NXIReportError (NXpData, "ERROR: HDF error opening SDS");
    pFile->iCurrentSDS = 0;
    return NX_ERROR;
  }
  return NX_OK;
}
@}

\subsubsection{NXclosedata}
NXclosedata ends the access to the currently active scientific dataset.
@d NXcloseD @{
NXstatus
NXclosedata (NXhandle fid)
{
  pNexusFile pFile;
  int iRet;

  pFile = NXIassert (fid);

  if (pFile->iCurrentSDS != 0) {
    iRet = SDendaccess (pFile->iCurrentSDS);
    pFile->iCurrentSDS = 0;
    if (iRet < 0) {
      NXIReportError (NXpData, "ERROR: HDF cannot end access to SDS");
      return NX_ERROR;
    }
  } else {
    NXIReportError (NXpData, "ERROR: no SDS open --> nothing to do");
    return NX_ERROR;
  }
  NXIKillAttDir (pFile);	/* for attribute data */
  return NX_OK;
}
@}

\subsection{Reading and writing}
\label{ss:rw}

Routines to read and write data and attributes to previously opened datasets:

@d DatDec @{
NXstatus  NXgetdata(NXhandle fileid, void* data);
NXstatus  NXgetslab(NXhandle fileid, void* data, int start[], int size[]);
NXstatus  NXgetattr(NXhandle fileid, char* name, char* data, int datalen);
NXstatus  NXgetdim (NXhandle fileid, int dimension, void * data);

NXstatus  NXputdata(NXhandle fileid, void* data);
NXstatus  NXputslab(NXhandle fileid, void* data, int start[], int size[]);
NXstatus  NXputattr(NXhandle fileid, char *name, void* data, int datalen, 
		    int iType);
NXstatus  NXputdim (NXhandle fileid, int dimension, void * data);
@}

Please note, that all data reading and writing routines require that the
scientific dataset has been opened beforehand with NXopendata.

\subsubsection{NXgetdata}
NXgetdata reads data from the currently open scientific data set into
data. Please note that memory overwrite occurs if the caller has not
allocated enough memory to hold all the data available. Functions described
in section \ref{ss:meta} allow the size of the data to be inquired before
being read in order to prevent this occurrence.

Note as well that the scientific dataset must have been opened with
NXopendata before this function can succeed. Before it can do its job,
NXgetdata has to get dimension information first.
@d NXgetD @{
NXstatus
NXgetdata (NXhandle fid, void *data)
{
  pNexusFile pFile;
  int32 iStart[MAX_VAR_DIMS], iEnd[MAX_VAR_DIMS];
  NXname pBuffer;
  int32 iRank, iAtt, iType;

  pFile = NXIassert (fid);

  /* check if there is an SDS open */
  if (pFile->iCurrentSDS == 0) {
    NXIReportError (NXpData, "ERROR: no SDS open");
    return NX_ERROR;
  }
  /* first read dimension information */
  memset (iStart, 0, MAX_VAR_DIMS * sizeof (int32));
  SDgetinfo (pFile->iCurrentSDS, pBuffer, &iRank, iEnd, &iType, &iAtt);
  /* actually read */
  SDreaddata (pFile->iCurrentSDS, (int32*)iStart, NULL, (int32*)iEnd, data);
  return NX_OK;
}
@} 

\subsubsection{NXgetslab}
NXgetslab reads a subset of the data in the current scientific data set.
The start dimensions to read from are specified in iStart, the end in iEnd.
The caller is responsible for allocating enough memory to store the data to
be read.
@d NXgetS @{
NXstatus
NXgetslab (NXhandle fid, void *data, int iStart[], int iEnd[])
{
  pNexusFile pFile;

  pFile = NXIassert (fid);

  /* check if there is an SDS open */
  if (pFile->iCurrentSDS == 0) {
    NXIReportError (NXpData, "ERROR: no SDS open");
    return NX_ERROR;
  }
  /* actually read */
  SDreaddata (pFile->iCurrentSDS, (int32*)iStart, NULL, (int32*)iEnd, data);
  return NX_OK;
}
@} 

\subsubsection{NXgetattr}

A useful feature of the hierarchical data format is the concept of attributes.
This is additional data which is usually associated with an SDS which
provide additional meaning (e.g., units). Attributes not associated with
any particular SDS are considered to be global. The function NXgetattr reads
this type of data. If an SDS is open, it reads the attributes associated with
the SDS, otherwise it tries to find a global attribute. Up to {\em datalen}
bytes, for which the caller is responsible for allocating,
are read into the string {\em data}.

In order to enable this scheme, NXgetattr has to first read into an
internal temporary buffer before it copying the data to the data buffer
provided. The temporary buffer is then discarded.
Note that searching for attributes is handled differently depending on whether
the attributes are global or associated with an SDS.
@D NXgetA @{
NXstatus
NXgetattr (NXhandle fid, char *name, char *data, int datalen)
{
  pNexusFile pFile;
  int32 iNew;
  void *pData = NULL;
  int32 iLen, iType, iRet;
  char pBuffer[256];
  NXname pNam;

  pFile = NXIassert (fid);

  /* find attribute */
  if (pFile->iCurrentSDS != 0) {
    /* SDS attribute */
    iNew = SDfindattr (pFile->iCurrentSDS, name);
  } else {
    /* global attribute */
    iNew = SDfindattr (pFile->iSID, name);
  }
  if (iNew < 0) {
    sprintf (pBuffer, "ERROR: attribute %s not found", name);
    NXIReportError (NXpData, pBuffer);
    return NX_ERROR;
  }
  /* get more info, allocate temporary data space */
  if (pFile->iCurrentSDS != 0) {
    iRet = SDattrinfo (pFile->iCurrentSDS, iNew, pNam, &iType, &iLen);
  } else {
    iRet = SDattrinfo (pFile->iSID, iNew, pNam, &iType, &iLen);
  }
  if (iRet < 0) {
    sprintf (pBuffer, "ERROR: HDF could not read attribute info");
    NXIReportError (NXpData, pBuffer);
    return NX_ERROR;
  }
  iLen = iLen * DFKNTsize (iType);
  pData = (void *) malloc (iLen);
  if (!pData) {
    NXIReportError (NXpData, "ERROR: allocating memory in NXgetattr");
    return NX_ERROR;
  }
  memset (pData, 0, iLen);

  /* finally read the data */
  if (pFile->iCurrentSDS != 0) {
    iRet = SDreadattr (pFile->iCurrentSDS, iNew, pData);
  } else {
    iRet = SDreadattr (pFile->iSID, iNew, pData);
  }
  if (iRet < 0) {
    sprintf (pBuffer, "ERROR: HDF could not read attribute data");
    NXIReportError (NXpData, pBuffer);
    return NX_ERROR;
  }
  /* copy data to caller */
  memset (data, 0, datalen);
  if (datalen < iLen) {
    iLen = datalen - 1;
  }
  memcpy (data, pData, iLen);
  free (pData);
  return NX_OK;
}
@} 

\subsubsection{NXputdata}
NXputdata copies data into the currently open scientific data set.
@D NXputD @{
NXstatus
NXputdata (NXhandle fid, void *data)
{
  pNexusFile pFile;
  int32 iStart[MAX_VAR_DIMS], iEnd[MAX_VAR_DIMS], iStride[MAX_VAR_DIMS];
  NXname pBuffer;
  int32 iRank, iAtt, iType, iRet, i;
  char pError[512];

  pFile = NXIassert (fid);

  /* check if there is an SDS open */
  if (pFile->iCurrentSDS == 0) {
    NXIReportError (NXpData, "ERROR: no SDS open");
    return NX_ERROR;
  }
  /* first read dimension information */
  memset (iStart, 0, MAX_VAR_DIMS * sizeof (int32));
  SDgetinfo (pFile->iCurrentSDS, pBuffer, &iRank, iEnd, &iType, &iAtt);

  /* initialise stride to 1 */
  for (i = 0; i < iRank; i++) {
    iStride[i] = 1;
  }

  /* actually write */
  iRet = SDwritedata (pFile->iCurrentSDS, (int32*)iStart, (int32*)iStride, 
                      (int32*)iEnd, data);
  if (iRet < 0) {
    sprintf (pError, "ERROR: failure to write data to %s", pBuffer);
    NXIReportError (NXpData, pError);
    return NX_ERROR;
  }
  return NX_OK;
}
@} 

\subsubsection{NXputslab}
NXputslab writes an subset of data as specified by iStart to iEnd into the
currently open SDS.
@d NXputS @{
NXstatus
NXputslab (NXhandle fid, void *data, int iStart[], int iEnd[])
{
  pNexusFile pFile;
  int iRet;
  int iStride[MAX_VAR_DIMS], i;


  pFile = NXIassert (fid);

  /* check if there is an SDS open */
  if (pFile->iCurrentSDS == 0) {
    NXIReportError (NXpData, "ERROR: no SDS open");
    return NX_ERROR;
  }
  /* initialise stride to 1 */
  for (i = 0; i < MAX_VAR_DIMS; i++) {
    iStride[i] = 1;
  }

  /* actually write */
  iRet = SDwritedata (pFile->iCurrentSDS, (int32*)iStart, (int32*)iStride, 
	              (int32*)iEnd, data);
  if (iRet < 0) {
    NXIReportError (NXpData, "ERROR: writing slab failed");
    return NX_ERROR;
  }
  return NX_OK;
}
@} 

\subsubsection{NXputattr}
Nxputattr writes an attribute into an Nexus file. The attribute will be
linked to the currently open SDS. If no SDS is open, the attribute becomes
a global one.
@d NXputA @{
NXstatus
NXputattr (NXhandle fid, char *name, void *data, int datalen, int iType)
{
  pNexusFile pFile;
  int iRet;

  pFile = NXIassert (fid);

  if (pFile->iCurrentSDS != 0) {
    /* SDS attribute */
    iRet = SDsetattr (pFile->iCurrentSDS, name, iType,
		      datalen, data);
  } else {
    /* global attribute */
    iRet = SDsetattr (pFile->iSID, name, iType,
		      datalen, data);
  }
  if (iRet < 0) {
    NXIReportError (NXpData, "ERROR: HDf failed to store attribute ");
    return NX_ERROR;
  }
  return NX_OK;
}
@}

\subsection{Meta-data} 
\label{ss:meta}

The following are routines to obtain meta-data and to iterate over
component datasets and attributes.

@d DecInfo @{
NXstatus  NXgetinfo     (NXhandle fileid, int* rank, int dimension[], int* 
datatype);
NXstatus  NXgetnextentry(NXhandle fileid, NXname name, NXname class, int* 
datatype);
NXstatus  NXgetnextattr(NXhandle fileid, NXname pName, int *iLength, int *iType);
@}

\subsubsection{NXgetinfo}
NXgetinfo returns information about an SDS. {\tt rank} is the dimensionality
of the data, {\tt dimension} will contain the size of the data in each
dimension and {\tt datatype} resolves to one of the HDF--datatypes.
The SDS must be open in order for this routine to work.
@d NXgetI @{
NXstatus
NXgetinfo (NXhandle fid, int *rank, int dimension[], int *iType)
{
  pNexusFile pFile;
  NXname pBuffer;
  int32 iAtt;

  pFile = NXIassert (fid);

  /* check if there is an SDS open */
  if (pFile->iCurrentSDS == 0) {
    NXIReportError (NXpData, "ERROR: no SDS open");
    return NX_ERROR;
  }
  /* read information */
  SDgetinfo (pFile->iCurrentSDS, pBuffer, (int32*)rank, (int32*)dimension, 
             (int32*)iType, &iAtt);
  return NX_OK;
}
@} 



\subsubsection{NXgetnextentry}

NXgetnextentry  implements a directory search facility on the current Vgroup
level. The first call will initialize Vgroup searching facilities and return
information on the first data item in the list. Subsequent calls will yield
information about each successive item in the Vgroup. If the end of the list is
reached, NXgetentry will return NX\_EOD, otherwise NX\_ERROR or NX\_OK
are returned. Information returned is different for each type of data. For
Vgroups the name and class of the Vgroup will be returned, and {\tt datatype}
will be 0. For scientific data sets, the name field will be the name, class
will be SDS and {\tt datatype} will denote the number type of the scientific
data set. For types not known to Nexus (but to HDF) both name and class will
be set to ``UNKNOWN''.
@D NXgetE @{
int
NXgetnextentry (NXhandle fid, NXname name, NXname class, int *datatype)
{
  pNexusFile pFile;
  int iRet, iStackPtr, iCurDir;
  int32 iTemp, iD1, iD2, iA;
  int32 iDim[MAX_VAR_DIMS];

  pFile = NXIassert (fid);

  iStackPtr = pFile->iStackPtr;
  iCurDir   = pFile->iStack[pFile->iStackPtr].iCurDir;

  /* first case to check for: no directory entry */
  if (pFile->iStack[pFile->iStackPtr].iRefDir == NULL) {
    iRet = NXIInitDir (pFile);
    if (iRet < 0) {
      NXIReportError (NXpData,
		      "ERROR: no memory to store directory info");
      return NX_EOD;
    }
  }
  /* Next case: end of directory */
  if (iCurDir >= pFile->iStack[pFile->iStackPtr].iNDir) {
    NXIKillDir (pFile);
    return NX_EOD;
  }
  /* Next case: we have data! supply it and increment counter */
  if (pFile->iCurrentVG == 0) {	/* root level */
    iTemp = Vattach (pFile->iVID,
		     pFile->iStack[iStackPtr].iRefDir[iCurDir], "r");
    if (iTemp < 0) {
      NXIReportError (NXpData, "ERROR: HDF cannot attach to Vgroup");
      return NX_ERROR;
    }
    Vgetname (iTemp, name);
    Vgetclass (iTemp, class);
    *datatype = DFTAG_VG;
    pFile->iStack[pFile->iStackPtr].iCurDir++;
    Vdetach (iTemp);
    return NX_OK;
  } else {			/* in Vgroup */
    if (pFile->iStack[iStackPtr].iTagDir[iCurDir] == DFTAG_VG) {/* Vgroup */
      iTemp = Vattach (pFile->iVID,
		       pFile->iStack[iStackPtr].iRefDir[iCurDir], "r");
      if (iTemp < 0) {
	NXIReportError (NXpData, "ERROR: HDF cannot attach to Vgroup");
	return NX_ERROR;
      }
      Vgetname (iTemp, name);
      Vgetclass (iTemp, class);
      *datatype = DFTAG_VG;
      pFile->iStack[pFile->iStackPtr].iCurDir++;
      Vdetach (iTemp);
      return NX_OK;
    } else if ((pFile->iStack[iStackPtr].iTagDir[iCurDir] == DFTAG_SDG) ||
	       (pFile->iStack[iStackPtr].iTagDir[iCurDir] == DFTAG_NDG)) {
      iTemp = SDreftoindex (pFile->iSID,
			    pFile->iStack[iStackPtr].iRefDir[iCurDir]);
      iTemp = SDselect (pFile->iSID, iTemp);
      SDgetinfo (iTemp, name, &iA, iDim, &iD1, &iD2);
      strcpy (class, "SDS");
      *datatype = iD1;
      SDendaccess (iTemp);
      pFile->iStack[pFile->iStackPtr].iCurDir++;
      return NX_OK;
    } else {			/* unidentified */
      strcpy (name, "UNKNOWN");
      strcpy (class, "UNKNOWN");
      pFile->iStack[pFile->iStackPtr].iCurDir++;
      return NX_OK;
    }
  }
  return NX_ERROR;		/* not reached */
}
@}
  
\subsubsection{NXgetnextattr}
NXgetnextattr works very much like NXgetnextentry with the exception that it
works on SDS attributes rather than Vgroup entries.
This function allows the names of attributes available to be scanned.
{\tt iLength} will be filled with the length of the attributes data in bytes.
{\tt iType} will be filled with the HDF type of the attribute.
Be warned: this routine returns global attributes when no SDS is currently 
open.
@d NXgetAt @{
/*-------------------------------------------------------------------------*/
NXstatus
NXgetnextattr (NXhandle fileid, NXname pName,
	       int *iLength, int *iType)
{
  pNexusFile pFile;
  int iRet;
  int32 iPType, iCount;

  pFile = NXIassert (fileid);

  /* first check if we have to start a new attribute search */
  if (pFile->iAtt.iNDir == 0) {
    iRet = NXIInitAttDir (pFile);
    if (iRet == NX_ERROR) {
      return NX_ERROR;
    }
  }
  /* are we done ? */
  if (pFile->iAtt.iCurDir >= pFile->iAtt.iNDir) {
    NXIKillAttDir (pFile);
    return NX_EOD;
  }
  /* well, there must be data to copy */
  if (pFile->iCurrentSDS == 0) {	/* global attribute */
    iRet = SDattrinfo (pFile->iSID, pFile->iAtt.iCurDir,
		       pName, &iPType, &iCount);
  } else {
    iRet = SDattrinfo (pFile->iCurrentSDS, pFile->iAtt.iCurDir,
		       pName, &iPType, &iCount);
  }
  if (iRet < 0) {
    NXIReportError (NXpData, "ERROR: HDF cannot read attribute info");
    return NX_ERROR;
  }
  *iLength = iCount * DFKNTsize (iPType);
  *iType = iPType;
  pFile->iAtt.iCurDir++;
  return NX_OK;
}
@}

\subsection{ Handling of linking and group hierarchy}
\label{ss:link}

@d DecLink @{
NXstatus    NXgetgroupID(NXhandle fileid, NXlink* sRes);
NXstatus    NXgetdataID(NXhandle fileid,  NXlink* sRes);
NXstatus    NXmakelink(NXhandle fileid, NXlink* link);
@}

\subsubsection{  NXgetgroupID}
NXgetgroupID retrieves the ID and tag of the currently open
Vgroup in an NXlink structure. In case of an error the iTag field of this
structure will contain NX\_ERROR.

@d NXgroupI @{
NXstatus
NXgetgroupID (NXhandle fileid, NXlink* sRes)
{
  pNexusFile pFile;

  pFile = NXIassert (fileid);

  if (pFile->iCurrentVG == 0) {
    sRes->iTag = NX_ERROR;
    return NX_ERROR;
  } else {
    sRes->iTag = DFTAG_VG;
    sRes->iRef = pFile->iCurrentVG;
    return NX_OK;
  }
  /* not reached */
  sRes->iTag = NX_ERROR;
  return NX_ERROR;
}
@}

\subsubsection{NXgetdataID}
NXgetdataID retrieves the tag and reference number of the current data
object. Returns NX\_ERROR if nothing is open.
 
@d NXdataI @{
NXstatus
NXgetdataID (NXhandle fid, NXlink* sRes)
{
  pNexusFile pFile;

  pFile = NXIassert (fid);

  if (pFile->iCurrentSDS == 0) {
    sRes->iTag = NX_ERROR;
    return NX_ERROR;
  } else {
    sRes->iTag = DFTAG_SDS;
    sRes->iRef = SDidtoref (pFile->iCurrentSDS);
    return NX_OK;
  }
  sRes->iTag = NX_ERROR;
  return NX_ERROR;                  /* not reached */
}
@}

\subsubsection{ NXlink}
NXlink links a Vgroup or SDS into a Vgroup.

@d NXlink @{
NXstatus
NXmakelink (NXhandle fid, NXlink* sLink)
{
  pNexusFile pFile;

  pFile = NXIassert (fid);

  if (pFile->iCurrentVG == 0) { /* root level, can not link here */
    return NX_ERROR;
  }
  if (sLink->iTag == DFTAG_VG) {
    Vinsert (pFile->iCurrentVG, sLink->iRef);
  } else {
    Vaddtagref (pFile->iCurrentVG, sLink->iTag, sLink->iRef);
  }
  return NX_OK;
}
@}

\subsection{Internal routines}
There are a couple of internal Nexus API routines which are declared
static and are not visible outside of the module. First a few
definitions:

@d IntDefs @{
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <mfhdf.h>
#include "napi.h"

/* FORTRAN helpers */
#define NXfopen 			MANGLE(nxifopen)
#define NXfclose			MANGLE(nxifclose)

#define NXMAXSTACK 50
#define NXSIGNATURE 959697

@} 
{\tt NXMAXSTACK} denotes the maximum depth of Vgroup stacking permissible.
If there are problems, feel free to increase this value and recompile.
50 is probably fairly generous.
{\tt NXSIGNATURE} is the signature value contained in each handle which the
Nexus API will check to determine whether the structure has been corrupted.
{\tt NXMAXNAME} is the number of characters permitted for names and Vgroup 
names.

The internal function {\tt NXIassert} converts the filehandle to a pointer
to the NexusFile datastructure. In the process, it checks the signature value
and throws an assertion if it does not match the present value.

\subsubsection{NX error handling}

As each piece of non trivial software the Nexus API needs a error handling
policy. The policy implemented is as follows: in the most serious case,
the NX routines terminate when an invalid NXhandle has been specified.
In any other case the NX routines are meant to complain, recover and present
an NX-ERROR return to the higher level code. That code is than responsible
to deal with the problem. The problem left is error reporting. This is done
throughout the code by a call to {\tt NXIReportError}. This function takes as 
its first parameter a pointer to void and as its next parameter the string 
with the complaint. The default implementation of {\tt NXIReportError} will 
print to stdout. However, there are environments where this strategy is not 
feasible because stdout is supported only in a very strange way. Think about
operating system like MS-Windows, MAC-OS (going to die anyway) or other
windowing systems. In order to cater for this there is an unofficial support
function, {\tt NXMSetError},  which allows to set both a pointer to a 
datastructure and a new function for error reporting. 

@d IntFunc @{
/*---------------------------------------------------------------------*/
static void NXNXNXReportError(void *pData, char *string)
{
  printf("%s \n",string);
}
/*---------------------------------------------------------------------*/
static void *NXpData = NULL;
static void (*NXIReportError)(void *pData, char *string) =
                NXNXNXReportError;
/*---------------------------------------------------------------------*/
void NXMSetError(void *pData, void (*NewError)(void *pD, char *text))
{
  NXpData = pData;
  NXIReportError = NewError;
}
@}

@d IntFunc @{
/*--------------------------------------------------------------------*/
static pNexusFile NXIassert(NXhandle fid)
{
  pNexusFile pRes;

  assert(fid);
  pRes = (pNexusFile)fid;
  assert(pRes->iNXID == NXSIGNATURE);
  return pRes;
}
@}

\subsubsection{ Object-finding Functions}

Routines for finding objects in an HDF file have to cope with two quirks in
the HDF interface. The first is that the root level is no Vgroup, which
implies that the root level is searched for objects using different routines
than are used to search for objects in a Vgroup. The last one uses, of course,
Vgroup interface routines.

Finding routines have  to cope with the plethora of different integer
ID's for any given HDF object. See \ref{ididid} for more details. 

{\tt NXIFindVgroup} searches the current Vgroup level in the file for the
occurence of a Vgroup with a specified name and of a specified class. If no
suitable Vgroup can be found, {\tt NXIFindVgroup} returns -1, else the ID of 
the Vgroup. {\tt NXIFindVgroup} must manage the multitude of different integer
ID's for any give Vgroup: the Vgroup ID, the reference number which can be
obtained by Vgettagref, and the tag denoting the object type.
@D IntFunc @{
/*----------------------------------------------------------------------*/
static int32
NXIFindVgroup (pNexusFile pFile, char *name, char *nxclass)
{
  int32 iNew, iRef, iTag;
  int iN, i;
  int32 *pArray = NULL;
  NXname pText;

  assert (pFile != NULL);

  if (pFile->iCurrentVG == 0) { /* root level */
    /* get the number and ID's of all lone Vgroups in the file */
    iN = Vlone (pFile->iVID, NULL, 0);
    if(iN == 0) {
       return NX_EOD;
    }
    pArray = (int32 *) malloc (iN * sizeof (int32));
    if (!pArray) {
      NXIReportError (NXpData, "ERROR: out of memory in NXIFindVgroup");
      return NX_EOD;
    }
    Vlone (pFile->iVID, pArray, iN);

    /* loop and check */
    for (i = 0; i < iN; i++) {
      iNew = Vattach (pFile->iVID, pArray[i], "r");
      Vgetname (iNew, pText);
      if (strcmp (pText, name) == 0) {
        Vgetclass (iNew, pText);
        if (strcmp (pText, nxclass) == 0) {
          /* found ! */
          Vdetach (iNew);
          iNew = pArray[i];
          free (pArray);
          return iNew;
        }
      }
      Vdetach (iNew);
    }
    /* nothing found */
    free (pArray);
    return NX_EOD;
  } else {                      /* case in Vgroup */
    iN = Vntagrefs (pFile->iCurrentVG);
    for (i = 0; i < iN; i++) {
      Vgettagref (pFile->iCurrentVG, i, &iTag, &iRef);
      if (iTag == DFTAG_VG) {
        iNew = Vattach (pFile->iVID, iRef, "r");
        Vgetname (iNew, pText);
        if (strcmp (pText, name) == 0) {
          Vgetclass (iNew, pText);
          if (strcmp (pText, nxclass) == 0) {
            /* found ! */
            Vdetach (iNew);
            return iRef;
          }
        }
        Vdetach (iNew);
      }
    }                           /* end for */
  }                             /* end else */
  /* not found */
  return NX_EOD;
}
@}

NXIFindSDS searches the current Vgroup level for an SDS  name. It returns
the reference ID of the SDS when found and -1 when no matching SDS can
be found.
@D IntFunc @{
static int32
NXIFindSDS (NXhandle fid, char *name)
{
  pNexusFile self;
  int32 iNew, iRet, iTag, iRef;
  int32 i, iN, iA, iD1, iD2;
  NXname pNam;
  int32 iDim[MAX_VAR_DIMS];

  self = NXIassert (fid);

  /* root level search */
  if (self->iCurrentVG == 0) {
    i = SDfileinfo (self->iSID, &iN, &iA);
    if (i < 0) {
      NXIReportError (NXpData, "ERROR: failure to read file information");
      return NX_EOD;
    }
    for (i = 0; i < iN; i++) {
      iNew = SDselect (self->iSID, i);
      SDgetinfo (iNew, pNam, &iA, iDim, &iD2, &iD2);
      if (strcmp (pNam, name) == 0) {
        iRet = SDidtoref (iNew);
        SDendaccess (iNew);
        return iRet;
      } else {
        SDendaccess (iNew);
      }
    }
    /* not found */
    return NX_EOD;
  }
  /* end root level */
  else {                        /* search in a Vgroup */
    iN = Vntagrefs (self->iCurrentVG);
    for (i = 0; i < iN; i++) {
      Vgettagref (self->iCurrentVG, i, &iTag, &iRef);
      if ((iTag == DFTAG_SDG) || (iTag == DFTAG_NDG)) {
        iNew = SDreftoindex (self->iSID, iRef);
        iNew = SDselect (self->iSID, iNew);
        SDgetinfo (iNew, pNam, &iA, iDim, &iD2, &iD2);
        if (strcmp (pNam, name) == 0) {
          SDendaccess (iNew);
          return iRef;
        }
        SDendaccess (iNew);
      }
    }                           /* end for */
  }                             /* end Vgroup */
  /* we get here, only if nothing found */
  return NX_EOD;
}
@}

\subsubsection{Helper routines for directory search}
NXIInitDir initializes the directory data fields in the Nexus File structure
for a subsequent directory request. Please note, that at root level only
Vgroups will be searched. When searching SDS's at root level, all SDS's in 
the whole file seem to be returned.
@D IntFunc @{
static int
NXIInitDir (pNexusFile self)
{
  int i;
  int32 iTag, iRef;
  int iStackPtr, iCurDir;

  iStackPtr = self->iStackPtr;
  if (self->iCurrentVG == 0 &&
      self->iStack[iStackPtr].iRefDir == NULL) {	/* root level */
    /* get the number and ID's of all lone Vgroups in the file */
    self->iStack[iStackPtr].iNDir = Vlone (self->iVID, NULL, 0);
    self->iStack[iStackPtr].iRefDir =
      (int32 *) malloc (self->iStack[iStackPtr].iNDir * sizeof (int32));
    if (!self->iStack[iStackPtr].iRefDir) {
      NXIReportError (NXpData, "ERROR: out of memory in NXIInitDir");
      return NX_EOD;
    }
    Vlone (self->iVID,
	   self->iStack[self->iStackPtr].iRefDir,
	   self->iStack[self->iStackPtr].iNDir);
  } else {
    /* Vgroup level */
    self->iStack[iStackPtr].iNDir = Vntagrefs (self->iCurrentVG);
    self->iStack[iStackPtr].iRefDir =
      (int32 *) malloc (self->iStack[iStackPtr].iNDir * sizeof (int32));
    self->iStack[iStackPtr].iTagDir =
      (int32 *) malloc (self->iStack[iStackPtr].iNDir * sizeof (int32));
    if ((!self->iStack[iStackPtr].iRefDir) ||
	(!self->iStack[iStackPtr].iTagDir)) {
      NXIReportError (NXpData, "ERROR: out of memory in NXIInitDir");
      return NX_EOD;
    }
    for (i = 0; i < self->iStack[self->iStackPtr].iNDir; i++) {
      Vgettagref (self->iCurrentVG, i, &iTag, &iRef);
      self->iStack[iStackPtr].iRefDir[i] = iRef;
      self->iStack[iStackPtr].iTagDir[i] = iTag;
    }
  }
  self->iStack[iStackPtr].iCurDir = 0;
  return 1;
}
@}


NXIKillDir removes all data associated with a directory search from 
memory.
@d IntFunc @{
static void
NXIKillDir (pNexusFile self)
{
  if (self->iStack[self->iStackPtr].iRefDir) {
    free (self->iStack[self->iStackPtr].iRefDir);
    self->iStack[self->iStackPtr].iRefDir = NULL;
  }
  if (self->iStack[self->iStackPtr].iTagDir) {
    free (self->iStack[self->iStackPtr].iTagDir);
    self->iStack[self->iStackPtr].iTagDir = NULL;
  }
  self->iStack[self->iStackPtr].iCurDir = 0;
  self->iStack[self->iStackPtr].iNDir = 0;
}
@}

NXIInitAttDir will initialize the counters for reading either SDS or global
attribute data. 
@d IntFunc @{
/*-------------------------------------------------------------------------*/
static int
NXIInitAttDir (pNexusFile pFile)
{
  int iRet;
  int32 iData, iAtt, iRank, iType;
  int32 iDim[MAX_VAR_DIMS];
  NXname pNam;

  pFile->iAtt.iCurDir = 0;
  if (pFile->iCurrentSDS != 0) {	/* SDS level */
    iRet = SDgetinfo (pFile->iCurrentSDS, pNam, &iRank, iDim, &iType,
		      &iAtt);
  } else {			/* global level */
    iRet = SDfileinfo (pFile->iSID, &iData, &iAtt);
  }
  if (iRet < 0) {
    NXIReportError (NXpData, "ERROR: HDF cannot read attribute numbers");
    pFile->iAtt.iNDir = 0;
    return NX_ERROR;
  }
  pFile->iAtt.iNDir = iAtt;
  return NX_OK;
}
@}

NXIKillAttDir removes data associated with a search for SDS attributes.
@d IntFunc @{
static void
NXIKillAttDir (pNexusFile self)
{
  if (self->iAtt.iRefDir) {
    free (self->iAtt.iRefDir);
    self->iAtt.iRefDir = NULL;
  }
  if (self->iAtt.iTagDir) {
    free (self->iAtt.iTagDir);
    self->iAtt.iTagDir = NULL;
  }
  self->iAtt.iCurDir = 0;
  self->iAtt.iNDir = 0;
}
@}

\subsection{The FORTRAN Interface}

FORTRAN is still widely used in neutron and X-ray scattering
facilities for data analysis and visualization. The considerable
volume of legacy code, the efficiency of the language for numerical
computation, and the development of Fortan 90/95 mean that we will
need to provide a FORTRAN version of the NeXus API for the forseeable
future. Because the FORTRAN API is built upon the C API, certain changes
must be made in the latter to allow the two portions to link together.

Here are a list of type conversions when using the Fortran
interface.

\begin{tabular}{ll}
\multicolumn{1}{c}{C} & \multicolumn{1}{c}{FORTRAN} \\ \hline
 {\tt int a, int* a} & {\tt INTEGER A } \\
 {\tt char* a} & {\tt CHARACTER*(*) A } \\
 {\tt NXhandle a, NXhandle* a} & {\tt INTEGER A(NXHANDLESIZE) } \\
 {\tt NXstatus} &  {\tt INTEGER } \\
 {\tt int[] a} &  {\tt INTEGER A(*) } \\
 {\tt void* a} &  {\tt REAL A(*)} or {\tt DOUBLE A(*)} or {\tt INTEGER A(*)} \\
 {\tt NXlink a, NXlink* a} &  {\tt INTEGER A(NXLINKSIZE) }
\end{tabular}


Because Unix FORTRAN compilers typically generate symbols which differ from
their ``C'' counterparts by an appended ``\_'', some preprocessing is in order
to assure that FORTRAN subroutines calling ``C'' functions will actually refer
to the correct symbol.
@D FORTRANdefs @{
/* 
 *  Now, we have the interface visible from FORTRAN and C. On UNIX system
 *  FORTRAN routines usually get an extra trailing "_"
 *  Do not mangle using "_" as f2c mangles _ containing and non _
 *  containing names differently
 *  We must also lowercase anything that is called from FORTRAN
 *  else we can't link
 */

#define CONCAT(__a,__b)	__a##__b	/* token concatenation */

/* 
 * Define a macro for FORTRAN name mangling _ often we have to add an "_"
 */
#if defined(__VMS)
#define MANGLE(__arg)	__arg
#elif defined(__unix__)
#define MANGLE(__arg)	CONCAT(__arg,_)
#else
#error Cannot compile - unknown operating system
#endif

#define NXopen 			MANGLE(nxiopen)
#define NXclose 		MANGLE(nxiclose)
#define NXmakegroup 		MANGLE(nximakegroup)
#define NXopengroup 		MANGLE(nxiopengroup)
#define NXclosegroup 		MANGLE(nxiclosegroup)
#define NXmakedata 		MANGLE(nximakedata)
#define NXopendata 		MANGLE(nxiopendata)
#define NXclosedata 		MANGLE(nxiclosedata)
#define NXgetdata 		MANGLE(nxigetdata)
#define NXgetslab 		MANGLE(nxigetslab)
#define NXgetattr 		MANGLE(nxigetattr)
#define NXgetdim 		MANGLE(nxigetdim)
#define NXputdata 		MANGLE(nxiputdata)
#define NXputslab 		MANGLE(nxiputslab)
#define NXputattr 		MANGLE(nxiputattr)
#define NXputdim 		MANGLE(nxiputdim)
#define NXgetinfo 		MANGLE(nxigetinfo)
#define NXgetnextentry 		MANGLE(nxigetnextentry)
#define NXgetnextattr 		MANGLE(nxigetnextattr)
#define NXgetgroupID 		MANGLE(nxigetgroupid)
#define NXgetdataID 		MANGLE(nxigetdataid)
#define NXmakelink 		MANGLE(nximakelink)
#define NXmalloc 		MANGLE(nximalloc)
#define NXfree 			MANGLE(nxifree)
@}

Because FORTRAN does not use a preprocessor {\em per se}, certain tokens
intended for the C preprocessor must be explicitly defined for the FORTRAN
interface.

@D FORTRANincs @{
C *** NXaccess enum - access modes for NXopen
      INTEGER NXACC_READ,NXACC_RDWR,NXACC_CREATE
      PARAMETER(NXACC_READ=1,NXACC_RDWR=3,NXACC_CREATE=7)
C *** NXHANDLESIZE should be the size of an INTEGER*4 array that is (at least)
C *** large enough to hold an NXhandle structure
      INTEGER NXHANDLESIZE
      PARAMETER(NXHANDLESIZE=600)
C *** NXLINKSIZE is (at least) the size of an INTEGER*4 array that can hold
C *** an NXlink structure: we'll assume 64bit alignment of structure members for 
safety
      INTEGER NXLINKSIZE
      PARAMETER(NXLINKSIZE=4)
C *** Possible NXstatus values - these are returned by all NX routines
      INTEGER NX_OK,NX_ERROR,NX_EOD
      PARAMETER(NX_OK=1,NX_ERROR=0,NX_EOD=-1)
C *** HDF datatypes used by Nexus - see hntdefs.h in HDF distribution
      INTEGER DFNT_FLOAT32,DFNT_FLOAT64,DFNT_INT8,DFNT_UINT8,DFNT_INT16,
     +        DFNT_UINT16,DFNT_INT32,DFNT_UINT32
      PARAMETER(DFNT_FLOAT32=5,DFNT_FLOAT64=6,DFNT_INT8=20,
     +          DFNT_UINT8=21,DFNT_INT16=22,DFNT_UINT16=23,
     +          DFNT_INT32=24,DFNT_UINT32=25)
      INTEGER NXOPEN, NXCLOSE, NXMAKEGROUP, NXOPENGROUP, NXCLOSEGROUP,
     +       NXMAKEDATA, NXOPENDATA, NXCLOSEDATA, NXGETDATA, NXGETSLAB,
     +       NXGETATTR, NXGETDIM, NXPUTDATA, NXPUTSLAB, NXPUTATTR,
     +       NXPUTDIM, NXGETINFO, NXGETNEXTENTRY, NXGETNEXTATTR,
     +       NXGETGROUPID, NXMAKELINK 
      EXTERNAL NXOPEN, NXCLOSE, NXMAKEGROUP, NXOPENGROUP, NXCLOSEGROUP,
     +       NXMAKEDATA, NXOPENDATA, NXCLOSEDATA, NXGETDATA, NXGETSLAB,
     +       NXGETATTR, NXGETDIM, NXPUTDATA, NXPUTSLAB, NXPUTATTR,
     +       NXPUTDIM, NXGETINFO, NXGETNEXTENTRY, NXGETNEXTATTR,
     +       NXGETGROUPID, NXMAKELINK 
@}

The following functions serve to bridge the gap between C-style null-terminated
strings and their FORTRAN counterparts.


@D FORTRANstring @{
C *** Return length of a string, ignoring trailing blanks
      INTEGER FUNCTION TRUELEN(STRING)
      CHARACTER*(*) STRING
      DO TRUELEN=LEN(STRING),1,-1
          IF (STRING(TRUELEN:TRUELEN) .NE. ' ') RETURN
      ENDDO
      TRUELEN = 0
      END

C *** Convert FORTRAN string STRING into NULL terminated C string ISTRING
      SUBROUTINE EXTRACT_STRING(ISTRING, LENMAX, STRING)
      BYTE ISTRING(*)
      CHARACTER*(*) STRING
      INTEGER I,ILEN,TRUELEN,LENMAX
      EXTERNAL TRUELEN
      ILEN = TRUELEN(STRING)
      IF (ILEN .GE. LENMAX) THEN
          WRITE(6,9000) LENMAX, ILEN+1
          RETURN
      ENDIF
      DO I=1,ILEN
          ISTRING(I) = ICHAR(STRING(I:I))
      ENDDO
      ISTRING(ILEN+1) = 0
      RETURN
 9000 FORMAT('NAPIF: String too long - buffer needs increasing from ',
     +        i4,' to at least ',i4)
      END

C *** Convert NULL terminated C string ISTRING to FORTRAN string STRING
      SUBROUTINE REPLACE_STRING(STRING, ISTRING)
      BYTE ISTRING(*)
      CHARACTER*(*) STRING
      INTEGER I
      STRING = ' '
      DO I=1,LEN(STRING)
          IF (ISTRING(I) .EQ. 0) RETURN
          STRING(I:I) = CHAR(ISTRING(I))
      ENDDO
      WRITE(6,9010) LEN(STRING) 
      RETURN
 9010 FORMAT('NAPIF: String truncated - buffer needs to be > ', I4)
      END
@}

The following functions serve as wrappers for the C API. In FORTRAN
all variables are passed by reference (i.e. as pointers), so in the
rare few cases when we need to pass an item by value to the C
interface we use the non-standard \verb+%VAL()+; so far all compilers we have
come across support this non-standard construct. C int and float types
map onto their FORTRAN counterparts transparently, but \verb+char*+ and
\verb+CHARACTER*(*)+ are not compatible and must be treated specially.



@D FORTRANfuncs @{
C *** Wrapper routines for NAPI interface
      INTEGER FUNCTION NXOPEN(FILENAME, ACCESS_METHOD, FILEID)
      CHARACTER*(*) FILENAME
      BYTE IFILENAME(256)
      INTEGER ACCESS_METHOD
      INTEGER FILEID(*),NXIFOPEN
      EXTERNAL NXIFOPEN
      CALL EXTRACT_STRING(IFILENAME, 256, FILENAME)
      NXOPEN = NXIFOPEN(IFILENAME, ACCESS_METHOD, FILEID)
      END

      INTEGER FUNCTION NXCLOSE(FILEID)
      INTEGER FILEID(*),NXIFCLOSE
      EXTERNAL NXIFCLOSE
      NXCLOSE = NXIFCLOSE(FILEID)
      END

      INTEGER FUNCTION NXMAKEGROUP(FILEID, VGROUP, NXCLASS)
      INTEGER FILEID(*),NXIMAKEGROUP
      CHARACTER*(*) VGROUP, NXCLASS
      BYTE IVGROUP(256), INXCLASS(256)
      EXTERNAL NXIMAKEGROUP
      CALL EXTRACT_STRING(IVGROUP, 256, VGROUP)
      CALL EXTRACT_STRING(INXCLASS, 256, NXCLASS)
      NXMAKEGROUP = NXIMAKEGROUP(FILEID, IVGROUP, INXCLASS)
      END

      INTEGER FUNCTION NXOPENGROUP(FILEID, VGROUP, NXCLASS)
      INTEGER FILEID(*),NXIOPENGROUP
      CHARACTER*(*) VGROUP, NXCLASS
      BYTE IVGROUP(256), INXCLASS(256)
      EXTERNAL NXIOPENGROUP
      CALL EXTRACT_STRING(IVGROUP, 256, VGROUP)
      CALL EXTRACT_STRING(INXCLASS, 256, NXCLASS)
      NXOPENGROUP = NXIOPENGROUP(FILEID, IVGROUP, INXCLASS)
      END

      INTEGER FUNCTION NXCLOSEGROUP(FILEID)
      INTEGER FILEID(*),NXICLOSEGROUP
      EXTERNAL NXICLOSEGROUP
      NXCLOSEGROUP = NXICLOSEGROUP(FILEID)
      END

      INTEGER FUNCTION NXMAKEDATA(FILEID, LABEL, DATATYPE, RANK, DIM)  
      INTEGER FILEID(*), DATATYPE, RANK, DIM,NXIMAKEDATA
      CHARACTER*(*) LABEL
      BYTE ILABEL(256)
      EXTERNAL NXIMAKEDATA
      CALL EXTRACT_STRING(ILABEL, 256, LABEL)
      NXMAKEDATA = NXIMAKEDATA(FILEID, ILABEL, %VAL(DATATYPE), 
     +                         %VAL(RANK), DIM)
      END

      INTEGER FUNCTION NXOPENDATA(FILEID, LABEL)
      INTEGER FILEID(*),NXIOPENDATA
      CHARACTER*(*) LABEL
      BYTE ILABEL(256)
      EXTERNAL NXIOPENDATA
      CALL EXTRACT_STRING(ILABEL, 256, LABEL)
      NXOPENDATA = NXIOPENDATA(FILEID, ILABEL)
      END

      INTEGER FUNCTION NXCLOSEDATA(FILEID)
      INTEGER FILEID(*),NXICLOSEDATA
      EXTERNAL NXICLOSEDATA
      NXCLOSEDATA = NXICLOSEDATA(FILEID)
      END

      INTEGER FUNCTION NXGETDATA(FILEID, DATA)
      INTEGER FILEID(*), DATA(*),NXIGETDATA
      EXTERNAL NXIGETDATA
      NXGETDATA = NXIGETDATA(FILEID, DATA)
      END

      INTEGER FUNCTION NXGETSLAB(FILEID, DATA, START, SIZE)
      INTEGER FILEID(*), DATA(*), START(*), SIZE(*)
      INTEGER NXIGETSLAB
      EXTERNAL NXIGETSLAB
      NXGETSLAB = NXIGETSLAB(FILEID, DATA, START, SIZE)
      END
      
      INTEGER FUNCTION NXGETATTR(FILEID, NAME, DATA, DATALEN, TYPE)
      INTEGER FILEID(*), DATALEN,MAX_DATALEN,NX_ERROR,TYPE
      PARAMETER(MAX_DATALEN=1024,NX_ERROR=0)
      CHARACTER*(*) NAME, DATA     
      BYTE INAME(256), IDATA(MAX_DATALEN)
      INTEGER NXIGETATTR
      EXTERNAL NXIGETATTR
      IF (DATALEN .GE. MAX_DATALEN) THEN
          WRITE(6,9020) DATALEN, MAX_DATALEN
          NXGETATTR=NX_ERROR
          RETURN
      ENDIF
      CALL EXTRACT_STRING(INAME, 256, NAME)
      NXGETATTR = NXIGETATTR(FILEID, INAME, IDATA, DATALEN, TYPE)
      CALL REPLACE_STRING(DATA, IDATA)
      RETURN
 9020 FORMAT('NXgetattr: asked for attribute size ', I4, 
     +       ' with buffer size only ', I4)
      END

      INTEGER FUNCTION NXPUTDATA(FILEID, DATA)
      INTEGER FILEID(*), DATA(*), NXIPUTDATA
      EXTERNAL NXIPUTDATA
      NXPUTDATA = NXIPUTDATA(FILEID, DATA)
      END

      INTEGER FUNCTION NXPUTSLAB(FILEID, DATA, START, SIZE)
      INTEGER FILEID(*), DATA(*), START(*), SIZE(*)
      INTEGER NXIPUTSLAB
      EXTERNAL NXIPUTSLAB
      NXPUTSLAB = NXIPUTSLAB(FILEID, DATA, START, SIZE)
      END

      INTEGER FUNCTION NXPUTATTR(FILEID, NAME, DATA, DATALEN, TYPE)
      INTEGER FILEID(*), DATALEN, TYPE
      CHARACTER*(*) NAME, DATA
      BYTE INAME(256), IDATA(1024)
      INTEGER NXIPUTATTR
      EXTERNAL NXIPUTATTR
      CALL EXTRACT_STRING(INAME, 256, NAME)
      CALL EXTRACT_STRING(IDATA, 1024, DATA)
      NXPUTATTR = NXIPUTATTR(FILEID, INAME, IDATA, 
     +                       %VAL(DATALEN), %VAL(TYPE))
      END

      INTEGER FUNCTION NXGETINFO(FILEID, RANK, DIM, DATATYPE)
      INTEGER FILEID(*), RANK, DIM(*), DATATYPE
      INTEGER NXIGETINFO
      EXTERNAL NXIGETINFO
      NXGETINFO = NXIGETINFO(FILEID, RANK, DIM, DATATYPE)
      END

      INTEGER FUNCTION NXGETNEXTENTRY(FILEID, NAME, CLASS, DATATYPE)
      INTEGER FILEID(*), DATATYPE
      CHARACTER*(*) NAME, CLASS
      BYTE INAME(256), ICLASS(256)
      INTEGER NXIGETNEXTENTRY
      EXTERNAL NXIGETNEXTENTRY
      CALL EXTRACT_STRING(INAME, 256, NAME)
      CALL EXTRACT_STRING(ICLASS, 256, CLASS)
      NXGETNEXTENTRY = NXIGETNEXTENTRY(FILEID, INAME, ICLASS, DATATYPE)
      CALL REPLACE_STRING(NAME, INAME)
      CALL REPLACE_STRING(CLASS, ICLASS)
      END

      INTEGER FUNCTION NXGETNEXTATTR(FILEID, PNAME, ILENGTH, ITYPE)
      INTEGER FILEID(*), ILENGTH, ITYPE, NXIGETNEXTATTR
      CHARACTER*(*) PNAME
      BYTE IPNAME(1024)
      EXTERNAL NXIGETNEXTATTR
      NXGETNEXTATTR = NXIGETNEXTATTR(FILEID, IPNAME, ILENGTH, ITYPE)
      CALL REPLACE_STRING(PNAME, IPNAME)
      END

      INTEGER FUNCTION NXGETGROUPID(FILEID, LINK)
      INTEGER FILEID(*), LINK(*), NXIGETGROUPID
      EXTERNAL NXIGETGROUPID
      NXGETGROUPID = NXIGETGROUPID(FILEID, LINK)
      END

      INTEGER FUNCTION NXGETDATAID(FILEID, LINK)
      INTEGER FILEID(*), LINK(*), NXIGETDATAID
      EXTERNAL NXIGETDATAID
      NXGETDATAID = NXIGETDATAID(FILEID, LINK)
      END

      INTEGER FUNCTION NXMAKELINK(FILEID, LINK)
      INTEGER FILEID(*), LINK(*), NXIMAKELINK
      EXTERNAL NXIMAKELINK
      NXMAKELINK = NXIMAKELINK(FILEID, LINK)
      END
@}

\section{Example Application}
The remaining scraps of code illustrate an example application which makes
use of the API described above. {\tt nxdump} reads the contents of a 
NEXUS file and displays them in indented form according to their position
in the Vgroup hierarchy. It is invoked simply as:
\begin{center}
{\tt nxdump filename}.
\end{center}
Vgroups are displayed as ``V {\tt class}$-\gt${\tt name},'' scientific data
sets are displayed as ``SD {\tt name} ({\tt datatype}, {\tt rank}, 
{\tt dimensions}),'' and attributes are displayed as 
``{\tt parent SDS}:{\tt AttributeName}={\tt AttributeValue}.'' Global
attributes are displayed without a parent SDS name.

The structure of this example is simple. First the file is opened and the
global attributes are displayed through a call to {\tt dumpAttr}. Then
the contents of all the Vgroups are displayed through recursive calls to
{\tt dumpVG}.

@d dumpmain @{
void
main(int argc, char ** argv) {
  char name[VGNAMELENMAX], class[VGNAMELENMAX], indent[80] = "";
  int datatype;
  char filename[80];
  NXhandle NXdat;
  NXstatus NXstat;

  if (argc < 2) {
    fprintf(stderr,"Usage: nxdump <filename>\n");
    exit(1);
  }
  strcpy(filename,argv[1]);

  if (NXopen(filename,DFACC_RDWR,&NXdat) != NX_OK) {
    fprintf(stderr,"Unable to access file %s! Exiting...\n",filename);
    exit(1);
  }

  /* Read and display global attributes */
  dumpAttr(NXdat, "", indent);

  /* Read anything else and recurse through V groups */
  dumpVG(NXdat, indent);

  NXclose(NXdat);
}
@}

{\tt dumpVG} displays the contents of the current Vgroup. Through calls to
{\tt NXgetnextentry} it retrieves a list of objects contained in the group
and increments its way through that list, displaying SDS information if the
object is an SDS, or calling itself to display the contents of any child
Vgroups.

@d dumpvg @{
int dumpVG(NXhandle NXdat, char * indent) {
  char name[VGNAMELENMAX],class[VGNAMELENMAX],new_indent[80];
  int datatype;
  NXstatus NXstat;

  strcpy(new_indent,indent); strcat(new_indent,INDENT_AMOUNT);

  while((NXstat = NXgetnextentry(NXdat, name, class, &datatype)) != NX_EOD){
    switch(datatype){
    case DFTAG_VG:
      /* Do not display HDF internal Vgroups */
      if (!((strcmp(class,"Dim0.0")==0) || (strcmp(class,"CDF0.0") ==0))) {
	printf("%sV %s->%s\n",indent,class,name);
	if ((NXstat = NXopengroup(NXdat,name,class)) == NX_ERROR) {  
	  fprintf(stderr,"Error opening V group %s!",name);  
	  return -1;  
	}  
	dumpVG(NXdat,new_indent); 
	NXstat = NXclosegroup(NXdat); 
	printf("%sv %s->%s\n",indent,class,name);
      }
	break;
    default:
      dumpSDS(NXdat,name,new_indent);
    }
  }
  return 0;
}
@}

{\tt dumpSDS} retrieves information about the SDS associated with {\tt name},
printing its datatype, rank, and dimensions (but not its data).

@d dumpsds @{
int dumpSDS(NXhandle NXdat, char * name, char * indent) {
  char new_indent[80];
  int rank, dims[MAX_VAR_DIMS], datatype,i;
  NXstatus NXstat;

  strcpy(new_indent,indent); strcat(new_indent,INDENT_AMOUNT);
  NXstat = NXopendata(NXdat, name);
  NXstat = NXgetinfo(NXdat, &rank, dims, &datatype);

  printf("%sSD %s (datatype=%s, rank=%d, dimensions=%d",new_indent, 
	 name, type2s(datatype), rank, dims[0]);
  if (rank > 1) {
    for (i=1;i<rank;i++) printf("X%d",dims[i]);
  }
  printf(")\n");

  NXstat = dumpAttr(NXdat, name, new_indent); 
  NXstat = NXclosedata(NXdat);
  return 0;
}
@}

{\tt dumpAttr} displays the attributes associated with the SDS currently
open. If no SDS is open, it displays global attributes.

@d dumpattr @{
int dumpAttr(NXhandle NXdat, char * parent, char * indent) {
  int length, itype;
  char name[VGNAMELENMAX], *data;
  NXstatus NXstat;

  while((NXstat = NXgetnextattr(NXdat, name, &length, &itype)) != NX_EOD) {
    data = (char *) malloc(length + 1);
    NXstat = NXgetattr(NXdat, name, data, length);
    data[length] = 0;
    printf("%s%s:%s=%s\n",indent,parent,name,data);
  }
  return 0;
}
@}

The utility function {\tt type2s} returns the name associated with the
supplied data type. For brevity, only the data types supported by the
NEXUS API are listed.

@d dumptype @{
char *
type2s(uint type){
  static char unknownType[40];

  switch (type){
  case DFNT_NONE:       return "DFNT_NONE"; 
  case DFNT_FLOAT32:    return "DFNT_FLOAT32"; 
  case DFNT_FLOAT64:    return "DFNT_FLOAT64"; 
  case DFNT_INT8:       return "DFNT_INT8"; 
  case DFNT_UINT8:      return "DFNT_UINT8"; 
  case DFNT_INT16:      return "DFNT_INT16"; 
  case DFNT_UINT16:     return "DFNT_UINT16"; 
  case DFNT_INT32:      return "DFNT_INT32"; 
  case DFNT_UINT32:     return "DFNT_UINT32"; 
  }

  return "Unsupported!";
}
@}




@O napi.h @{
/*---------------------------------------------------------------------------
                            Nexus API header file

   copyleft: Mark Koennecke, March 1997 at LNS,PSI, Switzerland
             Przemek Klosowski, U. of Maryland & NIST, USA       

   No warranties of any kind taken.
----------------------------------------------------------------------------*/
#ifndef NEXUSAPI
#define NEXUSAPI

#define NEXUS_VERSION	"0.9"		/* major.minor */

#include <mfhdf.h>

typedef enum {NXACC_READ = DFACC_READ, 
              NXACC_RDWR = DFACC_RDWR, 
              NXACC_CREATE = DFACC_CREATE } NXaccess;

typedef void* NXhandle;		/* really a pointer to a NexusFile structure */
typedef int NXstatus;
typedef char NXname[VGNAMELENMAX];
@< datal @>

#define NX_OK 1
#define NX_ERROR 0
#define NX_EOD -1
/*-------------------------------------------------------------------------
                HDF Datatype values for datatype parameters 
                       in the Nexus API

  DFNT_FLOAT32     32 bit float
  DFNT_FLOAT64     64 nit float == double
  DFNT_INT8        8 bit integer ==char, byte
  DFNT_UINT8       8 bit unsigned integer
  DFNT_INT16       16 bit integer
  DFNT_UINT16      16 bit unsigned integer
  DFNT_INT32       32 bit integer
  DFNT_UINT32      32 bit unsigned integer

  This list is a edited version of the one found in the HDF header file
  htndefs.h. That source will always be the real reference, this is
  documented here for your convenience.
--------------------------------------------------------------------------*/ 

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

  @< FORTRANdefs @>
/* 
 * Standard interface 
 */
  @< Dec1 @>
  @< DatDec @>
  @< DecInfo @>
  @< DecLink @>
/*-----------------------------------------------------------------------
    A non Nexus standard function to set an error handler 
*/
  void NXMSetError(void *pData, void (*ErrFunc)(void *pD, char *text));

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* NEXUSAPI */


@}

@O napi.c @{
/*---------------------------------------------------------------------------
  Neutron & X-ray Common Data Format
  
  Application Program Interface Routines
  
  Copyright (C) 1997 Mark Koennecke, Przemek Klosowski
  
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

  No warranties of any kind, whether explicit or implied, taken.
  Distributed under the GNU copyleft license as documented elsewhere.

  Mark Koennecke                     Przemek Klosowski
  Labor fuer Neutronenstreuung       U. of Maryland & NIST 
  Paul Scherrer Institut             Przemek.Klosowski@@nist.gov
  CH-5232 Villigen-PSI
  Switzerland                        Nick Maliszewskyj
  Mark.Koennecke@@psi.ch              NIST Center for Neutron Research 
                                     Nick.Maliszewskyj@@nist.gov

  Version   Date       Name    Comments
  -------   ----       ----    --------
    0.8    Mar 1997    MK/PK   First beta version of NeXus API
    0.9    Jun 1997     NM     Some changes made to allow for directory 
                               searches in file reading.
    0.95   Aug 1997    FAA     Merge in new interface definitions and 
                               modifications for C++ and FORTRAN calling
----------------------------------------------------------------------------*/

/* Revision interted by CVS */
static const char* rscid = "$Id: napi.w,v 1.9 1997/09/18 19:53:46 nickm Exp nickm 
$";

@< IntDefs @>
@< data    @>
@< IntFunc @>
@< NXopen  @>
@< NXclose @>
@< NXmakeG @>
@< NXopenG @>
@< NXcloseG @>
@< NXmakeD @>
@< NXopenD @>
@< NXcloseD @>
@< NXgetD @>
@< NXgetS @>
@< NXgetA @>
@< NXputD @>
@< NXputS @>
@< NXputA @>
@< NXgetI @>
@< NXgetE @>
@< NXgetAt @>
@< NXgroupI @>
@< NXdataI @>
@< NXlink @>

@}

@O napif.f -i @{
C----------------------------------------------------------------------------
C NeXus - Neutron & X-ray Common Data Format
C  
C API Fortran Interface
C  
C $Id: napi.w,v 1.9 1997/09/18 19:53:46 nickm Exp nickm $
C
C Copyright (C) 1997, Freddie Akeroyd
C                     ISIS Facility, Rutherford Appleton Laboratory
C  
C See NAPI.C for details
C
C 97/7/30 - Initial Release
C 97/7/31 - Correct NXPUTATTR/NXGETATTR and make 'implicit none' clean
C 97/8/7  - Update interface
C----------------------------------------------------------------------------

@< FORTRANstring @>
@< FORTRANfuncs @>
@}

@O napif.inc -i @{
C----------------------------------------------------------------------------
C NeXus - Neutron & X-ray Common Data Format
C  
C API Fortran Interface Definitions
C  
C $Id: napi.w,v 1.9 1997/09/18 19:53:46 nickm Exp nickm $
C
C Copyright (C) 1997, Freddie Akeroyd
C                     ISIS Facility, Rutherford Appleton Laboratory
C  
C See NAPI.C for details
C
C 97/7/30 - Initial Release
C 97/7/31 - Correct NXPUTATTR/NXGETATTR and make 'implicit none' clean
C 97/8/7  - Update interface
C----------------------------------------------------------------------------

@< FORTRANincs @>
@}

@O nxdump.c @{
/*
 *  NXdump: Dump contents of HDF (hierarchical data format) 
 *          file using the NEXUS API
 *
 *  Author: N. Maliszewskyj, NIST Center for Neutron Research, June 1997
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <mfhdf.h>
#include "napi.h"

#define INDENT_AMOUNT " "
@< dumptype @>
@< dumpattr @>
@< dumpsds  @>
@< dumpvg   @>
@< dumpmain @>
@}

@O Makefile -t @{
CC = gcc
CFLAGS = -g -I/usr/local/include
HLIBS  = -lmfhdf -ldf -lz
OBJ   = nxdump.o napi.o
 
nxdump: $(OBJ)
	$(CC) $(CFLAGS) -o $@@ $(OBJ) $(HLIBS)

.c.o:
	$(CC) $(CFLAGS) -c $*.c

# Fake out make so that it won't second guess us.
.w.c:
	true

clean:
	rm -f *.o

@}

\end{document}




More information about the NeXus-developers mailing list