/*
*class++
*  Name:
*     Channel

*  Purpose:
*     Basic (textual) I/O channel.

*  Constructor Function:
c     astChannel
f     AST_CHANNEL

*  Description:
*     The Channel class implements low-level input/output for the AST
*     library.  Writing an Object to a Channel will generate a textual
*     representation of that Object, and reading from a Channel will
*     create a new Object from its textual representation.
*
*     Normally, when you use a Channel, you should provide "source"
c     and "sink" functions which connect it to an external data store
f     and "sink" routines which connect it to an external data store
*     by reading and writing the resulting text. By default, however,
*     a Channel will read from standard input and write to standard
*     output. Alternatively, a Channel can be told to read or write from
*     specific text files using the SinkFile and SourceFile attributes,
*     in which case no sink or source function need be supplied.

*  Inheritance:
*     The Channel class inherits from the Object class.

*  Attributes:
*     In addition to those attributes common to all Objects, every
*     Channel also has the following attributes:
*
*     - Comment: Include textual comments in output?
*     - Full: Set level of output detail
*     - Indent: Indentation increment between objects
*     - ReportLevel: Selects the level of error reporting
*     - SinkFile: The path to a file to which the Channel should write
*     - Skip: Skip irrelevant data?
*     - SourceFile: The path to a file from which the Channel should read
*     - Strict: Generate errors instead of warnings?

*  Functions:
c     In addition to those functions applicable to all Objects, the
c     following functions may also be applied to all Channels:
f     In addition to those routines applicable to all Objects, the
f     following routines may also be applied to all Channels:
*
c     - astWarnings: Return warnings from the previous read or write
c     - astPutChannelData: Store data to pass to source or sink functions
c     - astRead: Read an Object from a Channel
c     - astWrite: Write an Object to a Channel
f     - AST_WARNINGS: Return warnings from the previous read or write
f     - AST_READ: Read an Object from a Channel
f     - AST_WRITE: Write an Object to a Channel

*  Copyright:
*     Copyright (C) 1997-2006 Council for the Central Laboratory of the
*     Copyright (C) 2009 Science & Technology Facilities Council.
*     All Rights Reserved.
*     Research Councils

*  Licence:
*     This program is free software: you can redistribute it and/or
*     modify it under the terms of the GNU Lesser General Public
*     License as published by the Free Software Foundation, either
*     version 3 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 Lesser General Public License for more details.
*
*     You should have received a copy of the GNU Lesser General
*     License along with this program.  If not, see
*     <http://www.gnu.org/licenses/>.

*  Authors:
*     RFWS: R.F. Warren-Smith (Starlink)

*  History:
*     12-AUG-1996 (RFWS):
*        Original version.
*     6-SEP-1996:
*        Finished initial implementation.
*     11-DEC-1996 (RFWS):
*        Added support for foreign language source and sink functions.
*     28-APR-1997 (RFWS):
*        Prevent "-0" being written (use "0" instead).
*     27-NOV-2002 (DSB):
*        Added astWriteInvocations.
*     8-JAN-2003 (DSB):
*        - Changed private InitVtab method to protected astInitChannelVtab
*        method.
*        - Modified to use protected Vtab initialisation methods when
*        loading an Object.
*     1-NOV-2003 (DSB):
*        Change the initialiser so that it accepts source and sink
*        wrapper functions as arguments (for use by derived classes).
*     16-AUG-2006 (DSB):
*        - Document non-destructive nature of unsuccessful astRead calls
*        on a FitsChan.
*     3-OCT-2008 (DSB):
*        Added "Strict" attribute.
*     11-DEC-2008 (DSB):
*        Added astPutChannelData and astChannelData functions.
*     16-JAN-2009 (DSB):
*        Added astAddWarning and astWarnings.
*     11-JUN-2009 (DSB):
*        Enable astChannelData to be used from within astRead.
*     7-DEC-2009 (DSB):
*        Added Indent attribute.
*     12-FEB-2010 (DSB):
*        Represent AST__BAD externally using the string "<bad>".
*     23-JUN-2011 (DSB):
*        Added attributes SinkFile and SourceFile.
*     2-OCT-2012 (DSB):
*        Report an error if an Inf or NaN value is read from the external
*        source.
*     2-OCT-2019 (DSB):
*        Speed up the reading of large objects (such as MOCs) by using
*        multiple linked lists accessed via a hash table to store the values
*        at each nesting level, rather than a single linked list.
*class--
*/

/* Module Macros. */
/* ============== */
/* Set the name of the class we are implementing. This indicates to
   the header files that define class interfaces that they should make
   "protected" symbols available. */
#define astCLASS Channel

/* Define a string containing the maximum length of keywords used to
   identify values in the external representation of data. This is
   deliberately kept small so as to simplify integration with
   standards such as FITS. */
#define MAX_NAME "8"

/* Max length of string returned by GetAttrib */
#define GETATTRIB_BUFF_LEN 50

/* String used to represent AST__BAD externally. */
#define BAD_STRING "<bad>"

/* The number of entries in the hash table used to speed up the search
   for a specified value name. */
#define HASHMAP_SIZE 128

/* Include files. */
/* ============== */
/* Interface definitions. */
/* ---------------------- */

#include "globals.h"             /* Thread-safe global data access */
#include "error.h"               /* Error reporting facilities */
#include "memory.h"              /* Memory allocation facilities */
#include "object.h"              /* Base Object class */
#include "channel.h"             /* Interface definition for this class */
#include "loader.h"              /* Interface to the global loader */
#include "keymap.h"              /* Storing arbitrary data in an AST Object */
#include "pointset.h"            /* For AST__BAD */

/* Error code definitions. */
/* ----------------------- */
#include "ast_err.h"             /* AST error codes */

/* C header files. */
/* --------------- */
#include <ctype.h>
#include <errno.h>
#include <float.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>

/* Module Variables. */
/* ================= */

/* Address of this static variable is used as a unique identifier for
   member of this class. */
static int class_check;

/* Pointers to parent class methods which are extended by this class. */
static const char *(* parent_getattrib)( AstObject *, const char *, int * );
static int (* parent_testattrib)( AstObject *, const char *, int * );
static void (* parent_clearattrib)( AstObject *, const char *, int * );
static void (* parent_setattrib)( AstObject *, const char *, int * );

/* Define macros for accessing each item of thread specific global data. */
#ifdef THREAD_SAFE

/* Define how to initialise thread-specific globals. */
#define GLOBAL_inits \
   globals->Class_Init = 0; \
   globals->AstReadClassData_Msg = 0; \
   globals->GetAttrib_Buff[ 0 ] = 0; \
   globals->Items_Written = 0; \
   globals->Current_Indent = 0; \
   globals->Nest = -1; \
   globals->Nwrite_Invoc = 0; \
   globals->Object_Class = NULL; \
   globals->Values_List = NULL; \
   globals->Values_Class = NULL; \
   globals->Values_OK = NULL; \
   globals->End_Of_Object = NULL; \
   globals->Channel_Data  = NULL;

/* Create the function that initialises global data for this module. */
astMAKE_INITGLOBALS(Channel)

/* Define macros for accessing each item of thread specific global data. */
#define class_init astGLOBAL(Channel,Class_Init)
#define class_vtab astGLOBAL(Channel,Class_Vtab)
#define astreadclassdata_msg astGLOBAL(Channel,AstReadClassData_Msg)
#define getattrib_buff astGLOBAL(Channel,GetAttrib_Buff)
#define items_written  astGLOBAL(Channel,Items_Written)
#define current_indent astGLOBAL(Channel,Current_Indent)
#define nest           astGLOBAL(Channel,Nest)
#define nwrite_invoc   astGLOBAL(Channel,Nwrite_Invoc)
#define object_class   astGLOBAL(Channel,Object_Class)
#define values_list    astGLOBAL(Channel,Values_List)
#define values_class   astGLOBAL(Channel,Values_Class)
#define values_ok      astGLOBAL(Channel,Values_OK)
#define end_of_object  astGLOBAL(Channel,End_Of_Object)
#define channel_data   astGLOBAL(Channel,Channel_Data)



static pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
#define LOCK_MUTEX2 pthread_mutex_lock( &mutex2 );
#define UNLOCK_MUTEX2 pthread_mutex_unlock( &mutex2 );

static pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;
#define LOCK_MUTEX3 pthread_mutex_lock( &mutex3 );
#define UNLOCK_MUTEX3 pthread_mutex_unlock( &mutex3 );

/* If thread safety is not needed, declare and initialise globals at static
   variables. */
#else

/* Contextual error message reported in astReadClassData? */
static int astreadclassdata_msg = 0;

/* Buffer returned by GetAttrib. */
static char getattrib_buff[ GETATTRIB_BUFF_LEN + 1 ];

/* Count of the number of output items written since the last "Begin"
   or "IsA" item. */
static int items_written = 0;

/* Amount of indentation to be applied to the next output item. */
static int current_indent = 0;

/* Nesting level, used to keep track of data associated with building
   Objects when they contain other Objects. */
static int nest = -1;

/* The number of times astWrite has been invoked. */
static int nwrite_invoc = 0;

/* Pointer to a user-supplied block of memory to be made available to
   source or sink functions via the astChannelData function. */
static void *channel_data = NULL;

/***
   The following items are all pointers to dynamically allocated
   arrays (stacks) that grow as necessary to accommodate one element
   for each level of nesting (one more than the value of "nest").
***/

/* Stack of pointers to null-terminated character strings giving the
   names of the classes of the Objects being built at each nesting
   level. */
static char **object_class = NULL;

/* Stack of pointers to the elements designated as the "heads" of
   circular, doubly linked lists of name-value associations. */
static AstChannelValue ***values_list = NULL;

/* Stack of pointers to null-terminated character strings giving the
   names of the classes for which the values held in the values lists
   are intended. */
static char **values_class = NULL;

/* Stack of flags indicating whether the values held in the values
   lists are intended for the class loaders currently executing to
   build Objects at each nesting level. */
static int *values_ok = NULL;

/* Stack of flags indicating whether "End" items have been read for
   the Objects being built at each nesting level. */
static int *end_of_object = NULL;


/* Define the class virtual function table and its initialisation flag
   as static variables. */
static AstChannelVtab class_vtab;   /* Virtual function table */
static int class_init = 0;       /* Virtual function table initialised? */
#define LOCK_MUTEX2
#define UNLOCK_MUTEX2
#define LOCK_MUTEX3
#define UNLOCK_MUTEX3

#endif

/* External Interface Function Prototypes. */
/* ======================================= */
/* The following functions have public prototypes only (i.e. no
   protected prototypes), so we must provide local prototypes for use
   within this module. */
AstChannel *astChannelForId_( const char *(*)( void ),
                              char *(*)( const char *(*)( void ), int * ),
                              void (*)( const char * ),
                              void (*)( void (*)( const char * ),
                                        const char *, int * ),
                              const char *, ... );
AstChannel *astChannelId_( const char *(*)( void ), void (*)( const char * ), const char *, ... );

/* Prototypes for Private Member Functions. */
/* ======================================== */
static AstObject *Read( AstChannel *, int * );
static AstObject *ReadObject( AstChannel *, const char *, AstObject *, int * );
static AstChannelValue *FreeValue( AstChannelValue *, int * );
static AstChannelValue *LookupValue( const char *, int * );
static AstKeyMap *Warnings( AstChannel *, int * );
static char *GetNextText( AstChannel *, int * );
static char *InputTextItem( AstChannel *, int * );
static char *ReadString( AstChannel *, const char *, const char *, int * );
static char *SourceWrap( const char *(*)( void ), int * );
static const char *GetAttrib( AstObject *, const char *, int * );
static double ReadDouble( AstChannel *, const char *, double, int * );
static int GetComment( AstChannel *, int * );
static int GetFull( AstChannel *, int * );
static int GetSkip( AstChannel *, int * );
static int GetStrict( AstChannel *, int * );
static int HashFun( const char *, int, unsigned long * );
static int ReadInt( AstChannel *, const char *, int, int * );
static int64_t ReadInt64( AstChannel *, const char *, int64_t, int * );
static int TestAttrib( AstObject *, const char *, int * );
static int TestComment( AstChannel *, int * );
static int TestFull( AstChannel *, int * );
static int TestSkip( AstChannel *, int * );
static int TestStrict( AstChannel *, int * );
static int Use( AstChannel *, int, int, int * );
static int Write( AstChannel *, AstObject *, int * );
static void AddWarning( AstChannel *, int, const char *, const char *, int * );
static void AppendValue( AstChannelValue *, AstChannelValue **, int * );
static void ClearAttrib( AstObject *, const char *, int * );
static void ClearComment( AstChannel *, int * );
static void ClearFull( AstChannel *, int * );
static void ClearSkip( AstChannel *, int * );
static void ClearStrict( AstChannel *, int * );
static void ClearValues( AstChannel *, int * );
static void Copy( const AstObject *, AstObject *, int * );
static void Delete( AstObject *, int * );
static void Dump( AstObject *, AstChannel *, int * );
static void GetNextData( AstChannel *, int, char **, char **, int * );
static void OutputTextItem( AstChannel *, const char *, int * );
static void PutChannelData( AstChannel *, void *, int * );
static void PutNextText( AstChannel *, const char *, int * );
static void ReadClassData( AstChannel *, const char *, int * );
static void RemoveValue( AstChannelValue *, AstChannelValue **, int * );
static void SetAttrib( AstObject *, const char *, int * );
static void SetComment( AstChannel *, int, int * );
static void SetFull( AstChannel *, int, int * );
static void SetSkip( AstChannel *, int, int * );
static void SetStrict( AstChannel *, int, int * );
static void SinkWrap( void (*)( const char * ), const char *, int * );
static void Unquote( AstChannel *, char *, int * );
static void WriteBegin( AstChannel *, const char *, const char *, int * );
static void WriteDouble( AstChannel *, const char *, int, int, double, const char *, int * );
static void WriteEnd( AstChannel *, const char *, int * );
static void WriteInt( AstChannel *, const char *, int, int, int, const char *, int * );
static void WriteInt64( AstChannel *, const char *, int, int, int64_t, const char *, int * );
static void WriteIsA( AstChannel *, const char *, const char *, int * );
static void WriteObject( AstChannel *, const char *, int, int, AstObject *, const char *, int * );
static void WriteString( AstChannel *, const char *, int, int, const char *, const char *, int * );
static void WriteFlush( AstChannel *, int * );

static int GetReportLevel( AstChannel *, int * );
static int TestReportLevel( AstChannel *, int * );
static void ClearReportLevel( AstChannel *, int * );
static void SetReportLevel( AstChannel *, int, int * );

static int GetIndent( AstChannel *, int * );
static int TestIndent( AstChannel *, int * );
static void ClearIndent( AstChannel *, int * );
static void SetIndent( AstChannel *, int, int * );

static const char *GetSourceFile( AstChannel *, int * );
static int TestSourceFile( AstChannel *, int * );
static void ClearSourceFile( AstChannel *, int * );
static void SetSourceFile( AstChannel *, const char *, int * );

static const char *GetSinkFile( AstChannel *, int * );
static int TestSinkFile( AstChannel *, int * );
static void ClearSinkFile( AstChannel *, int * );
static void SetSinkFile( AstChannel *, const char *, int * );

/* Member functions. */
/* ================= */
static void AddWarning( AstChannel *this, int level, const char *msg,
                        const char *method, int *status ) {
/*
*+
*  Name:
*     astAddWarning

*  Purpose:
*     Add a warning to a Channel.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     void astAddWarning( AstChannel *this, int level, const char *msg,
*                         const char *method, int status, ... )

*  Class Membership:
*     Channel method.

*  Description:
*     This function stores a warning message inside a Channel. These
*     messages can be retirieved using astWarnings.

*  Parameters:
*     this
*        Pointer to the Channel.
*     level
*        Ignore the warning if the ReportLevel attribute value is less
*        than "level".
*     msg
*        The wanting message to store. It may contain printf format
*        specifiers. If a NULL pointer is supplied, all warnings
*        currently stored in the Channel are removed.
*     method
*        The method name.
*     status
*        Inherited status value.
*     ...
*        Extra values to substitute into the message string as
*        replacements for the printf format specifiers.
*-

*  Note: The expansion of the printf format specifiers is done in the
*     astAddWarning_ wrapper function. The AddWarning functions defined by
*     each class receives the fully expanded message and does not have a
*     variable argument list. The variable argument list is included in the
*     above prologue in order to document the wrapper function.

*/

/* Local Variables: */
   int i;          /* Message index */
   char *a;        /* Pointer to copy of message */

/* If a NULL pointer was supplied, free all warnings currently in the
   Channel. Do this before checking the inherited status so that it works
   even if an error has occurred. */
   if( !msg ) {
      for( i = 0; i < this->nwarn; i++ ) {
         (this->warnings)[ i ] = astFree( (this->warnings)[ i ] );
      }
      this->warnings = astFree( this->warnings );
      this->nwarn = 0;
      return;
   }

/* Check the global error status. */
   if ( !astOK ) return;

/* Only proceed if the message level is sufficiently important. */
   if( astGetReportLevel( this ) >= level ) {

/* If we are being strict, issue an error rather than a warning. */
      if( astGetStrict( this ) ) {
         if( astOK ) {
            astError( AST__BADIN, "%s(%s): %s", status, method,
                      astGetClass( this ), msg );
         }

/* Otherwise, we store a copy of the message in the Channel. */
      } else {

/* Allocate memory and store a copy of th supplied string in it. */
         a = astStore( NULL, msg, strlen( msg ) + 1 );

/* Expand the array of warning pointers in ther Channel structure. */
         this->warnings = astGrow( this->warnings, this->nwarn + 1,
                                   sizeof( char * ) );

/* If all is OK so far, store the new warning pointer, and increment the
   number of warnings in the Channel. */
         if( astOK ) {
            (this->warnings)[ (this->nwarn)++ ] = a;

/* Otherwise, attempt to free the memory holding the copy of the warning. */
         } else {
            a = astFree( a );
         }
      }
   }
}

static void AppendValue( AstChannelValue *value, AstChannelValue **head, int *status ) {
/*
*  Name:
*     AppendValue

*  Purpose:
*     Append a Value structure to a list.

*  Type:
*     Private function.

*  Synopsis:
*     #include "channel.h"
*     void AppendValue( AstChannelValue *value, AstChannelValue **head, int *status )

*  Class Membership:
*     Channel member function.

*  Description:
*     This function appends a Value structure to a doubly linked
*     circular list of such structures. The new list element is
*     inserted just in front of the element occupying the "head of
*     list" position (i.e. it becomes the new last element in the
*     list).

*  Parameters:
*     value
*        Pointer to the new element. This must not already be in the
*        list.
*     head
*        Address of a pointer to the element at the head of the list
*        (this pointer should be NULL if the list is initially
*        empty). This pointer will only be updated if a new element is
*        being added to an empty list.
*     status
*        Pointer to the inherited status variable.

*  Notes:
*     - This function does not perform error chacking and does not
*     generate errors.
*/

/* If the list is initially empty, the sole new element points at
   itself. */
   if ( !*head ) {
      value->flink = value;
      value->blink = value;

/* Update the list head to identify the new element. */
      *head = value;

/* Otherwise, insert the new element in front of the element at the
   head of the list. */
   } else {
      value->flink = *head;
      value->blink = ( *head )->blink;
      ( *head )->blink = value;
      value->blink->flink = value;
   }
}

void *astChannelData_( void ) {
/*
c++
*  Name:
*     astChannelData

*  Purpose:
*     Return a pointer to user-supplied data stored with a Channel.

*  Type:
*     Public macro.

*  Synopsis:
*     #include "channel.h"
*     void *astChannelData

*  Class Membership:
*     Channel macro.

*  Description:
*     This macro is intended to be used within the source or sink
*     functions associated with a Channel. It returns any pointer
*     previously stored in the Channel (that is, the Channel that has
*     invoked the source or sink function) using astPutChannelData.
*
*     This mechanism is a thread-safe alternative to passing file
*     descriptors, etc, via static global variables.

*  Returned Value:
*     astChannelData
*        The pointer previously stored with the Channel using
*        astPutChannelData. A NULL pointer will be returned if no such
*        pointer has been stored with the Channel.

*  Applicability:
*     Channel
*        This macro applies to all Channels.

*  Notes:
*     - This routine is not available in the Fortran 77 interface to
*     the AST library.
c--
*/
   astDECLARE_GLOBALS
   astGET_GLOBALS(NULL);
   return channel_data;
}

static void ClearAttrib( AstObject *this_object, const char *attrib, int *status ) {
/*
*  Name:
*     ClearAttrib

*  Purpose:
*     Clear an attribute value for a Channel.

*  Type:
*     Private function.

*  Synopsis:
*     #include "channel.h"
*     void ClearAttrib( AstObject *this, const char *attrib, int *status )

*  Class Membership:
*     Channel member function (over-rides the astClearAttrib protected
*     method inherited from the Object class).

*  Description:
*     This function clears the value of a specified attribute for a
*     Channel, so that the default value will subsequently be used.

*  Parameters:
*     this
*        Pointer to the Channel.
*     attrib
*        Pointer to a null terminated string specifying the attribute
*        name.  This should be in lower case with no surrounding white
*        space.
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   AstChannel *this;              /* Pointer to the Channel structure */

/* Check the global error status. */
   if ( !astOK ) return;

/* Obtain a pointer to the Channel structure. */
   this = (AstChannel *) this_object;

/* Check the attribute name and clear the appropriate attribute. */

/* Comment. */
/* -------- */
   if ( !strcmp( attrib, "comment" ) ) {
      astClearComment( this );

/* Full. */
/* ----- */
   } else if ( !strcmp( attrib, "full" ) ) {
      astClearFull( this );

/* Indent. */
/* ------- */
   } else if ( !strcmp( attrib, "indent" ) ) {
      astClearIndent( this );

/* ReportLevel. */
/* ------------ */
   } else if ( !strcmp( attrib, "reportlevel" ) ) {
      astClearReportLevel( this );

/* Skip. */
/* ----- */
   } else if ( !strcmp( attrib, "skip" ) ) {
      astClearSkip( this );

/* SourceFile. */
/* ----------- */
   } else if ( !strcmp( attrib, "sourcefile" ) ) {
      astClearSourceFile( this );

/* SinkFile. */
/* --------- */
   } else if ( !strcmp( attrib, "sinkfile" ) ) {
      astClearSinkFile( this );

/* Strict. */
/* ------- */
   } else if ( !strcmp( attrib, "strict" ) ) {
      astClearStrict( this );

/* If the attribute is still not recognised, pass it on to the parent
   method for further interpretation. */
   } else {
      (*parent_clearattrib)( this_object, attrib, status );
   }
}

static void ClearValues( AstChannel *this, int *status ) {
/*
*  Name:
*     ClearValues

*  Purpose:
*     Clear the current values list.

*  Type:
*     Private function.

*  Synopsis:
*     #include "channel.h"
*     void ClearValues( AstChannel *this, int *status )

*  Class Membership:
*     Channel member function.

*  Description:
*     This function clears any (un-read) Value structures remaining in
*     the current values list (i.e. at the current nesting level). It
*     should be invoked after all required values have been read.
*
*     If the values list has not been read, or if any remaining values
*     are found (i.e. the list is not empty) then this indicates an
*     unrecognised input class or an input value that has not been
*     read by a class loader. This implies an error in the loader, or
*     bad input data, so an error is reported.
*
*     All resources used by any remaining Value structures are freed
*     and the values list is left in an empty state.

*  Parameters:
*     this
*        Pointer to the Channel being read. This is only used for
*        constructing error messages. It must not be NULL.
*     status
*        Pointer to the inherited status variable.

*  Notes:
*     - This function attempts to execute even if the global error
*     status is set on entry, although no further error report will be
*     made if it subsequently fails under these circumstances.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   AstChannelValue **head;       /* Address of pointer to values list */
   AstChannelValue *value;       /* Pointer to value list element */
   int indx;                     /* Index into hash map */

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* If "values_class" is non-NULL, then the values list has previously
   been filled with Values for a class. */
   if ( values_class[ nest ] ) {

/* If "values_ok" is zero, however, then these Values have not yet
   been read by a class loader. This must be due to a bad class name
   associated with them or because the class data are not available in
   the correct order. If we are using strict error reporting, then report
   an error (unless the error status is already set). */
      if ( astGetStrict( this ) && !values_ok[ nest ] && astOK ) {
         astError( AST__BADIN,
                   "astRead(%s): Invalid class structure in input data.", status,
                   astGetClass( this ) );
         astError( AST__BADIN,
                   "Class \"%s\" is invalid or out of order within a %s.", status,
                   values_class[ nest ], object_class[ nest ] );
      }

/* Free the memory holding the class string. */
      values_class[ nest ] = astFree( values_class[ nest ] );
   }

/* Reset the "values_ok" flag. */
   values_ok[ nest ] = 0;

/* Now clear any Values remaining in the values list. Obtain the
   address of the pointer to the head of this list (at the current
   nesting level) and loop to remove Values from the list while it is
   not empty. */
   for( indx = 0; indx < HASHMAP_SIZE; indx++ ) {
      head = values_list[ nest ] + indx;
      while ( *head ) {

/* Obtain a pointer to the first element. */
         value = *head;

/* Issue a warning. */
         if ( value->is_object ) {
            astAddWarning( this, 1, "The Object \"%s = <%s>\" was "
                           "not recognised as valid input.", "astRead", status,
                           value->name, astGetClass( value->ptr.object ) );
         } else {
            astAddWarning( this, 1, "The value \"%s = %s\" was not "
                           "recognised as valid input.", "astRead", status,
                           value->name, value->ptr.string );
         }

/* Remove the Value structure from the list (which updates the head of
   list pointer) and free its resources. */
         RemoveValue( value, head, status );
         value = FreeValue( value, status );
      }
   }
}

static AstChannelValue *FreeValue( AstChannelValue *value, int *status ) {
/*
*  Name:
*     FreeValue

*  Purpose:
*     Free a dynamically allocated Value structure.

*  Type:
*     Private function.

*  Synopsis:
*     #include "channel.h"
*     AstChannelValue *FreeValue( AstChannelValue *value, int *status )

*  Class Membership:
*     Channel member function.

*  Description:
*     This function frees a dynamically allocated Value structure,
*     releasing all resources used by it. The structure contents must
*     have been correctly initialised.

*  Parameters:
*     value
*        Pointer to the Value structure to be freed.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A NULL pointer is always returned.

*  Notes:
*     - This function attempts to execute even if the global error
*     status is set on entry, although no further error report will be
*     made if it subsequently fails under these circumstances.
*/

/* Check that a non-NULL pointer has been supplied. */
   if ( value ) {

/* If the "name" component has been allocated, then free it. */
      if ( value->name ) value->name = astFree( value->name );

/* If the "ptr" component identifies an Object, then annul the Object
   pointer. */
      if ( value->is_object ) {
         if ( value->ptr.object ) {
            value->ptr.object = astAnnul( value->ptr.object );
         }

/* Otherwise, if it identifies a string, then free the string. */
      } else {
         if ( value->ptr.string ) {
            value->ptr.string = astFree( value->ptr.string );
         }
      }

/* Free the Value structure itself. */
      value = astFree( value );
   }

/* Return a NULL pointer. */
   return NULL;
}

static const char *GetAttrib( AstObject *this_object, const char *attrib, int *status ) {
/*
*  Name:
*     GetAttrib

*  Purpose:
*     Get the value of a specified attribute for a Channel.

*  Type:
*     Private function.

*  Synopsis:
*     #include "channel.h"
*     const char *GetAttrib( AstObject *this, const char *attrib, int *status )

*  Class Membership:
*     Channel member function (over-rides the protected astGetAttrib
*     method inherited from the Object class).

*  Description:
*     This function returns a pointer to the value of a specified
*     attribute for a Channel, formatted as a character string.

*  Parameters:
*     this
*        Pointer to the Channel.
*     attrib
*        Pointer to a null terminated string containing the name of
*        the attribute whose value is required. This name should be in
*        lower case, with all white space removed.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     - Pointer to a null terminated string containing the attribute
*     value.

*  Notes:
*     - The returned string pointer may point at memory allocated
*     within the Channel, or at static memory. The contents of the
*     string may be over-written or the pointer may become invalid
*     following a further invocation of the same function or any
*     modification of the Channel. A copy of the string should
*     therefore be made if necessary.
*     - A NULL pointer will be returned if this function is invoked
*     with the global error status set, or if it should fail for any
*     reason.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   AstChannel *this;             /* Pointer to the Channel structure */
   const char *result;           /* Pointer value to return */
   int comment;                  /* Comment attribute value */
   int full;                     /* Full attribute value */
   int indent;                   /* Indent attribute value */
   int report_level;             /* ReportLevel attribute value */
   int skip;                     /* Skip attribute value */
   int strict;                   /* Report errors insead of warnings? */

/* Initialise. */
   result = NULL;

/* Check the global error status. */
   if ( !astOK ) return result;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this_object);

/* Obtain a pointer to the Channel structure. */
   this = (AstChannel *) this_object;

/* Compare "attrib" with each recognised attribute name in turn,
   obtaining the value of the required attribute. If necessary, write
   the value into "getattrib_buff" as a null terminated string in an appropriate
   format.  Set "result" to point at the result string. */

/* Comment. */
/* -------- */
   if ( !strcmp( attrib, "comment" ) ) {
      comment = astGetComment( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", comment );
         result = getattrib_buff;
      }

/* Full. */
/* ----- */
   } else if ( !strcmp( attrib, "full" ) ) {
      full = astGetFull( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", full );
         result = getattrib_buff;
      }

/* Indent. */
/* ------- */
   } else if ( !strcmp( attrib, "indent" ) ) {
      indent = astGetIndent( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", indent );
         result = getattrib_buff;
      }

/* ReportLevel. */
/* ------------ */
   } else if ( !strcmp( attrib, "reportlevel" ) ) {
      report_level = astGetReportLevel( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", report_level );
         result = getattrib_buff;
      }

/* Skip. */
/* ----- */
   } else if ( !strcmp( attrib, "skip" ) ) {
      skip = astGetSkip( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", skip );
         result = getattrib_buff;
      }

/* SourceFile. */
/* ----------- */
   } else if ( !strcmp( attrib, "sourcefile" ) ) {
      result = astGetSourceFile( this );

/* SinkFile. */
/* --------- */
   } else if ( !strcmp( attrib, "sinkfile" ) ) {
      result = astGetSinkFile( this );

/* Strict. */
/* ------- */
   } else if ( !strcmp( attrib, "strict" ) ) {
      strict = astGetStrict( this );
      if ( astOK ) {
         (void) sprintf( getattrib_buff, "%d", strict );
         result = getattrib_buff;
      }

/* If the attribute name was not recognised, pass it on to the parent
   method for further interpretation. */
   } else {
      result = (*parent_getattrib)( this_object, attrib, status );
   }

/* Return the result. */
   return result;

}

static void GetNextData( AstChannel *this, int skip, char **name,
                         char **val, int *status ) {
/*
*+
*  Name:
*     astGetNextData

*  Purpose:
*     Read the next item of data from a data source.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     void astGetNextData( AstChannel *this, int skip, char **name,
*                          char **val )

*  Class Membership:
*     Channel method.

*  Description:
*     This function reads the next item of input data from a data
*     source associated with a Channel and returns the result.
*
*     It is layered conceptually on the astGetNextText method, but
*     instead of returning the raw input text, it decodes it and
*     returns name/value pairs ready for use. Note that in some
*     derived classes, where the data are not stored as text, this
*     function may not actually use astGetNextText, but will access
*     the data directly.

*  Parameters:
*     this
*        Pointer to the Channel.
*     skip
*        A non-zero value indicates that a new Object is to be read,
*        and that all input data up to the next "Begin" item are to be
*        skipped in order to locate it. This is useful if the data
*        source contains AST objects interspersed with other data (but
*        note that these other data cannot appear inside AST Objects,
*        only between them).
*
*        A zero value indicates that all input data are significant
*        and the next item will therefore be read and an attempt made
*        to interpret it whatever it contains. Any other data
*        inter-mixed with AST Objects will then result in an error.
*     name
*        An address at which to store a pointer to a null-terminated
*        dynamically allocated string containing the name of the next
*        item in the input data stream. This name will be in lower
*        case with no surrounding white space.  It is the callers
*        responsibilty to free the memory holding this string (using
*        astFree) when it is no longer required.
*
*        A NULL pointer value will be returned (without error) to
*        indicate when there are no further input data items to be
*        read.
*     val
*        An address at which to store a pointer to a null-terminated
*        dynamically allocated string containing the value associated
*        with the next item in the input data stream. No case
*        conversion is performed on this string and all white space is
*        potentially significant.  It is the callers responsibilty to
*        free the memory holding this string (using astFree) when it
*        is no longer required.
*
*        The returned pointer will be NULL if an Object data item is
*        read (see the "Data Representation" section).

*  Data Representation:
*     The returned data items fall into the following categories:
*
*     - Begin: Identified by the name string "begin", this indicates
*     the start of an Object definition. The associated value string
*     gives the class name of the Object being defined.
*
*     - IsA: Identified by the name string "isa", this indicates the
*     end of the data associated with a particular class structure
*     within the definiton of a larger Object. The associated value
*     string gives the name of the class whose data have just been
*     read.
*
*     - End: Identified by the name string "end", this indicates the
*     end of the data associated with a complete Object
*     definition. The associated value string gives the class name of
*     the Object whose definition is being ended.
*
*     - Non-Object: Identified by any other name string plus a
*     non-NULL "val" pointer, this gives the value of a non-Object
*     structure component (instance variable). The name identifies
*     which instance variable it is (within the context of the class
*     whose data are being read) and the value is encoded as a string.
*
*     - Object: Identified by any other name string plus a NULL "val"
*     pointer, this identifies the value of an Object structure
*     component (instance variable).  The name identifies which
*     instance variable it is (within the context of the class whose
*     data are being read) and the value is given by subsequent data
*     items (so the next item should be a "Begin" item).

*  Notes:
*     - NULL pointer values will be returned if this function is
*     invoked with the global error status set, or if it should fail
*     for any reason.
*     - This method is provided primarily so that derived classes may
*     over-ride it in order to read from alternative data sources. It
*     provides a higher-level interface than astGetNextText, so is
*     suitable for classes that either need to read textual data in a
*     different format, or to read from non-textual data sources.
*-
*/

/* Local Variables: */
   char *line;                   /* Pointer to input text line */
   int done;                     /* Data item read? */
   int i;                        /* Loop counter for string characters */
   int len;                      /* Length of input text line */
   int nc1;                      /* Offset to start of first field */
   int nc2;                      /* Offset to end of first field */
   int nc3;                      /* Offset to start of second field */
   int nc;                       /* Number of charaters read by "astSscanf" */

/* Initialise the returned values. */
   *name = NULL;
   *val = NULL;

/* Check the global error status. */
   if ( !astOK ) return;

/* Read the next input line as text (the loop is needed to allow
   initial lines to be skipped if the "skip" flag is set). */
   done = 0;
   while ( !done && ( line = InputTextItem( this, status ) ) && astOK ) {

/* If OK, determine the line length. */
      len = strlen( line );

/* Non-Object value. */
/* ----------------- */
/* Test for lines of the form " name = value" (or similar), where the
   name is no more than MAX_NAME characters long (the presence of a
   value on the right hand side indicates that this is a non-Object
   value, encoded as a string). Ignore these lines if the "skip" flag
   is set. */
      if ( nc = 0,
           ( !skip
             && ( 0 == astSscanf( line,
                               " %n%*" MAX_NAME "[^ \t=]%n = %n%*[^\n]%n",
                               &nc1, &nc2, &nc3, &nc ) )
             && ( nc >= len ) ) ) {

/* Note we have found a data item. */
         done = 1;

/* Extract the name and value fields. */
         *name = astString( line + nc1, nc2 - nc1 );
         *val = astString( line + nc3, len - nc3 );

/* If OK, truncate the value to remove any trailing white space. */
         if ( astOK ) {
            i = len - nc3 - 1;
            while ( ( i >= 0 ) && isspace( ( *val )[ i ] ) ) i--;
            ( *val )[ i + 1 ] = '\0';

/* Also remove any quotes from the string. */
            Unquote( this, *val, status );
         }

/* Object value. */
/* ------------- */
/* Test for lines of the form " name = " (or similar), where the name
   is no more than MAX_NAME characters long (the absence of a value on
   the right hand side indicates that this is an Object, whose
   definition follows on subsequent input lines). Ignore these lines
   if the "skip" flag is set. */
      } else if ( nc = 0,
                  ( !skip
                    && ( 0 == astSscanf( line,
                                      " %n%*" MAX_NAME "[^ \t=]%n = %n",
                                      &nc1, &nc2, &nc ) )
                    && ( nc >= len ) ) ) {

/* Note we have found a data item. */
         done = 1;

/* Extract the name field but leave the value pointer as NULL. */
         *name = astString( line + nc1, nc2 - nc1 );

/* Begin. */
/* ------ */
/* Test for lines of the form " Begin Class " (or similar). */
      } else if ( nc = 0,
             ( ( 0 == astSscanf( line,
                             " %*1[Bb]%*1[Ee]%*1[Gg]%*1[Ii]%*1[Nn] %n%*s%n %n",
                              &nc1, &nc2, &nc ) )
               && ( nc >= len ) ) ) {

/* Note we have found a data item. */
         done = 1;

/* Set the returned name to "begin" and extract the associated class
   name for the value. Store both of these in dynamically allocated
   strings. */
         *name = astString( "begin", 5 );
         *val = astString( line + nc1, nc2 - nc1 );

/* IsA. */
/* ---- */
/* Test for lines of the form " IsA Class " (or similar). Ignore these
   lies if the "skip" flag is set. */
      } else if ( nc = 0,
                  ( !skip
                    && ( 0 == astSscanf( line,
                                      " %*1[Ii]%*1[Ss]%*1[Aa] %n%*s%n %n",
                                      &nc1, &nc2, &nc ) )
                    && ( nc >= len ) ) ) {

/* Note we have found a data item. */
         done = 1;

/* Set the returned name to "isa" and extract the associated class
   name for the value. */
         *name = astString( "isa", 3 );
         *val = astString( line + nc1, nc2 - nc1 );

/* End. */
/* ---- */
/* Test for lines of the form " End Class " (or similar). Ignore these
   lines if the "skip" flag is set. */
      } else if ( nc = 0,
                  ( !skip
                    && ( 0 == astSscanf( line,
                                      " %*1[Ee]%*1[Nn]%*1[Dd] %n%*s%n %n",
                                      &nc1, &nc2, &nc ) )
                    && ( nc >= len ) ) ) {

/* Note we have found a data item. */
         done = 1;

/* If found, set the returned name to "end" and extract the associated
   class name for the value. */
         *name = astString( "end", 3 );
         *val = astString( line + nc1, nc2 - nc1 );

/* If the input line didn't match any of the above and the "skip" flag
   is not set, then report an error. Truncate long lines for display. */
      } else if ( !skip ) {
         if( len > 50 ) strcpy( line + 50, "..." );
         astError( AST__BADIN,
                   "astRead(%s): Cannot interpret the input data: \"%s\".", status,
                   astGetClass( this ), line );
      }

/* Free the memory holding the input data as text. */
      line = astFree( line );
   }

/* If successful, convert the name to lower case. */
   if ( astOK && *name ) {
      for ( i = 0; ( *name )[ i ]; i++ ) {
         ( *name )[ i ] = tolower( ( *name )[ i ] );
      }
   }

/* If an error occurred, ensure that any memory allocated is freed and
   that NULL pointer values are returned. */
   if ( !astOK ) {
      *name = astFree( *name );
      *val = astFree( *val );
   }
}

static int HashFun( const char *key, int bitmask, unsigned long *hash ){
/*
*  Name:
*     HashFun

*  Purpose:
*     Returns an integer hash code for a string

*  Type:
*     Private function.

*  Synopsis:
*     #include "channel.h"
*     int HashFun( const char *key, int bitmask, int *hash )

*  Class Membership:
*     KeyMap member function.

*  Description:
*     This function returns an integer hash code for the supplied string,
*     This is the value that isused as the index into the hash table for
*     the specified key.

*  Parameters:
*     key
*        Pointer to the string. Trailing spaces are ignored.
*     bitmask
*        A bit mask that is used to zero the upper bits of a full width
*        hash value in order to produce the required array index. This
*        should be one less than the length of the hash table.
*     hash
*        Pointer to a location at which to put the full width hash value.

*  Returned Value:
*     An integer in the range zero to ( mapsize - 1 ).

*  Notes:
*     - This function does not check or set the inherited status.
*/

/* Local Variables: */
   int c;

/* djb2: This hash function was first reported by Dan Bernstein many years
   ago in comp.lang.c Each pass through the "while" loop corresponds to
   "hash = hash*33 + c ". */

   *hash = 5381;
   while( (c = *key++) ) {
      *hash = ((*hash << 5) + *hash) + c;
   }
   return ( *hash & bitmask );
}

static char *GetNextText( AstChannel *this, int *status ) {
/*
*+
*  Name:
*     GetNextText

*  Purpose:
*     Read the next line of input text from a data source.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     char *astGetNextText( AstChannel *this )

*  Class Membership:
*     Channel method.

*  Description:
*     This function reads the next "raw" input line of text from the
*     data source associated with a Channel.
*
*     Each line is returned as a pointer to a null-terminated string
*     held in dynamic memory, and it is the caller's responsibility to
*     free this memory (using astFree) when it is no longer
*     required. A NULL pointer is returned if there are no more input
*     lines to be read.

*  Parameters:
*     this
*        Pointer to the Channel.

*  Returned Value:
*     Pointer to a null-terminated string containing the input line
*     (held in dynamically allocated memory, which must be freed by
*     the caller when no longer required). A NULL pointer is returned
*     if there are no more input lines to be read.

*  Notes:
*     - A NULL pointer will be returned if this function is invoked
*     with the global error status set, or if it should fail for any
*     reason.
*     - This method is provided primarily so that derived classes may
*     over-ride it in order to read from alternative (textual) data
*     sources.
*-
*/

/* Local Constants: */
#define MIN_CHARS 81             /* Initial size for allocating memory */
#define ERRBUF_LEN 80

/* Local Variables: */
   FILE *fd;                     /* Input file descriptor */
   char *errstat;                /* Pointer for system error message */
   char *line;                   /* Pointer to line data to be returned */
   char errbuf[ ERRBUF_LEN ];    /* Buffer for system error message */
   const char *sink_file;        /* Path to output sink file */
   const char *source_file;      /* Path to source file */
   int c;                        /* Input character */
   int len;                      /* Length of input line */
   int readstat;                 /* "errno" value set by "getchar" */
   int size;                     /* Size of allocated memory */

/* Initialise. */
   line = NULL;

/* Check the global error status. */
   if ( !astOK ) return line;

/* If the SourceFile attribute of the Channel specifies an input file,
   but no input file has yet been opened, open it now. Report an error if
   it is the same as the sink file. */
   if( astTestSourceFile( this ) && !this->fd_in ) {
      source_file = astGetSourceFile( this );

      if( this->fd_out ) {
         sink_file = astGetSinkFile( this );
         if( astOK && !strcmp( sink_file, source_file ) ) {
            astError( AST__RDERR, "astRead(%s): Failed to open input "
                      "SourceFile '%s' - the file is currently being used "
                      "as the output SinkFile.", status, astGetClass( this ),
                      source_file );
         }
      }

      if( astOK ) {
         this->fd_in = fopen( source_file, "r" );
         if( !this->fd_in ) {
            if ( errno ) {
#if HAVE_STRERROR_R
               strerror_r( errno, errbuf, ERRBUF_LEN );
               errstat = errbuf;
#else
               errstat = strerror( errno );
#endif
               astError( AST__RDERR, "astRead(%s): Failed to open input "
                         "SourceFile '%s' - %s.", status, astGetClass( this ),
                         source_file, errstat );
            } else {
               astError( AST__RDERR, "astRead(%s): Failed to open input "
                         "SourceFile '%s'.", status, astGetClass( this ),
                         source_file );
            }
         }

      }
   }

/* Source function defined, but no input file. */
/* ------------------------------------------- */
/* If no active input file descriptor is stored in the Channel, but
   a source function (and its wrapper function) is defined for the
   Channel, use the wrapper function to invoke the source function to
   read a line of input text. This is returned in a dynamically
   allocated string. */
   if ( !this->fd_in && this->source && this->source_wrap ) {

/* About to call an externally supplied function which may not be
   thread-safe, so lock a mutex first. Also store the channel data
   pointer in a global variable so that it can be accessed in the source
   function using macro astChannelData. */
      astStoreChannelData( this );
      LOCK_MUTEX3;
      line = ( *this->source_wrap )( this->source, status );
      UNLOCK_MUTEX3;

/* Input file defined, or no source function. */
/* ------------------------------------------ */
/* Read the line from the input file or from standard input. */
   } else if( astOK ) {
      c = '\0';
      len = 0;
      size = 0;

/* Choose the file descriptor to use. */
      fd = this->fd_in ? this->fd_in : stdin;

/* Loop to read input characters, saving any "errno" value that may be
   set by "getchar" if an error occurs. Quit if an end of file (or
   error) occurs or if a newline character is read. */
      while ( errno = 0, c = getc( fd ), readstat = errno,
              ( c != EOF ) && ( c != '\n' ) ) {

/* If no memory has yet been allocated to hold the line, allocate some
   now, using MIN_CHARS as the initial line length. */
         if ( !line ) {
            line = astMalloc( sizeof( char ) * (size_t) MIN_CHARS );
            size = MIN_CHARS;

/* If memory has previously been allocated, extend it when necessary
   to hold the new input character (plus a terminating null) and note
   the new size. */
         } else if ( ( len + 2 ) > size ) {
            line = astGrow( line, len + 2, sizeof( char ) );
            if ( !astOK ) break;
            size = (int) astSizeOf( line );
         }

/* Store the character just read. */
         line[ len++ ] = c;
      }

/* If the above loop completed without setting the global error
   status, check the last character read and use "ferror" to see if a
   read error occurred. If so, report the error, using the saved
   "errno" value (but only if one was set). */
      if ( astOK && ( c == EOF ) && ferror( fd ) ) {
         if ( readstat ) {
#if HAVE_STRERROR_R
            strerror_r( readstat, errbuf, ERRBUF_LEN );
            errstat = errbuf;
#else
            errstat = strerror( readstat );
#endif
            astError( AST__RDERR,
                      "astRead(%s): Read error on standard input - %s.", status,
                      astGetClass( this ), errstat );
         } else {
            astError( AST__RDERR,
                      "astRead(%s): Read error on standard input.", status,
                      astGetClass( this ) );
         }
      }

/* If an empty line has been read, allocate memory to hold an empty
   string. */
      if ( !line && ( c == '\n' ) ) {
         line = astMalloc( sizeof( char ) );
      }

/* If memory has been allocated and there has been no error,
   null-terminate the string of input characters. */
      if ( line ) {
         if ( astOK ) {
            line[ len ] = '\0';

/* If there has been an error, free the allocated memory. */
         } else {
            line = astFree( line );
         }
      }
   }


/* Return the result pointer. */
   return line;

/* Undefine macros local to this function. */
#undef MIN_CHARS
#undef ERRBUF_LEN
}

static AstKeyMap *Warnings( AstChannel *this, int *status ){
/*
*++
*  Name:
c     astWarnings
f     AST_WARNINGS

*  Purpose:
*     Returns any warnings issued by the previous read or write operation.

*  Type:
*     Public virtual function.

*  Synopsis:
c     #include "channel.h"
c     AstKeyMap *astWarnings( AstChannel *this )
f     RESULT = AST_WARNINGS( THIS, STATUS )

*  Class Membership:
*     Channel member function.

*  Description:
*     This function returns an AST KeyMap object holding the text of any
*     warnings issued as a result of the previous invocation of the
c     astRead or astWrite
f     AST_READ or AST_WRITE
*     function on the Channel. If no warnings were issued, a
c     a NULL value
f     AST__NULL
*     will be returned.
*
*     Such warnings are non-fatal and will not prevent the
*     read or write operation succeeding. However, the converted object
*     may not be identical to the original object in all respects.
*     Differences which would usually be deemed as insignificant in most
*     usual cases will generate a warning, whereas more significant
*     differences will generate an error.
*
*     The "Strict" attribute allows this warning facility to be switched
*     off, so that a fatal error is always reported for any conversion
*     error.

*  Parameters:
c     this
f     THIS = INTEGER (Given)
*        Pointer to the Channel.
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Returned Value:
c     astWarnings()
f     AST_WARNINGS = INTEGER
*        A pointer to the KeyMap holding the warning messages, or
c        NULL
f        AST__NULL
*        if no warnings were issued during the previous read operation.

*  Applicability:
*     Channel
*        The basic Channel class generates a warning when ever an
*        un-recognised item is encountered whilst reading an Object from
*        an external data source. If Strict is zero (the default), then
*        unexpected items in the Object description are simply ignored,
*        and any remaining items are used to construct the returned
*        Object. If Strict is non-zero, an error will be reported and a
*        NULL Object pointer returned if any unexpected items are
*        encountered.
*
*        As AST continues to be developed, new attributes are added
*        occasionally to selected classes. If an older version of AST is
*        used to read external Object descriptions created by a more
*        recent version of AST, then the Channel class will, by default,
*        ignore the new attributes, using the remaining attributes to
*        construct the Object. This is usually a good thing. However,
*        since external Object descriptions are often stored in plain
*        text, it is possible to edit them using a text editor. This
*        gives rise to the possibility of genuine errors in the
*        description due to finger-slips, typos, or simple
*        mis-understanding. Such inappropriate attributes will be ignored
*        if Strict is left at its default zero value. This will cause the
*        mis-spelled attribute to revert to its default value,
*        potentially causing subtle changes in the behaviour of
*        application software. If such an effect is suspected, the Strict
*        attribute can be set non-zero, resulting in the erroneous
*        attribute being identified in an error message.
*     FitsChan
*        The returned KeyMap will contain warnings for all conditions
*        listed in the Warnings attribute.
*     XmlChan
*        Reports conversion errors that result in what are usally
*        insignificant  changes.

*  Notes:
*     - The returned KeyMap uses keys of the form "Warning_1",
*     "Warning_2", etc.
*     - A value of
c     NULL will be returned if this function is invoked with the AST
c     error status set,
f     AST__NULL will be returned if this function is invoked with STATUS
f     set to an error value,
*     or if it should fail for any reason.
*--
*/

/* Local Variables: */
   AstKeyMap *result;
   char key[ 20 ];
   int i;

/* Check the global status, and supplied keyword name. */
   result = NULL;
   if( !astOK ) return result;

/* Check there are some warnings to return. */
   if( this->nwarn && this->warnings ) {

/* Create the KeyMap. */
      result = astKeyMap( "", status );

/* Loop round all warnings, adding them into the KeyMap. */
      for( i = 0; i < this->nwarn; i++ ){
         sprintf( key, "Warning_%d", i + 1 );
         astMapPut0C( result, key, (this->warnings)[ i ], " " );
      }
   }

/* Return the KeyMap. */
   return result;
}

AstChannel *astInitChannel_( void *mem, size_t size, int init,
                             AstChannelVtab *vtab, const char *name,
                             const char *(* source)( void ),
                             char *(* source_wrap)( const char *(*)( void ), int * ),
                             void (* sink)( const char * ),
                             void (* sink_wrap)( void (*)( const char * ),
                                                 const char *, int * ), int *status ) {
/*
*+
*  Name:
*     astInitChannel

*  Purpose:
*     Initialise a Channel.

*  Type:
*     Protected function.

*  Synopsis:
*     #include "channel.h"
*     AstChannel *astInitChannel( void *mem, size_t size, int init,
*                                 AstChannelVtab *vtab, const char *name,
*                                 const char *(* source)( void ),
*                                 char *(* source_wrap)( const char *(*)( void ), int * ),
*                                 void (* sink)( const char * ),
*                                 void (* sink_wrap)( void (*)( const char * ),
*                                                     const char *, int * ) )

*  Class Membership:
*     Channel initialiser.

*  Description:
*     This function is provided for use by class implementations to
*     initialise a new Channel object. It allocates memory (if
*     necessary) to accommodate the Channel plus any additional data
*     associated with the derived class.  It then initialises a
*     Channel structure at the start of this memory. If the "init"
*     flag is set, it also initialises the contents of a virtual
*     function table for a Channel at the start of the memory passed
*     via the "vtab" parameter.

*  Parameters:
*     mem
*        A pointer to the memory in which the Channel is to be
*        initialised.  This must be of sufficient size to accommodate
*        the Channel data (sizeof(Channel)) plus any data used by the
*        derived class. If a value of NULL is given, this function
*        will allocate the memory itself using the "size" parameter to
*        determine its size.
*     size
*        The amount of memory used by the Channel (plus derived class
*        data).  This will be used to allocate memory if a value of
*        NULL is given for the "mem" parameter. This value is also
*        stored in the Channel structure, so a valid value must be
*        supplied even if not required for allocating memory.
*     init
*        A boolean flag indicating if the Channel's virtual function
*        table is to be initialised. If this value is non-zero, the
*        virtual function table will be initialised by this function.
*     vtab
*        Pointer to the start of the virtual function table to be
*        associated with the new Channel.
*     name
*        Pointer to a constant null-terminated character string which
*        contains the name of the class to which the new object
*        belongs (it is this pointer value that will subsequently be
*        returned by the astGetClass method).
*     source
*        Pointer to a "source" function which will be used to obtain
*        lines of input text. Generally, this will be obtained by
*        casting a pointer to a source function which is compatible
*        with the "source_wrap" wrapper function (below). The pointer
*        should later be cast back to its original type by the
*        "source_wrap" function before the function is invoked.
*
*        If "source" is NULL, the Channel will read from standard
*        input instead.
*     source_wrap
*        Pointer to a function which can be used to invoke the
*        "source" function supplied (above). This wrapper function is
*        necessary in order to hide variations in the nature of the
*        source function, such as may arise when it is supplied by a
*        foreign (non-C) language interface.
*
*        The single parameter of the "source_wrap" function is a
*        pointer to the "source" function, and it should cast this
*        function pointer (as necessary) and invoke the function with
*        appropriate arguments to obtain the next line of input
*        text. The "source_wrap" function should then return a pointer
*        to a dynamically allocated, null terminated string containing
*        the text that was read. The string will be freed (using
*        astFree) when no longer required and the "source_wrap"
*        function need not concern itself with this. A NULL pointer
*        should be returned if there is no more input to read.
*
*        If "source_wrap" is NULL, the Channel will read from standard
*        input instead.
*     sink
*        Pointer to a "sink" function which will be used to deliver
*        lines of output text. Generally, this will be obtained by
*        casting a pointer to a sink function which is compatible with
*        the "sink_wrap" wrapper function (below). The pointer should
*        later be cast back to its original type by the "sink_wrap"
*        function before the function is invoked.
*
*        If "sink" is NULL, the Channel will write to standard output
*        instead.
*     sink_wrap
*        Pointer to a function which can be used to invoke the "sink"
*        function supplied (above). This wrapper function is necessary
*        in order to hide variations in the nature of the sink
*        function, such as may arise when it is supplied by a foreign
*        (non-C) language interface.
*
*        The first parameter of the "sink_wrap" function is a pointer
*        to the "sink" function, and the second parameter is a pointer
*        to a const, null-terminated character string containing the
*        text to be written.  The "sink_wrap" function should cast the
*        "sink" function pointer (as necessary) and invoke the
*        function with appropriate arguments to deliver the line of
*        output text. The "sink_wrap" function then returns void.
*
*        If "sink_wrap" is NULL, the Channel will write to standard
*        output instead.

*  Returned Value:
*     A pointer to the new Channel.

*  Notes:
*     - A null pointer will be returned if this function is invoked
*     with the global error status set, or if it should fail for any
*     reason.
*-
*/

/* Local Variables: */
   AstChannel *new;              /* Pointer to new Channel */

/* Check the global status. */
   if ( !astOK ) return NULL;

/* If necessary, initialise the virtual function table. */
   if ( init ) astInitChannelVtab( vtab, name );

/* Initialise an Object structure (the parent class) as the first
   component within the Channel structure, allocating memory if
   necessary. */
   new = (AstChannel *) astInitObject( mem, size, 0,
                                       (AstObjectVtab *) vtab, name );

   if ( astOK ) {

/* Initialise the Channel data. */
/* ---------------------------- */
/* Save the pointers to the source and sink functions and the wrapper
   functions that invoke them. */
      new->source = source;
      new->source_wrap = source_wrap;
      new->sink = sink;
      new->sink_wrap = sink_wrap;

/* Indicate no input or output files have been associated with the
   Channel. */
      new->fd_in = NULL;
      new->fn_in = NULL;
      new->fd_out = NULL;
      new->fn_out = NULL;

/* Set all attributes to their undefined values. */
      new->comment = -INT_MAX;
      new->full = -INT_MAX;
      new->indent = -INT_MAX;
      new->report_level = -INT_MAX;
      new->skip = -INT_MAX;
      new->strict = -INT_MAX;
      new->data = NULL;
      new->warnings = NULL;
      new->nwarn = 0;

/* If an error occurred, clean up by deleting the new object. */
      if ( !astOK ) new = astDelete( new );
   }

/* Return a pointer to the new object. */
   return new;
}

void astInitChannelVtab_(  AstChannelVtab *vtab, const char *name, int *status ) {
/*
*+
*  Name:
*     astInitChannelVtab

*  Purpose:
*     Initialise a virtual function table for a Channel.

*  Type:
*     Protected function.

*  Synopsis:
*     #include "channel.h"
*     void astInitChannelVtab( AstChannelVtab *vtab, const char *name )

*  Class Membership:
*     Channel vtab initialiser.

*  Description:
*     This function initialises the component of a virtual function
*     table which is used by the Channel class.

*  Parameters:
*     vtab
*        Pointer to the virtual function table. The components used by
*        all ancestral classes will be initialised if they have not already
*        been initialised.
*     name
*        Pointer to a constant null-terminated character string which contains
*        the name of the class to which the virtual function table belongs (it
*        is this pointer value that will subsequently be returned by the Object
*        astClass function).
*-
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
   AstObjectVtab *object;        /* Pointer to Object component of Vtab */

/* Check the local error status. */
   if ( !astOK ) return;

/* Get a pointer to the thread specific global data structure. */
   astGET_GLOBALS(NULL);

/* Initialize the component of the virtual function table used by the
   parent class. */
   astInitObjectVtab( (AstObjectVtab *) vtab, name );

/* Store a unique "magic" value in the virtual function table. This
   will be used (by astIsAChannel) to determine if an object belongs
   to this class.  We can conveniently use the address of the (static)
   class_check variable to generate this unique value. */
   vtab->id.check = &class_check;
   vtab->id.parent = &(((AstObjectVtab *) vtab)->id);

/* Initialise member function pointers. */
/* ------------------------------------ */
/* Store pointers to the member functions (implemented here) that
   provide virtual methods for this class. */
   vtab->AddWarning = AddWarning;
   vtab->ClearComment = ClearComment;
   vtab->ClearFull = ClearFull;
   vtab->ClearSkip = ClearSkip;
   vtab->ClearStrict = ClearStrict;
   vtab->GetComment = GetComment;
   vtab->GetFull = GetFull;
   vtab->GetNextData = GetNextData;
   vtab->GetNextText = GetNextText;
   vtab->GetSkip = GetSkip;
   vtab->GetStrict = GetStrict;
   vtab->Warnings = Warnings;
   vtab->PutNextText = PutNextText;
   vtab->Read = Read;
   vtab->ReadClassData = ReadClassData;
   vtab->ReadDouble = ReadDouble;
   vtab->ReadInt = ReadInt;
   vtab->ReadInt64 = ReadInt64;
   vtab->ReadObject = ReadObject;
   vtab->ReadString = ReadString;
   vtab->SetComment = SetComment;
   vtab->SetFull = SetFull;
   vtab->SetSkip = SetSkip;
   vtab->SetStrict = SetStrict;
   vtab->TestComment = TestComment;
   vtab->TestFull = TestFull;
   vtab->TestSkip = TestSkip;
   vtab->TestStrict = TestStrict;
   vtab->Write = Write;
   vtab->WriteBegin = WriteBegin;
   vtab->WriteDouble = WriteDouble;
   vtab->WriteEnd = WriteEnd;
   vtab->WriteInt = WriteInt;
   vtab->WriteInt64 = WriteInt64;
   vtab->WriteIsA = WriteIsA;
   vtab->WriteObject = WriteObject;
   vtab->WriteString = WriteString;
   vtab->WriteFlush = WriteFlush;
   vtab->PutChannelData = PutChannelData;

   vtab->ClearReportLevel = ClearReportLevel;
   vtab->GetReportLevel = GetReportLevel;
   vtab->SetReportLevel = SetReportLevel;
   vtab->TestReportLevel = TestReportLevel;

   vtab->ClearIndent = ClearIndent;
   vtab->GetIndent = GetIndent;
   vtab->SetIndent = SetIndent;
   vtab->TestIndent = TestIndent;

   vtab->ClearSourceFile = ClearSourceFile;
   vtab->GetSourceFile = GetSourceFile;
   vtab->SetSourceFile = SetSourceFile;
   vtab->TestSourceFile = TestSourceFile;

   vtab->ClearSinkFile = ClearSinkFile;
   vtab->GetSinkFile = GetSinkFile;
   vtab->SetSinkFile = SetSinkFile;
   vtab->TestSinkFile = TestSinkFile;

/* Save the inherited pointers to methods that will be extended, and
   replace them with pointers to the new member functions. */
   object = (AstObjectVtab *) vtab;

   parent_clearattrib = object->ClearAttrib;
   object->ClearAttrib = ClearAttrib;
   parent_getattrib = object->GetAttrib;
   object->GetAttrib = GetAttrib;
   parent_setattrib = object->SetAttrib;
   object->SetAttrib = SetAttrib;
   parent_testattrib = object->TestAttrib;
   object->TestAttrib = TestAttrib;

/* Declare the destructor and copy constructor. */
   astSetDelete( (AstObjectVtab *) vtab, Delete );
   astSetCopy( (AstObjectVtab *) vtab, Copy );

/* Declare the Dump function for this class. There is no destructor or
   copy constructor. */
   astSetDump( vtab, Dump, "Channel", "Basic I/O Channel" );

/* If we have just initialised the vtab for the current class, indicate
   that the vtab is now initialised, and store a pointer to the class
   identifier in the base "object" level of the vtab. */
   if( vtab == &class_vtab ) {
      class_init = 1;
      astSetVtabClassIdentifier( vtab, &(vtab->id) );
   }
}

static char *InputTextItem( AstChannel *this, int *status ) {
/*
*  Name:
*     InputTextItem

*  Purpose:
*     Read the next item from a data source as text.

*  Type:
*     Private function.

*  Synopsis:
*     #include "channel.h"
*     char *InputTextItem( AstChannel *this )

*  Class Membership:
*     Channel member function.

*  Description:
*     This function reads the next input data item as text from the
*     data source associated with a Channel. It is similar to the
*     astGetNextText method (which it invokes), except that it strips
*     off any comments along with leading and trailing white
*     space. Input lines which are empty or do not contain significant
*     characters (e.g. all comment) are skipped, so that only
*     significant lines are returned.
*
*     Each line is returned as a pointer to a null-terminated string
*     held in dynamic memory, and it is the caller's responsibility to
*     free this memory (using astFree) when it is no longer
*     required. A NULL pointer is returned if there are no more input
*     lines to be read.

*  Parameters:
*     this
*        Pointer to the Channel.

*  Returned Value:
*     Pointer to a null-terminated string containing the input line
*     (held in dynamically allocated memory, which must be freed by
*     the caller when no longer required). A NULL pointer is returned
*     if there are no more input lines to be read.

*  Notes:
*     - A NULL pointer will be returned if this function is invoked
*     with the global error status set, or if it should fail for any
*     reason.
*/

/* Local Variables: */
   char *line;                   /* Pointer to line data to be returned */
   int i;                        /* Loop counter for line characters */
   int j;                        /* Counter for characters */
   int len;                      /* Length of result line */
   int nonspace;                 /* Non-space character encountered? */
   int quoted;                   /* Character is inside quotes? */

/* Initialise. */
   line = NULL;

/* Check the global error status. */
   if ( !astOK ) return line;

/* Loop to read input lines until one is found which contains useful
   characters or end of file is reached (or a read error occurs). */
   while ( !line && ( line = astGetNextText( this ) ) && astOK ) {

/* Loop to remove comments and leading and trailing white space. */
      len = 0;
      nonspace = 0;
      quoted = 0;
      for ( i = j = 0; line[ i ]; i++ ) {

/* Note quote characters and ignore all text after the first unquoted
   comment character. */
         if ( line[ i ] == '"' ) quoted = !quoted;
         if ( ( line[ i ] == '#' ) && !quoted ) break;

/* Note the first non-space character and ignore everything before
   it. */
         if ( ( nonspace = nonspace || !isspace( line[ i ] ) ) ) {

/* Move each character to its new position in the string. */
            line[ j++ ] = line[ i ];

/* Note the final length of the string (ignoring trailing spaces). */
            if ( !isspace( line[ i ] ) ) len = j;
         }
      }

/* If the string is not empty, terminate it. */
     if ( len ) {
        line[ len ] = '\0';

/* Otherwise, free the memory used for the string so that another
   input line will be read. */
      } else {
         line = astFree( line );
      }
   }

/* Return the result pointer. */
   return line;

/* Undefine macros local to this function. */
#undef MIN_CHARS
}

static AstChannelValue *LookupValue( const char *name, int *status ) {
/*
*  Name:
*     LookupValue

*  Purpose:
*     Look up a Value structure by name.

*  Type:
*     Private function.

*  Synopsis:
*     #include "channel.h"
*     AstChannelValue *LookupValue( const char *name )

*  Class Membership:
*     Channel member function.

*  Description:
*     This function searches the current values list (i.e. at the
*     current nesting level) to identify a Value structure with a
*     specified name. If one is found, it is removed from the list and
*     a pointer to it is returned. If no suitable Value can be found,
*     a NULL pointer is returned instead.

*  Parameters:
*     name
*        Pointer to a constant null-terminated character string
*        containing the name of the required Value. This must be in
*        lower case with no surrounding white space. Note that names
*        longer than NAME_MAX characters will not match any Value.

*  Returned value:
*     Pointer to the required Value structure, or NULL if no suitable
*     Value exists.

*  Notes:
*     - The returned pointer refers to a dynamically allocated
*     structure and it is the callers responsibility to free this when
*     no longer required. The FreeValue function must be used for this
*     purpose.
*     - A NULL pointer will be returned if this function is invoked
*     with the global error status set, or if it should fail for any
*     reason.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   AstChannelValue **head;       /* Address of head of list pointer */
   AstChannelValue *result;      /* Pointer value to return */
   AstChannelValue *value;       /* Pointer to list element */
   int indx;                     /* Index into the hash table */
   unsigned long hash;           /* Hash value for current name */

/* Initialise. */
   result = NULL;

/* Check the global error status. */
   if ( !astOK ) return result;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(NULL);

/* Check that the "values_ok" flag is set. If not, the Values in the
   values list belong to a different class to that of the current
   class loader, so we cannot return any Value. */
   if ( values_ok[ nest ] ) {

/* The value in the values list (at the current nesting level) is a
   pointer to an array containing HASHMAP_SIZE elements, each of which
   holds a pointer to the first element in a linked list of values.
   Values are appended to the appropriate linked list, depending on the
   hash of the associated name. Use the hash function to get the index
   of the element holding the appropriate linked list for the name of the
   required value. */
      indx = HashFun( name, HASHMAP_SIZE - 1, &hash );

/* Obtain the address of the current "head of list" pointer for the
   values list (at the current nesting level). */
      head = values_list[ nest ] + indx;

/* Obtain the head of list pointer itself and check the list is not
   empty. */
      if ( ( value = *head ) ) {

/* Loop to inspect each list element. */
         while ( 1 ) {

/* If a name match is found, remove the element from the list, return
   a pointer to it and quit searching. */
            if ( !strcmp( name, value->name ) ) {
               RemoveValue( value, head, status );
               result = value;
               break;
            }

/* Follow the list until we return to the head. */
            value = value->flink;
            if ( value == *head ) break;
         }
      }
   }

/* Return the result. */
   return result;
}

static void OutputTextItem( AstChannel *this, const char *line, int *status ) {
/*
*  Name:
*     OutputTextItem

*  Purpose:
*     Output a data item formatted as text.

*  Type:
*     Private function.

*  Synopsis:
*     #include "channel.h"
*     void OutputTextItem( AstChannel *this, const char *line, int *status )

*  Class Membership:
*     Channel member function.

*  Description:
*     This function outputs a data item formatted as a text string to
*     a data sink associated with a Channel. It keeps track of the
*     number of items written.

*  Parameters:
*     this
*        Pointer to the Channel.
*     line
*        Pointer to a constant null-terminated string containing the
*        data item to be output (no newline character should be
*        appended).
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */

/* Check the global error status. */
   if ( !astOK ) return;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Write out the line of text using the astPutNextText method (which
   may be over-ridden). */
   astPutNextText( this, line );

/* If successful, increment the count of items written. */
   if ( astOK ) items_written++;
}

static void PutChannelData( AstChannel *this, void *data, int *status ) {
/*
c++
*  Name:
*     astPutChannelData

*  Purpose:
*     Store arbitrary data to be passed to a source or sink function.

*  Type:
*     Public function.

*  Synopsis:
*     #include "channel.h"
*     void astPutChannelData( AstChannel *this, void *data )

*  Class Membership:
*     Channel method.

*  Description:
*     This function stores a supplied arbitrary pointer in the Channel.
*     When a source or sink function is invoked by the Channel, the
*     invoked function can use the astChannelData macro to retrieve the
*     pointer. This provides a thread-safe alternative to passing file
*     descriptors, etc, via global static variables.

*  Parameters:
*     this
*        Pointer to the Channel.
*     data
*        A pointer to be made available to the source and sink functions
*        via the astChannelData macro. May be NULL.

*  Applicability:
*     Channel
*        All Channels have this function.

*  Notes:
*     - This routine is not available in the Fortran 77 interface to
*     the AST library.
c--
*/

/* Check the global error status. */
   if ( !astOK ) return;

/* Store the pointer. */
   this->data = data;
}

static void PutNextText( AstChannel *this, const char *line, int *status ) {
/*
*+
*  Name:
*     astPutNextText

*  Purpose:
*     Write a line of output text to a data sink.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     void astPutNextText( AstChannel *this, const char *line )

*  Class Membership:
*     Channel method.

*  Description:
*     This function writes an output line of text to a data sink
*     associated with a Channel.

*  Parameters:
*     this
*        Pointer to the Channel.
*     line
*        Pointer to a constant null-terminated string containing the
*        line of output text to be written (no newline character
*        should be appended).

*  Notes:
*     - This method is provided primarily so that derived classes may
*     over-ride it in order to write to alternative (textual) data
*     sinks.
*-
*/

/* Local Constants: */
#define ERRBUF_LEN 80

/* Local Variables: */
   char *errstat;                /* Pointer for system error message */
   char errbuf[ ERRBUF_LEN ];    /* Buffer for system error message */
   const char *sink_file;        /* Path to output sink file */
   const char *source_file;      /* Path to output source file */

/* Check the global error status. */
   if ( !astOK ) return;

/* If the SinkFile attribute of the Channel specifies an output file,
   but no output file has yet been opened, open it now. Report an error
   if it is the same as the source file. */
   if( astTestSinkFile( this ) && !this->fd_out ) {
      sink_file = astGetSinkFile( this );

      if( this->fd_out ) {
         source_file = astGetSourceFile( this );
         if( astOK && !strcmp( sink_file, source_file ) ) {
            astError( AST__WRERR, "astWrite(%s): Failed to open output "
                      "SinkFile '%s' - the file is currently being used "
                      "as the input SourceFile.", status, astGetClass( this ),
                      sink_file );
         }
      }

      if( astOK ) {
         this->fd_out = fopen( sink_file, "w" );
         if( !this->fd_out ) {
            if ( errno ) {
#if HAVE_STRERROR_R
               strerror_r( errno, errbuf, ERRBUF_LEN );
               errstat = errbuf;
#else
               errstat = strerror( errno );
#endif
               astError( AST__WRERR, "astWrite(%s): Failed to open output "
                         "SinkFile '%s' - %s.", status, astGetClass( this ),
                         sink_file, errstat );
            } else {
               astError( AST__WRERR, "astWrite(%s): Failed to open output "
                         "SinkFile '%s'.", status, astGetClass( this ),
                         sink_file );
            }
         }
      }
   }

/* Check no error occurred above. */
   if( astOK ) {

/* If an active output file descriptor is stored in the channel, write
   the text to it, with a newline appended. */
      if( this->fd_out ) {
         (void) fprintf( this->fd_out, "%s\n", line );

/* Otherwise, if a sink function (and its wrapper function) is defined for
   the Channel, use the wrapper function to invoke the sink function to
   output the text line. Since we are about to call an externally supplied
   function which may not be thread-safe, lock a mutex first. Also store
   the channel data pointer in a global variable so that it can be accessed
   in the source function using macro astChannelData. */
      } else if ( this->sink && this->sink_wrap ) {
         astStoreChannelData( this );
         LOCK_MUTEX2;
         ( *this->sink_wrap )( *this->sink, line, status );
         UNLOCK_MUTEX2;

/* Otherwise, simply write the text to standard output with a newline
   appended. */
      } else {
         (void) printf( "%s\n", line );
      }
   }
}

static AstObject *Read( AstChannel *this, int *status ) {
/*
*++
*  Name:
c     astRead
f     AST_READ

*  Purpose:
*     Read an Object from a Channel.

*  Type:
*     Public function.

*  Synopsis:
c     #include "channel.h"
c     AstObject *astRead( AstChannel *this )
f     RESULT = AST_READ( THIS, STATUS )

*  Class Membership:
*     Channel method.

*  Description:
*     This function reads the next Object from a Channel and returns a
*     pointer to the new Object.

*  Parameters:
c     this
f     THIS = INTEGER (Given)
*        Pointer to the Channel.
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Returned Value:
c     astRead()
f     AST_READ = INTEGER
*        A pointer to the new Object. The class to which this will
*        belong is determined by the input data, so is not known in
*        advance.

*  Applicability:
*     FitsChan
c        All successful use of astRead on a FitsChan is destructive, so that
f        All successful use of AST_READ on a FitsChan is destructive, so that
*        FITS header cards are consumed in the process of reading an Object,
*        and are removed from the FitsChan (this deletion can be prevented
*        for specific cards by calling the FitsChan
c        astRetainFits function).
f        AST_RETAINFITS routine).
*        An unsuccessful call of
c        astRead
f        AST_READ
*        (for instance, caused by the FitsChan not containing the necessary
*        FITS headers cards needed to create an Object) results in the
*        contents of the FitsChan being left unchanged.
*     StcsChan
*        The AST Object returned by a successful use of
c        astRead
f        AST_READ
*        on an StcsChan, will be either a Region or a KeyMap, depending
*        on the values of the StcsArea, StcsCoords and StcsProps
*        attributes. See the documentation for these attributes for further
*        information.

*  Notes:
*     - A null Object pointer (AST__NULL) will be returned, without
*     error, if the Channel contains no further Objects to be read.
*     - A null Object pointer will also be returned if this function
c     is invoked with the AST error status set, or if it should fail
f     is invoked with STATUS set to an error value, or if it should fail
*     for any reason.
*--
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   AstLoaderType *loader;        /* Pointer to loader for Object */
   AstObject *new;               /* Pointer to new Object */
   char *class;                  /* Pointer to Object class name string */
   char *name;                   /* Pointer to data item name */
   int skip;                     /* Skip non-AST data? */
   int top;                      /* Reading top-level Object definition? */

/* Initialise. */
   new = NULL;

/* Check the global error status. */
   if ( !astOK ) return new;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Determine if we are reading a top-level (i.e. user-level) Object
   definition, as opposed to the definition of an Object contained
   within another Object. This is indicated by the current nesting
   level. */
   top = ( nest == -1 );

/* If reading a top-level object, determine if data lying in between
   Object definitions in the input data stream are to be skipped. */
   skip = ( top && astGetSkip( this ) );

/* Read the next input data item. If we are reading a top-level Object
   definition, skip any unrelated data beforehand. Otherwise read the
   data strictly as it comes (there should be no unrelated data
   embedded within Object definitions themselves). */
   astGetNextData( this, skip, &name, &class );

/* If no suitable data item was found (and no error occurred), we have
   reached the end of data. For a top-level Object a NULL Object
   pointer is simply returned, but for a nested Object this indicates
   that part of the Object definition is missing, so report an
   error. */
   if ( astOK ) {
      if ( !name ) {
         if ( !top ) {
            astError( AST__EOCHN,
                      "astRead(%s): End of input encountered while trying to "
                      "read an AST Object.", status, astGetClass( this ) );
         }

/* If a data item was found, check it is a "Begin" item. If not, there
   is a data item missing, so report an error and free all memory. */
      } else if ( strcmp( name, "begin" ) ) {
         astError( AST__BADIN,
                   "astRead(%s): Missing \"Begin\" when expecting an Object.", status,
                   astGetClass( this ) );
         name = astFree( name );
         if ( class ) class = astFree( class );

/* If the required "Begin" item was found, free the memory used for the
   name string. */
      } else {
         name = astFree( name );

/* Use the associated class name to locate the loader for that
   class. This function will then be used to build the Object. */
         loader = astGetLoader( class, status );

/* Extend all necessary stack arrays to accommodate entries for the
   next nesting level (this allocates space if none has yet been
   allocated). */
         end_of_object = astGrow( end_of_object, nest + 2, sizeof( int ) );
         object_class = astGrow( object_class, nest + 2, sizeof( char * ) );
         values_class = astGrow( values_class, nest + 2, sizeof( char * ) );
         values_list = astGrow( values_list, nest + 2, sizeof( AstChannelValue ** ) );
         values_ok = astGrow( values_ok, nest + 2, sizeof( int ) );

/* If an error occurred, free the memory used by the class string,
   which will not now be used. */
         if ( !astOK ) {
            class = astFree( class );

/* Otherwise, increment the nesting level and initialise the new stack
   elements for this new level. This includes clearing the
   "end_of_object" flag so that ReadClassData can read more data, and
   storing the class name of the object we are about to read. */
         } else {
            nest++;
            end_of_object[ nest ] = 0;
            object_class[ nest ] = class;
            values_class[ nest ] = NULL;
            values_ok[ nest ] = 0;
            values_list[ nest ] = astCalloc( HASHMAP_SIZE,
                                             sizeof( AstChannelValue * ) );


/* Invoke the loader, which reads the Object definition from the input
   data stream and builds the Object. Supply NULL/zero values to the
   loader so that it will substitute values appropriate to its own
   class. */
            new = (*loader)( NULL, (size_t) 0, NULL, NULL, this, status );

/* Clear the values list for the current nesting level. If the list
   has not been read or any Values remain in it, an error will
   result. */
            ClearValues( this, status );

/* If no error has yet occurred, check that the "end_of_object" flag
   has been set. If not, the input data were not correctly terminated,
   so report an error. */
            if ( astOK && !end_of_object[ nest ] ) {
               astError( AST__BADIN,
                         "astRead(%s): Unexpected end of input (missing end "
                         "of %s).", status,
                         astGetClass( this ), object_class[ nest ] );
            }

/* If an error occurred, report contextual information. Only do this
   for top-level Objects to avoid multple messages. */
            if ( !astOK && top ) {
               astError( astStatus, "Error while reading a %s from a %s.", status,
                         class, astGetClass( this ) );
            }

/* Clear the Object's class string, freeing the associated memory
   (note this is the memory allocated for the "class" string
   earlier). */
            object_class[ nest ] = astFree( object_class[ nest ] );

/* Clear the hash table. */
            values_list[ nest ] = astFree( values_list[ nest ] );

/* Restore the previous nesting level. */
            nest--;
         }

/* Once the top-level Object has been built, free the memory used by
   the stack arrays. */
         if ( top ) {
            end_of_object = astFree( end_of_object );
            object_class = astFree( object_class );
            values_class = astFree( values_class );
            values_list = astFree( values_list );
            values_ok = astFree( values_ok );
         }
      }
   }

/* If an error occurred, clean up by deleting the new Object and
   return a NULL pointer. */
   if ( !astOK ) new = astDelete( new );

/* Return the pointer to the new Object. */
   return new;
}

static void ReadClassData( AstChannel *this, const char *class, int *status ) {
/*
*+
*  Name:
*     astReadClassData

*  Purpose:
*     Read values from a data source for a class loader.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     void astReadClassData( AstChannel *this, const char *class )

*  Class Membership:
*     Channel method.

*  Description:
*     This function reads the data for a class from the data source
*     associated with a Channel, so as to provide values for
*     initialising the instance variables of that class as part of
*     building a complete Object. This function should be invoked by
*     the loader for each class immediately before it attempts to read
*     these values.
*
*     The values read are placed into the current values list by this
*     function. They may then be read from this list by the class
*     loader making calls to astReadDouble, astReadInt, astReadObject
*     and astReadString. The order in which values are read by the
*     loader is unimportant (although using the same order for reading
*     as for writing will usually be more efficient) and values are
*     removed from the list as they are read.

*  Parameters:
*     this
*        Pointer to the Channel.
*     class
*        A pointer to a constant null-terminated string containing the
*        name of the class whose loader is requesting the data (note
*        this is not usually the same as the class name of the Object
*        being built). This value allows the class structure of the
*        input data to be validated.
*-
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   AstObject *object;            /* Pointer to new Object */
   AstChannelValue *value;       /* Pointer to Value structure */
   char *name;                   /* Pointer to data item name string */
   char *val;                    /* Pointer to data item value string */
   int done;                     /* All class data read? */
   int indx;                     /* Index into hash table */
   unsigned long hash;           /* Hash value for current name */

/* Check the global error status. */
   if ( !astOK ) return;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* If the "values_ok" flag is set, this indicates that the values list
   (at the current nesting level) has been filled by a previous
   invocation of this function and has then been read by the
   appropriate class loader. In this case, clear any entries which may
   remain in the current values list. If any such entries are found,
   they represent input data that were not read, so an error will
   result. */
   if ( values_ok[ nest ] ) ClearValues( this, status );

/* If "values_class" is non-NULL, this indicates that the values list
   (at the current nesting level) has been filled by a previous
   invocation of this function, but that the values belong to a class
   whose loader has not yet tried to read them. In this case, we must
   continue to keep the values until they are needed, so we do not
   read any more input data this time. */
   if ( values_class[ nest ] ) {

/* If the class to which the previously saved values belong matches
   the class we now want values for, set the "values_ok" flag. This
   then allows the values to be accessed (by LookupValue). */
      values_ok[ nest ] = !strcmp( values_class[ nest ], class );

/* If the current values list is empty, we must read in values for the
   next class that appears in the input data. However, first check
   that the "end_of_object" flag has not been set. If it has, we have
   already reached the end of this Object's data, so there is some
   kind of problem with the order in which class loaders have been
   invoked. This will probably never happen, but report an error if
   necessary. */
   } else if ( end_of_object[ nest ] ) {
      astError( AST__LDERR,
                "astRead(%s): Invalid attempt to read further %s data "
                "following an end of %s.", status,
                astGetClass( this ), class, object_class[ nest ] );
      astError( AST__LDERR,
                "Perhaps the wrong class loader has been invoked?" , status);

/* If we need new values, loop to read input data items until the end
   of the data for a class is reached. */
   } else {
      done = 0;
      while ( astOK && !done ) {

/* Read the next input data item. */
         astGetNextData( this, 0, &name, &val );
         if ( astOK ) {

/* Unexpected end of input. */
/* ------------------------ */
/* If no "name" value is returned, we have reached the end of the
   input data stream without finding the required end of class
   terminator, so report an error. */
            if ( !name ) {
               astError( AST__EOCHN,
                         "astRead(%s): Unexpected end of input (missing end "
                         "of %s).", status,
                         astGetClass( this ), object_class[ nest ] );

/* "IsA" item. */
/* ----------- */
/* Otherwise, if an "IsA" item was read, it indicates the end of the
   data for a class. Store the pointer to the name of this class in
   "values_class" and note whether this is the class whose data we
   wanted in "values_ok". If the data we have read do not belong to
   the class we wanted, they will simply be kept until the right class
   comes looking for them. */
            } else if ( !strcmp( name, "isa" ) ) {
               values_class[ nest ] = val;
               values_ok[ nest ] = !strcmp( val, class );

/* Free the memory holding the name string. */
               name = astFree( name );

/* Note we have finished reading class data. */
               done = 1;

/* "End" item. */
/* ----------- */
/* If an "End" item was read, it indicates the end of the data both
   for a class and for the current Object definition as a whole. Set
   the "end_of_object" flag (for the current nesting level) which
   prevents any further data being read for this Object. This flag is
   also used (by Read) to check that an "End" item was actually
   read. */
            } else if ( !strcmp( name, "end" ) ) {
               end_of_object[ nest ] = 1;

/* Check that the class name in the "End" item matches that of the
   Object being built. If so, store the pointer to the name of this
   class in "values_class" and note whether this is the class whose
   data we wanted in "values_ok". If the data we have read do not
   belong to the class we wanted, they will simply be kept until the
   right class comes looking for them. */
               if ( !strcmp( val, object_class[ nest ] ) ) {
                  values_class[ nest ] = val;
                  values_ok[ nest ] = !strcmp( class, val );

/* If the "End" item contains the wrong class name (i.e. not matching
   the corresponding "Begin" item), then report an error. */
               } else {
                  astError( AST__BADIN,
                            "astRead(%s): Bad class structure in input data.", status,
                            astGetClass( this ) );
                  astError( AST__BADIN,
                            "End of %s read when expecting end of %s.", status,
                            val, object_class[ nest ] );

/* Free the memory used by the class string, which will not now be
   used. */
                  val = astFree( val );
               }

/* Free the memory holding the name string. */
               name = astFree( name );

/* Note we have finished reading class data. */
               done = 1;

/* String value. */
/* ------------- */
/* If any other name is obtained and "val" is not NULL, we have read a
   non-Object value, encoded as a string. Allocate memory for a Value
   structure to describe it. */
            } else if ( val ) {
               value = astMalloc( sizeof( AstChannelValue ) );
               if ( astOK ) {

/* Store pointers to the name and value string in the Value structure
   and note this is not an Object value. */
                  value->name = name;
                  value->ptr.string = val;
                  value->is_object = 0;

/* Append the Value structure to the values list for the current
   nesting level. */
                  indx = HashFun( name, HASHMAP_SIZE - 1, &hash );
                  AppendValue( value, values_list[ nest ] + indx, status );

/* If an error occurred, free the memory holding the "name" and "val"
   strings. */
               } else {
                  name = astFree( name );
                  val = astFree( val );
               }

/* Object value. */
/* ------------- */
/* If "val" is NULL, we have read an Object item, and the Object
   definition should follow. Allocate memory for a Value structure to
   describe it. */
            } else {
               value = astMalloc( sizeof( AstChannelValue ) );

/* Invoke astRead to read the Object definition from subsequent data
   items and to build the Object, returning a pointer to it. This will
   result in recursive calls to the current function, but as these
   will use higher nesting levels they will not interfere with the
   current invocation. */
               astreadclassdata_msg = 0;
               object = astRead( this );
               if ( astOK ) {

/* Store pointers to the name and Object in the Value structure and
   note this is an Object value. */
                  value->name = name;
                  value->ptr.object = object;
                  value->is_object = 1;

/* Append the Value structure to the values list for the current
   nesting level. */
                  indx = HashFun( name, HASHMAP_SIZE - 1, &hash );
                  AppendValue( value, values_list[ nest ] + indx, status );

/* If an error occurred, report a contextual error maessage and set
   the "astreadclassdata_msg" flag (this prevents multiple messages if this function is
   invoked recursively to deal with nested Objects). */
               } else {
                  if ( !astreadclassdata_msg ) {
                     astError( astStatus,
                               "Failed to read the \"%s\" Object value.", status,
                               name );
                     astreadclassdata_msg = 1;
                  }

/* Free the memory holding the "name" string and any Value structure
   that was allocated. */
                  name = astFree( name );
                  value = astFree( value );
               }
            }
         }
      }
   }
}

static double ReadDouble( AstChannel *this, const char *name, double def, int *status ) {
/*
*+
*  Name:
*     astReadDouble

*  Purpose:
*     Read a double value as part of loading a class.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     double astReadDouble( AstChannel *this, const char *name, double def )

*  Class Membership:
*     Channel method.

*  Description:
*     This function searches the current values list of a Channel to
*     identify a double value with a specified name. If such a value
*     is found, it is returned, otherwise a default value is returned
*     instead.
*
*     This function should only be invoked from within the loader
*     function associated with a class, in order to return a double
*     value to be assigned to an instance variable. It must be
*     preceded by a call to the astReadClassData function, which loads
*     the values associated with the class into the current values
*     list from the input data source.

*  Parameters:
*     this
*        Pointer to the Channel.
*     name
*        Pointer to a constant null-terminated character string
*        containing the name of the required value. This must be in
*        lower case with no surrounding white space. Note that names
*        longer than 6 characters will not match any value.
*     def
*        If no suitable value can be found (e.g. it is absent from the
*        data stream being read), then this value will be returned
*        instead.

*  Returned Value:
*     The required value, or the default if the value was not found.

*  Notes:
*     - A value of 0.0 will be returned if this function is invoked
*     with the global error status set, or if it should fail for any
*     reason.
*-
*/

/* Local Variables: */
   AstChannelValue *value;       /* Pointer to required Value structure */
   double result;                /* Value to be returned */
   int nc;                       /* Number of characters read by astSscanf */

/* Initialise. */
   result = 0.0;

/* Check the global error status. */
   if ( !astOK ) return result;

/* Search for a Value structure with the required name in the current
   values list.*/
   value = LookupValue( name, status );
   if ( astOK ) {

/* If a Value was found, check that it describes a string (as opposed
   to an Object). */
      if ( value ) {
         if ( !value->is_object ) {

/* If so, then attempt to decode the string to give a double value,
   checking that the entire string is read (and checking for the magic string
   used to represent bad values). If this fails, then the wrong name has
   probably been given, or the input data are corrupt, so report an error. */
            nc = 0;
            if ( ( 0 == astSscanf( value->ptr.string, " " BAD_STRING " %n",
                                                      &nc ) )
                    && ( nc >= (int) strlen( value->ptr.string ) ) ) {
               result = AST__BAD;

            } else if ( !( ( 1 == astSscanf( value->ptr.string, " %lf %n",
                                                      &result, &nc ) )
                    && ( nc >= (int) strlen( value->ptr.string ) ) ) ) {
               astError( AST__BADIN,
                         "astRead(%s): The value \"%s = %s\" cannot "
                         "be read as a double precision floating point "
                         "number.", status, astGetClass( this ),
                         value->name, value->ptr.string );

            } else if( !astISFINITE( result ) ) {
               astError( AST__BADIN,
                         "astRead(%s): Illegal double precision floating "
                         "point value \"%s\" read for \"%s\".", status,
                         astGetClass( this ), value->ptr.string, value->name );

            }

/* Report a similar error if the Value does not describe a string. */
         } else {
            astError( AST__BADIN,
                      "astRead(%s): The Object \"%s = <%s>\" cannot "
                      "be read as a double precision floating point number.", status,
                      astGetClass( this ),
                      value->name, astGetClass( value->ptr.object ) );
         }

/* Free the Value structure and the resources it points at. */
         value = FreeValue( value, status );

/* If no suitable Value structure was found, then use the default
   value instead. */
      } else {
         result = def;
      }
   }

/* Return the result. */
   return result;
}

static int ReadInt( AstChannel *this, const char *name, int def, int *status ) {
/*
*+
*  Name:
*     astReadInt

*  Purpose:
*     Read an int value as part of loading a class.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     int astReadInt( AstChannel *this, const char *name, int def )

*  Class Membership:
*     Channel method.

*  Description:
*     This function searches the current values list of a Channel to
*     identify an int value with a specified name. If such a value is
*     found, it is returned, otherwise a default value is returned
*     instead.
*
*     This function should only be invoked from within the loader
*     function associated with a class, in order to return an int
*     value to be assigned to an instance variable. It must be
*     preceded by a call to the astReadClassData function, which loads
*     the values associated with the class into the current values
*     list from the input data source.

*  Parameters:
*     this
*        Pointer to the Channel.
*     name
*        Pointer to a constant null-terminated character string
*        containing the name of the required value. This must be in
*        lower case with no surrounding white space. Note that names
*        longer than 6 characters will not match any value.
*     def
*        If no suitable value can be found (e.g. it is absent from the
*        data stream being read), then this value will be returned
*        instead.

*  Returned Value:
*     The required value, or the default if the value was not found.

*  Notes:
*     - A value of zero will be returned if this function is invoked
*     with the global error status set, or if it should fail for any
*     reason.
*-
*/

/* Local Variables: */
   AstChannelValue *value;       /* Pointer to required Value structure */
   int nc;                       /* Number of characters read by astSscanf */
   int result;                   /* Value to be returned */

/* Initialise. */
   result = 0;

/* Check the global error status. */
   if ( !astOK ) return result;

/* Search for a Value structure with the required name in the current
   values list.*/
   value = LookupValue( name, status );
   if ( astOK ) {

/* If a Value was found, check that it describes a string (as opposed
   to an Object). */
      if ( value ) {
         if ( !value->is_object ) {

/* If so, then attempt to decode the string to give an int value,
   checking that the entire string is read. If this fails, then the
   wrong name has probably been given, or the input data are corrupt,
   so report an error. */
            nc = 0;
            if ( !( ( 1 == astSscanf( value->ptr.string, " %d %n",
                                                      &result, &nc ) )
                    && ( nc >= (int) strlen( value->ptr.string ) ) ) ) {
               astError( AST__BADIN,
                         "astRead(%s): The value \"%s = %s\" cannot "
                         "be read as an integer.", status, astGetClass( this ),
                         value->name, value->ptr.string );
            }

/* Report a similar error if the Value does not describe a string. */
         } else {
            astError( AST__BADIN,
                      "astRead(%s): The Object \"%s = <%s>\" cannot "
                      "be read as an integer.", status, astGetClass( this ),
                      value->name, astGetClass( value->ptr.object ) );
         }

/* Free the Value structure and the resources it points at. */
         value = FreeValue( value, status );

/* If no suitable Value structure was found, then use the default
   value instead. */
      } else {
         result = def;
      }
   }

/* Return the result. */
   return result;
}

static int64_t ReadInt64( AstChannel *this, const char *name, int64_t def, int *status ) {
/*
*+
*  Name:
*     astReadInt64

*  Purpose:
*     Read a 64 bit int value as part of loading a class.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     int64_t astReadInt64( AstChannel *this, const char *name, int64_t def )

*  Class Membership:
*     Channel method.

*  Description:
*     This function searches the current values list of a Channel to
*     identify an int value with a specified name. If such a value is
*     found, it is returned, otherwise a default value is returned
*     instead.
*
*     This function should only be invoked from within the loader
*     function associated with a class, in order to return an int
*     value to be assigned to an instance variable. It must be
*     preceded by a call to the astReadClassData function, which loads
*     the values associated with the class into the current values
*     list from the input data source.

*  Parameters:
*     this
*        Pointer to the Channel.
*     name
*        Pointer to a constant null-terminated character string
*        containing the name of the required value. This must be in
*        lower case with no surrounding white space. Note that names
*        longer than 6 characters will not match any value.
*     def
*        If no suitable value can be found (e.g. it is absent from the
*        data stream being read), then this value will be returned
*        instead.

*  Returned Value:
*     The required value, or the default if the value was not found.

*  Notes:
*     - A value of zero will be returned if this function is invoked
*     with the global error status set, or if it should fail for any
*     reason.
*-
*/

/* Local Variables: */
   AstChannelValue *value;       /* Pointer to required Value structure */
   int nc;                       /* Number of characters read by astSscanf */
   int64_t result;               /* Value to be returned */

/* Initialise. */
   result = 0;

/* Check the global error status. */
   if ( !astOK ) return result;

/* Search for a Value structure with the required name in the current
   values list.*/
   value = LookupValue( name, status );
   if ( astOK ) {

/* If a Value was found, check that it describes a string (as opposed
   to an Object). */
      if ( value ) {
         if ( !value->is_object ) {

/* If so, then attempt to decode the string to give an int value,
   checking that the entire string is read. If this fails, then the
   wrong name has probably been given, or the input data are corrupt,
   so report an error. */
            nc = 0;
            if ( !( ( 1 == astSscanf( value->ptr.string, " %" SCNd64 " %n",
                                                      &result, &nc ) )
                    && ( nc >= (int) strlen( value->ptr.string ) ) ) ) {
               astError( AST__BADIN,
                         "astRead(%s): The value \"%s = %s\" cannot "
                         "be read as a 64 bit integer.", status,
                         astGetClass( this ), value->name, value->ptr.string );
            }

/* Report a similar error if the Value does not describe a string. */
         } else {
            astError( AST__BADIN,
                      "astRead(%s): The Object \"%s = <%s>\" cannot be read "
                      "as a 64 bit integer.", status, astGetClass( this ),
                      value->name, astGetClass( value->ptr.object ) );
         }

/* Free the Value structure and the resources it points at. */
         value = FreeValue( value, status );

/* If no suitable Value structure was found, then use the default
   value instead. */
      } else {
         result = def;
      }
   }

/* Return the result. */
   return result;
}

static AstObject *ReadObject( AstChannel *this, const char *name,
                              AstObject *def, int *status ) {
/*
*+
*  Name:
*     astReadObject

*  Purpose:
*     Read a (sub)Object as part of loading a class.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     AstObject *astReadObject( AstChannel *this, const char *name,
*                               AstObject *def )

*  Class Membership:
*     Channel method.

*  Description:
*     This function searches the current values list of a Channel to
*     identify an Object with a specified name. If such an Object is
*     found, a pointer to it is returned, otherwise a default pointer
*     is returned instead.
*
*     This function should only be invoked from within the loader
*     function associated with a class, in order to return an Object
*     pointer value to be assigned to an instance variable. It must be
*     preceded by a call to the astReadClassData function, which loads
*     the values associated with the class into the current values
*     list from the input data source.

*  Parameters:
*     this
*        Pointer to the Channel.
*     name
*        Pointer to a constant null-terminated character string
*        containing the name of the required Object. This must be in
*        lower case with no surrounding white space. Note that names
*        longer than 6 characters will not match any Object.
*     def
*        If no suitable Object can be found (e.g. the Object is absent
*        from the data stream being read), then a clone of this
*        default Object pointer will be returned instead (or NULL if
*        this default pointer is NULL).

*  Returned Value:
*     A pointer to the Object, or a clone of the default pointer if
*     the Object was not found.

*  Notes:
*     - A NULL pointer will be returned if this function is invoked
*     with the global error status set, or if it should fail for any
*     reason.
*-
*/

/* Local Variables: */
   AstObject *result;            /* Pointer value to return */
   AstChannelValue *value;       /* Pointer to required Value structure */

/* Initialise. */
   result = NULL;

/* Check the global error status. */
   if ( !astOK ) return result;

/* Search for a Value structure with the required name in the current
   values list.*/
   value = LookupValue( name, status );
   if ( astOK ) {

/* If a Value was found, check that it describes an Object (as opposed to
   a string). */
      if ( value ) {
         if ( value->is_object ) {

/* If so, then extract the Object pointer, replacing it with NULL. */
            result = value->ptr.object;
            value->ptr.object = NULL;

/* If the Value does not describe an Object, then the wrong name has
   probably been given, or the input data are corrupt, so report an
   error. */
         } else {
            astError( AST__BADIN,
                      "astRead(%s): The value \"%s = %s\" cannot be "
                      "read as an Object.", status, astGetClass( this ),
                      value->name, value->ptr.string );
         }

/* Free the Value structure and the resources it points at. */
         value = FreeValue( value, status );

/* If no suitable Value structure was found, clone the default
   pointer, if given. */
      } else if ( def ) {
         result = astClone( def );
      }
   }

/* Return the result. */
   return result;
}

static char *ReadString( AstChannel *this, const char *name,
                         const char *def, int *status ) {
/*
*+
*  Name:
*     astReadString

*  Purpose:
*     Read a string value as part of loading a class.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     char *astReadString( AstChannel *this, const char *name,
*                          const char *def )

*  Class Membership:
*     Channel method.

*  Description:
*     This function searches the current values list of a Channel to
*     identify a string value with a specified name. If such a value
*     is found, a pointer to the string is returned, otherwise a
*     pointer to a copy of a default string is returned instead.
*
*     This function should only be invoked from within the loader
*     function associated with a class, in order to return a string
*     pointer value to be assigned to an instance variable. It must be
*     preceded by a call to the astReadClassData function, which loads
*     the values associated with the class into the current values
*     list from the input data source.

*  Parameters:
*     this
*        Pointer to the Channel.
*     name
*        Pointer to a constant null-terminated character string
*        containing the name of the required value. This must be in
*        lower case with no surrounding white space. Note that names
*        longer than 6 characters will not match any value.
*     def
*        If no suitable string can be found (e.g. the value is absent
*        from the data stream being read), then a dynamically
*        allocated copy of the null-terminated string pointed at by
*        "def" will be made, and a pointer to this copy will be
*        returned instead (or NULL if this default pointer is NULL).

*  Returned Value:
*     A pointer to a dynamically allocated null-terminated string
*     containing the value required, or to a copy of the default
*     string if the value was not found (or NULL if the "def" pointer
*     was NULL).

*  Notes:
*     - It is the caller's responsibility to arrange for the memory
*     holding the returned string to be freed (using astFree) when it
*     is no longer required.
*     - A NULL pointer will be returned if this function is invoked
*     with the global error status set, or if it should fail for any
*     reason.
*-
*/

/* Local Variables: */
   AstChannelValue *value;       /* Pointer to required Value structure */
   char *result;                 /* Pointer value to return */

/* Initialise. */
   result = NULL;

/* Check the global error status. */
   if ( !astOK ) return result;

/* Search for a Value structure with the required name in the current
   values list.*/
   value = LookupValue( name, status );
   if ( astOK ) {

/* If a Value was found, check that it describes a string (as opposed
   to an Object). */
      if ( value ) {
         if ( !value->is_object ) {

/* If so, then extract the string pointer, replacing it with NULL. */
            result = value->ptr.string;
            value->ptr.string = NULL;

/* If the Value does not describe a string, then the wrong name has
   probably been given, or the input data are corrupt, so report an
   error. */
         } else {
            astError( AST__BADIN,
                      "astRead(%s): The Object \"%s = <%s>\" cannot "
                      "be read as a string.", status, astGetClass( this ),
                      value->name, astGetClass( value->ptr.object ) );
         }

/* Free the Value structure and the resources it points at. */
         value = FreeValue( value, status );

/* If no suitable Value structure was found, then make a dynamic copy
   of the default string (if given) and return a pointer to this. */
      } else if ( def ) {
         result = astStore( NULL, def, strlen( def ) + (size_t) 1 );
      }
   }

/* Return the result. */
   return result;
}

static void RemoveValue( AstChannelValue *value, AstChannelValue **head, int *status ) {
/*
*  Name:
*     RemoveValue

*  Purpose:
*     Remove a Value structure from a circular linked list.

*  Type:
*     Private function.

*  Synopsis:
*     #include "channel.h"
*     void RemoveValue( AstChannelValue *value, AstChannelValue **head, int *status );

*  Class Membership:
*     Channel member function.

*  Description:
*     This function removes a Value structure from a doubly linked
*     circular list of such structures. The "head of list" pointer is
*     updated to point at the element following the one removed.

*  Parameters:
*     value
*        Pointer to the structure to be removed (note that this must
*        actually be in the list, although this function does not
*        check).
*     head
*        Address of a pointer to the element at the head of the
*        list. This pointer will be updated to point at the list
*        element that follows the one removed. If the list becomes
*        empty, the pointer will be set to NULL.
*     status
*        Pointer to the inherited status variable.

*  Notes:
*     - This function does not perform error chacking and does not
*     generate errors.
*/

/* Remove the Value structure from the list by re-establishing links
   between the elements on either side of it. */
   value->blink->flink = value->flink;
   value->flink->blink = value->blink;

/* Update the head of list pointer to identify the following
   element. */
   *head = value->flink;

/* If the head of list identifies the removed element, then note that
   the list is now empty. */
   if ( *head == value ) *head = NULL;

/* Make the removed element point at itself. */
   value->flink = value;
   value->blink = value;
}

static void SetAttrib( AstObject *this_object, const char *setting, int *status ) {
/*
*  Name:
*     SetAttrib

*  Purpose:
*     Set an attribute value for a Channel.

*  Type:
*     Private function.

*  Synopsis:
*     #include "channel.h"
*     void SetAttrib( AstObject *this, const char *setting )

*  Class Membership:
*     Channel member function (over-rides the astSetAttrib protected
*     method inherited from the Object class).

*  Description:
*     This function assigns an attribute value for a Channel, the
*     attribute and its value being specified by means of a string of
*     the form:
*
*        "attribute= value "
*
*     Here, "attribute" specifies the attribute name and should be in
*     lower case with no white space present. The value to the right
*     of the "=" should be a suitable textual representation of the
*     value to be assigned and this will be interpreted according to
*     the attribute's data type.  White space surrounding the value is
*     only significant for string attributes.

*  Parameters:
*     this
*        Pointer to the Channel.
*     setting
*        Pointer to a null terminated string specifying the new attribute
*        value.
*/

/* Local Variables: */
   AstChannel *this;             /* Pointer to the Channel structure */
   int comment;                  /* Comment attribute value */
   int full;                     /* Full attribute value */
   int indent;                   /* Indent attribute value */
   int len;                      /* Length of setting string */
   int nc;                       /* Number of characters read by "astSscanf" */
   int report_level;             /* Skip attribute value */
   int skip;                     /* Skip attribute value */
   int sourcefile;               /* Offset of SourceFile string */
   int sinkfile;                 /* Offset of SinkFile string */
   int strict;                   /* Report errors instead of warnings? */

/* Check the global error status. */
   if ( !astOK ) return;

/* Obtain a pointer to the Channel structure. */
   this = (AstChannel *) this_object;

/* Obtain the length of the setting string. */
   len = (int) strlen( setting );

/* Test for each recognised attribute in turn, using "astSscanf" to parse
   the setting string and extract the attribute value (or an offset to
   it in the case of string values). In each case, use the value set
   in "nc" to check that the entire string was matched. Once a value
   has been obtained, use the appropriate method to set it. */

/* Comment. */
/* ---------*/
   if ( nc = 0,
        ( 1 == astSscanf( setting, "comment= %d %n", &comment, &nc ) )
        && ( nc >= len ) ) {
      astSetComment( this, comment );

/* Full. */
/* ----- */
   } else if ( nc = 0,
               ( 1 == astSscanf( setting, "full= %d %n", &full, &nc ) )
               && ( nc >= len ) ) {
      astSetFull( this, full );

/* Indent. */
/* ------- */
   } else if ( nc = 0,
               ( 1 == astSscanf( setting, "indent= %d %n", &indent, &nc ) )
               && ( nc >= len ) ) {
      astSetIndent( this, indent );

/* ReportLavel. */
/* ------------ */
   } else if ( nc = 0,
               ( 1 == astSscanf( setting, "reportlevel= %d %n", &report_level, &nc ) )
               && ( nc >= len ) ) {
      astSetReportLevel( this, report_level );

/* Skip. */
/* ----- */
   } else if ( nc = 0,
               ( 1 == astSscanf( setting, "skip= %d %n", &skip, &nc ) )
               && ( nc >= len ) ) {
      astSetSkip( this, skip );

/* SinkFile. */
/* --------- */
   } else if ( nc = 0,
               ( 0 == astSscanf( setting, "sinkfile=%n%*[^\n]%n", &sinkfile, &nc ) )
               && ( nc >= len ) ) {
      astSetSinkFile( this, setting + sinkfile );

/* SourceFile. */
/* ----------- */
   } else if ( nc = 0,
               ( 0 == astSscanf( setting, "sourcefile=%n%*[^\n]%n", &sourcefile, &nc ) )
               && ( nc >= len ) ) {
      astSetSourceFile( this, setting + sourcefile );

/* Strict. */
/* ------- */
   } else if ( nc = 0,
               ( 1 == astSscanf( setting, "strict= %d %n", &strict, &nc ) )
               && ( nc >= len ) ) {
      astSetStrict( this, strict );

/* If the attribute is still not recognised, pass it on to the parent
   method for further interpretation. */
   } else {
      (*parent_setattrib)( this_object, setting, status );
   }
}

static void SinkWrap( void (* sink)( const char * ), const char *line, int *status ) {
/*
*  Name:
*     SinkWrap

*  Purpose:
*     Wrapper function to invoke a C Channel sink function.

*  Type:
*     Private function.

*  Synopsis:
*     #include "channel.h"
*     void SinkWrap( void (* sink)( const char * ), const char *line, int *status )

*  Class Membership:
*     Channel member function.

*  Description:
*     This function invokes the sink function whose pointer is
*     supplied in order to write an output line to an external data
*     store.

*  Parameters:
*     sink
*        Pointer to a sink function, whose single parameter is a
*        pointer to a const, null-terminated string containing the
*        text to be written, and which returns void. This is the form
*        of Channel sink function employed by the C language interface
*        to the AST library.
*     status
*        Pointer to the inherited status variable.
*/

/* Check the global error status. */
   if ( !astOK ) return;

/* Invoke the sink function. */
   ( *sink )( line );
}

static char *SourceWrap( const char *(* source)( void ), int *status ) {
/*
*  Name:
*     SourceWrap

*  Purpose:
*     Wrapper function to invoke a C Channel source function.

*  Type:
*     Private function.

*  Synopsis:
*     #include "channel.h"
*     char *SourceWrap( const char *, int *status(* source)( void ) )

*  Class Membership:
*     Channel member function.

*  Description:
*     This function invokes the source function whose pointer is
*     supplied in order to read the next input line from an external
*     data store. It then returns a pointer to a dynamic string
*     containing a copy of the text that was read.

*  Parameters:
*     source
*        Pointer to a source function, with no parameters, that
*        returns a pointer to a const, null-terminated string
*        containing the text that it read. This is the form of Channel
*        source function employed by the C language interface to the
*        AST library.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     A pointer to a dynamically allocated, null terminated string
*     containing a copy of the text that was read. This string must be
*     freed by the caller (using astFree) when no longer required.
*
*     A NULL pointer will be returned if there is no more input text
*     to read.

*  Notes:
*     - A NULL pointer value will be returned if this function is
*     invoked with the global error status set or if it should fail
*     for any reason.
*/

/* Local Variables: */
   char *result;                 /* Pointer value to return */
   const char *line;             /* Pointer to input line */

/* Initialise. */
   result = NULL;

/* Check the global error status. */
   if ( !astOK ) return result;

/* Invoke the source function to read the next input line and return a
   pointer to the resulting string. */
   line = ( *source )();

/* If a string was obtained, make a dynamic copy of it and save the
   resulting pointer. */
   if ( line ) result = astString( line, (int) strlen( line ) );

/* Return the result. */
   return result;
}

void astStoreChannelData_( AstChannel *this, int *status ) {
/*
*+
*  Name:
*     astStoreChannelData

*  Purpose:
*     Store the Channel's channel-data pointer in a thread-specific
*     global variable.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     astStoreChannelData( AstChannel *this )

*  Class Membership:
*     Channel method.

*  Description:
*     This function stores the Channel's channel-data pointer (if any)
*     established by the previous call to astPutChannelData, in a
*     thread-specific global variable from where the astChannelData macro
*     can access it.

*  Parameters:
*     this
*        Pointer to the Channel.
*-
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */

/* Check the global error status. */
   if ( !astOK ) return;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Store the pointer int he global variable. */
   channel_data = this->data;
}

static int TestAttrib( AstObject *this_object, const char *attrib, int *status ) {
/*
*  Name:
*     TestAttrib

*  Purpose:
*     Test if a specified attribute value is set for a Channel.

*  Type:
*     Private function.

*  Synopsis:
*     #include "channel.h"
*     int TestAttrib( AstObject *this, const char *attrib, int *status )

*  Class Membership:
*     Channel member function (over-rides the astTestAttrib protected
*     method inherited from the Object class).

*  Description:
*     This function returns a boolean result (0 or 1) to indicate whether
*     a value has been set for one of a Channel's attributes.

*  Parameters:
*     this
*        Pointer to the Channel.
*     attrib
*        Pointer to a null terminated string specifying the attribute
*        name.  This should be in lower case with no surrounding white
*        space.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     One if a value has been set, otherwise zero.

*  Notes:
*     - A value of zero will be returned if this function is invoked
*     with the global status set, or if it should fail for any reason.
*/

/* Local Variables: */
   AstChannel *this;             /* Pointer to the Channel structure */
   int result;                   /* Result value to return */

/* Initialise. */
   result = 0;

/* Check the global error status. */
   if ( !astOK ) return result;

/* Obtain a pointer to the Channel structure. */
   this = (AstChannel *) this_object;

/* Check the attribute name and test the appropriate attribute. */

/* Comment. */
/* -------- */
   if ( !strcmp( attrib, "comment" ) ) {
      result = astTestComment( this );

/* Full. */
/* ----- */
   } else if ( !strcmp( attrib, "full" ) ) {
      result = astTestFull( this );

/* Indent. */
/* ------- */
   } else if ( !strcmp( attrib, "indent" ) ) {
      result = astTestIndent( this );

/* ReportLevel. */
/* ------------ */
   } else if ( !strcmp( attrib, "reportlevel" ) ) {
      result = astTestReportLevel( this );

/* Skip. */
/* ----- */
   } else if ( !strcmp( attrib, "skip" ) ) {
      result = astTestSkip( this );

/* SourceFile. */
/* ----------- */
   } else if ( !strcmp( attrib, "sourcefile" ) ) {
      result = astTestSourceFile( this );

/* SinkFile. */
/* ----------- */
   } else if ( !strcmp( attrib, "sinkfile" ) ) {
      result = astTestSinkFile( this );

/* Strict. */
/* ------- */
   } else if ( !strcmp( attrib, "strict" ) ) {
      result = astTestStrict( this );

/* If the attribute is still not recognised, pass it on to the parent
   method for further interpretation. */
   } else {
      result = (*parent_testattrib)( this_object, attrib, status );
   }

/* Return the result, */
   return result;
}

static void Unquote( AstChannel *this, char *str, int *status ) {
/*
*  Name:
*     Unquote

*  Purpose:
*     Remove quotes from a (possibly) quoted string.

*  Type:
*     Private function.

*  Synopsis:
*     #include "channel.h"
*     void Unquote( AstChannel *this, char *str, int *status )

*  Class Membership:
*     Channel member function.

*  Description:
*     This function removes one layer of quote characters (") from a
*     string which is possibly quoted. Any quotes within quotes (which
*     should have been doubled when the string was originally quoted)
*     are also converted back to single quotes again.
*
*     The quotes need not start or end at the ends of the string, and
*     there may be any number of quoted sections within the string. No
*     error results if the string does not contain any quotes at all
*     (it is simply returned unchanged), but an error results if any
*     unmatched quotes are found.

*  Parameters:
*     this
*        Pointer to a Channel. This is only used for constructing error
*        messages and has no influence on the string processing.
*     str
*        Pointer to the null-terminated string to be processed. This
*        is modified in place. The new string starts at the same
*        location as the original but has a new null character
*        appended if necessary (it will usually be shorter than the
*        original).
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   int i;                        /* Loop counter for "input" characters */
   int j;                        /* Counter for "output" characters */
   int quoted;                   /* Inside a quoted string? */

/* Check the global error status. */
   if ( !astOK ) return;

/* Loop to inspect each character in the string. */
   quoted = 0;
   for ( i = j = 0; str[ i ]; i++ ) {

/* Non-quote characters are simply copied to their new location in the
   string. */
      if ( str[ i ] != '"' ) {
         str[ j++ ] = str[ i ];

/* If a quote character '"' is encountered and we are not already in a
   quoted string, then note the start of a quoted string (and discard
   the quote). */
      } else if ( !quoted ) {
         quoted = 1;

/* If a quote character is encountered inside a quoted string, then
   check if the next character is also a quote. If so, convert this
   double quote to a single one. */
      } else if ( str[ i + 1 ] == '"' ) {
         str[ j++ ] = '"';
         i++;

/* If a single quote character is encountered inside a quoted string,
   then note the end of the quoted string (and discard the quote). */
      } else {
         quoted = 0;
      }
   }

/* Append a null to terminate the processed string. */
   str[ j ] = '\0';

/* If the "quoted" flag is still set, then there were unmatched
   quotes, so report an error. */
   if ( quoted ) {
      astError( AST__UNMQT,
                "astRead(%s): Unmatched quotes in input data: %s.", status,
                astGetClass( this ), str );
   }
}

static int Use( AstChannel *this, int set, int helpful, int *status ) {
/*
*  Name:
*     Use

*  Purpose:
*     Decide whether to write a value to a data sink.

*  Type:
*     Private function.

*  Synopsis:
*     #include "channel.h"
*     int Use( AstChannel *this, int set, int helpful, int *status )

*  Class Membership:
*     Channel member function.

*  Description:
*     This function decides whether a value supplied by a class "Dump"
*     function, via a call to one of the astWrite... protected
*     methods, should actually be written to the data sink associated
*     with a Channel.
*
*     This decision is based on the settings of the "set" and
*     "helpful" flags supplied to the astWrite... method, plus the
*     attribute settings of the Channel.

*  Parameters:
*     this
*        A pointer to the Channel.
*     set
*        The "set" flag supplied.
*     helpful
*        The "helpful" value supplied.
*     status
*        Pointer to the inherited status variable.

*  Returned Value:
*     One if the value should be written out, otherwise zero.

*  Notes:
*     - A value of zero will be returned if this function is invoked
*     with the global error status set or if it should fail for any
*     reason.
*/

/* Local Variables: */
   int full;                     /* Full attribute value */
   int result;                   /* Result value to be returned */

/* Check the global error status. */
   if ( !astOK ) return 0;

/* If "set" is non-zero, then so is the result ("set" values must
   always be written out). */
   result = ( set != 0 );

/* Otherwise, obtain the value of the Channel's Full attribute. */
   if ( !set ) {
      full = astGetFull( this );

/* If Full is positive, display all values, if zero, display only
   "helpful" values, if negative, display no (un-"set") values. */
      if ( astOK ) result = ( ( helpful && ( full > -1 ) ) || ( full > 0 ) );
   }

/* Return the result. */
   return result;
}

static int Write( AstChannel *this, AstObject *object, int *status ) {
/*
*++
*  Name:
c     astWrite
f     AST_WRITE

*  Purpose:
*     Write an Object to a Channel.

*  Type:
*     Public function.

*  Synopsis:
c     #include "channel.h"
c     int astWrite( AstChannel *this, AstObject *object )
f     RESULT = AST_WRITE( THIS, OBJECT, STATUS )

*  Class Membership:
*     Channel method.

*  Description:
*     This function writes an Object to a Channel, appending it to any
*     previous Objects written to that Channel.

*  Parameters:
c     this
f     THIS = INTEGER (Given)
*        Pointer to the Channel.
c     object
f     OBJECT = INTEGER (Given)
*        Pointer to the Object which is to be written.
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Returned Value:
c     astWrite()
f     AST_WRITE = INTEGER
*        The number of Objects written to the Channel by this
c        invocation of astWrite (normally, this will be one).
f        invocation of AST_WRITE (normally, this will be one).

*  Applicability:
*     FitsChan
*        If the FitsChan uses a foreign encoding (e.g. FITS-WCS) rather
*        than the native AST encoding, then storing values in the
*        FitsChan for keywords NAXIS1, NAXIS2, etc., before invoking
c        astWrite
f        AST_WRITE
*        can help to produce a successful write.

*  Notes:
*     - A value of zero will be returned if this function is invoked
c     with the AST error status set, or if it should fail for any
f     with STATUS set to an error value, or if it should fail for any
*     reason.
*     - Invoking this function will usually cause the sink function
*     associated with the channel to be called in order to transfer a
*     textual description of the supplied object to some external data
*     store. However, the FitsChan class behaves differently. Invoking
*     this function on a FitsChan causes new FITS header cards to be
*     added to an internal buffer (the sink function is not invoked).
*     This buffer is written out through the sink function only when the
*     FitsChan is deleted.
*--
*/

/* Check the global error status. */
   if ( !astOK ) return 0;

/* The work of this function is actually performed by the protected
   astDump method of the Object. The fact that this is further
   encapsulated within the astWrite method (which belongs to the
   Channel) is simply a trick to allow it to be over-ridden either by
   a derived Channel, or a derived Object (or both), and hence to
   adapt to the nature of either argument. */
   astDump( object, this );

/* Ensure all output is flushed to any associated output file specified
   by the SinkFile attribute. */
   astWriteFlush( this );

/* Return the number of Objects written. */
   return astOK ? 1 : 0;
}

static void WriteBegin( AstChannel *this, const char *class,
                        const char *comment, int *status ) {
/*
*+
*  Name:
*     astWriteBegin

*  Purpose:
*     Write a "Begin" data item to a data sink.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     void astWriteBegin( AstChannel *this, const char *class,
*                         const char *comment )

*  Class Membership:
*     Channel method.

*  Description:
*     This function writes a "Begin" data item to the data sink
*     associated with a Channel, so as to begin the output of a new
*     Object definition.

*  Parameters:
*     this
*        Pointer to the Channel.
*     class
*        Pointer to a constant null-terminated string containing the
*        name of the class to which the Object belongs.
*     comment
*        Pointer to a constant null-terminated string containing a
*        textual comment to be associated with the "Begin"
*        item. Normally, this will describe the purpose of the Object.

*  Notes:
*     - The comment supplied may not actually be used, depending on
*     the nature of the Channel supplied.
*-
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   char *line;                   /* Pointer to dynamic output string */
   int i;                        /* Loop counter for indentation characters */
   int nc;                       /* Number of output characters */

/* Check the global error status. */
   if ( !astOK ) return;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Start building a dynamic string with an initial space. Then add
   further spaces to suit the current indentation level. */
   line = astAppendString( NULL, &nc, " " );
   for ( i = 0; i < current_indent; i++ ) {
      line = astAppendString( line, &nc, " " );
   }

/* Append the "Begin" keyword followed by the class name. */
   line = astAppendString( line, &nc, "Begin " );
   line = astAppendString( line, &nc, class );

/* If required, also append the comment. */
   if ( astGetComment( this ) && *comment ) {
      line = astAppendString( line, &nc, " \t# " );
      line = astAppendString( line, &nc, comment );
   }

/* Write out the resulting line of text. */
   OutputTextItem( this, line, status );

/* Free the dynamic string. */
   line = astFree( line );

/* Increment the indentation level and clear the count of items written
   for this Object. */
   current_indent += astGetIndent( this );
   items_written = 0;
}

static void WriteDouble( AstChannel *this, const char *name,
                         int set, int helpful,
                         double value, const char *comment, int *status ) {
/*
*+
*  Name:
*     astWriteDouble

*  Purpose:
*     Write a double value to a data sink.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     void astWriteDouble( AstChannel *this, const char *name,
*                          int set, int helpful,
*                          double value, const char *comment )

*  Class Membership:
*     Channel method.

*  Description:
*     This function writes a named double value, representing the
*     value of a class instance variable, to the data sink associated
*     with a Channel. It is intended for use by class "Dump" functions
*     when writing out class information which will subsequently be
*     re-read.

*  Parameters:
*     this
*        Pointer to the Channel.
*     name
*        Pointer to a constant null-terminated string containing the
*        name to be used to identify the value in the external
*        representation. This will form the key for identifying it
*        again when it is re-read. The name supplied should be unique
*        within its class.
*
*        Mixed case may be used and will be preserved in the external
*        representation (where possible) for cosmetic effect. However,
*        case is not significant when re-reading values.
*
*        It is recommended that a maximum of 6 alphanumeric characters
*        (starting with an alphabetic character) be used. This permits
*        maximum flexibility in adapting to standard external data
*        representations (e.g. FITS).
*     set
*        If this is zero, it indicates that the value being written is
*        a default value (or can be re-generated from other values) so
*        need not necessarily be written out. Such values will
*        typically be included in the external representation with
*        (e.g.) a comment character so that they are available to
*        human readers but will be ignored when re-read. They may also
*        be completely omitted in some circumstances.
*
*        If "set" is non-zero, the value will always be explicitly
*        included in the external representation so that it can be
*        re-read.
*     helpful
*        This flag provides a hint about whether a value whose "set"
*        flag is zero (above) should actually appear at all in the
*        external representaton.
*
*        If the external representation allows values to be "commented
*        out" then, by default, values will be included in this form
*        only if their "helpful" flag is non-zero. Otherwise, they
*        will be omitted entirely. When possible, omitting the more
*        obscure values associated with a class is recommended in
*        order to improve readability.
*
*        This default behaviour may be further modified if the
*        Channel's Full attribute is set - either to permit all values
*        to be shown, or to suppress non-essential information
*        entirely.
*     value
*        The value to be written.
*     comment
*        Pointer to a constant null-terminated string containing a
*        textual comment to be associated with the value.
*
*        Note that this comment may not actually be used, depending on
*        the nature of the Channel supplied and the setting of its
*        Comment attribute.
*-
*/

/* Local Constants: */
#define BUFF_LEN 100             /* Size of local formatting buffer */

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   char *line;                   /* Pointer to dynamic output string */
   char buff[ BUFF_LEN + 1 ];    /* Local formatting buffer */
   int i;                        /* Loop counter for indentation characters */
   int nc;                       /* Number of output characters */

/* Check the global error status. */
   if ( !astOK ) return;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Use the "set" and "helpful" flags, along with the Channel's
   attributes to decide whether this value should actually be
   written. */
   if ( Use( this, set, helpful, status ) ) {

/* Start building a dynamic string with an initial space, or a comment
   character if "set" is zero. Then add further spaces to suit the
   current indentation level. */
      line = astAppendString( NULL, &nc, set ? " " : "#" );
      for ( i = 0; i < current_indent; i++ ) {
         line = astAppendString( line, &nc, " " );
      }

/* Append the name string followed by " = ". */
      line = astAppendString( line, &nc, name );
      line = astAppendString( line, &nc, " = " );

/* Format the value as a string and append this. Make sure "-0" isn't
   produced. Use a magic string to represent bad values. */
      if( value != AST__BAD ) {
         (void) sprintf( buff, "%.*g", AST__DBL_DIG, value );
         if ( !strcmp( buff, "-0" ) ) {
            buff[ 0 ] = '0';
            buff[ 1 ] = '\0';
         }
      } else {
         strcpy( buff, BAD_STRING );
      }
      line = astAppendString( line, &nc, buff );

/* If required, also append the comment. */
      if ( astGetComment( this ) && *comment ) {
         line = astAppendString( line, &nc, " \t# " );
         line = astAppendString( line, &nc, comment );
      }

/* Write out the resulting line of text. */
      OutputTextItem( this, line, status );

/* Free the dynamic string. */
      line = astFree( line );
   }

/* Undefine macros local to this function. */
#undef BUFF_LEN
}

static void WriteEnd( AstChannel *this, const char *class, int *status ) {
/*
*+
*  Name:
*     astWriteEnd

*  Purpose:
*     Write an "End" data item to a data sink.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     void astWriteEnd( AstChannel *this, const char *class )

*  Class Membership:
*     Channel method.

*  Description:
*     This function writes an "End" data item to the data sink
*     associated with a Channel. This item delimits the end of an
*     Object definition.

*  Parameters:
*     this
*        Pointer to the Channel.
*     class
*        Pointer to a constant null-terminated string containing the
*        class name of the Object whose definition is being terminated
*        by the "End" item.
*-
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   char *line;                   /* Pointer to dynamic output string */
   int i;                        /* Loop counter for indentation characters */
   int nc;                       /* Number of output characters */

/* Check the global error status. */
   if ( !astOK ) return;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Decrement the indentation level so that the "End" item matches the
   corresponding "Begin" item. */
   current_indent -= astGetIndent( this );

/* Start building a dynamic string with an initial space. Then add
   further spaces to suit the current indentation level. */
   line = astAppendString( NULL, &nc, " " );
   for ( i = 0; i < current_indent; i++ ) {
      line = astAppendString( line, &nc, " " );
   }

/* Append the "End" keyword followed by the class name. */
   line = astAppendString( line, &nc, "End " );
   line = astAppendString( line, &nc, class );

/* Write out the resulting line of text. */
   OutputTextItem( this, line, status );

/* Free the dynamic string. */
   line = astFree( line );
}

static void WriteFlush( AstChannel *this, int *status ) {
/*
*+
*  Name:
*     astWriteFlush

*  Purpose:
*     Flush the output file associated with a Channel.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     void astWriteFlush( AstChannel *this )

*  Class Membership:
*     Channel method.

*  Description:
*     This function flushes any buffered output to any ouput file
*     associated with the supplied CHannel.

*  Parameters:
*     this
*        Pointer to the Channel.
*-
*/

/* Check the global error status. */
   if ( !astOK ) return;

/* If the Channel has an associated output file, flush it. */
   if( this->fd_out ) fflush( this->fd_out );
}

static void WriteInt( AstChannel *this, const char *name, int set, int helpful,
                      int value, const char *comment, int *status ) {
/*
*+
*  Name:
*     astWriteInt

*  Purpose:
*     Write an integer value to a data sink.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     void astWriteInt( AstChannel *this, const char *name,
*                       int set, int helpful,
*                       int value, const char *comment )

*  Class Membership:
*     Channel method.

*  Description:
*     This function writes a named integer value, representing the
*     value of a class instance variable, to the data sink associated
*     with a Channel. It is intended for use by class "Dump" functions
*     when writing out class information which will subsequently be
*     re-read.

*  Parameters:
*     this
*        Pointer to the Channel.
*     name
*        Pointer to a constant null-terminated string containing the
*        name to be used to identify the value in the external
*        representation. This will form the key for identifying it
*        again when it is re-read. The name supplied should be unique
*        within its class.
*
*        Mixed case may be used and will be preserved in the external
*        representation (where possible) for cosmetic effect. However,
*        case is not significant when re-reading values.
*
*        It is recommended that a maximum of 6 alphanumeric characters
*        (starting with an alphabetic character) be used. This permits
*        maximum flexibility in adapting to standard external data
*        representations (e.g. FITS).
*     set
*        If this is zero, it indicates that the value being written is
*        a default value (or can be re-generated from other values) so
*        need not necessarily be written out. Such values will
*        typically be included in the external representation with
*        (e.g.) a comment character so that they are available to
*        human readers but will be ignored when re-read. They may also
*        be completely omitted in some circumstances.
*
*        If "set" is non-zero, the value will always be explicitly
*        included in the external representation so that it can be
*        re-read.
*     helpful
*        This flag provides a hint about whether a value whose "set"
*        flag is zero (above) should actually appear at all in the
*        external representaton.
*
*        If the external representation allows values to be "commented
*        out" then, by default, values will be included in this form
*        only if their "helpful" flag is non-zero. Otherwise, they
*        will be omitted entirely. When possible, omitting the more
*        obscure values associated with a class is recommended in
*        order to improve readability.
*
*        This default behaviour may be further modified if the
*        Channel's Full attribute is set - either to permit all values
*        to be shown, or to suppress non-essential information
*        entirely.
*     value
*        The value to be written.
*     comment
*        Pointer to a constant null-terminated string containing a
*        textual comment to be associated with the value.
*
*        Note that this comment may not actually be used, depending on
*        the nature of the Channel supplied and the setting of its
*        Comment attribute.
*-
*/

/* Local Constants: */
#define BUFF_LEN 50              /* Size of local formatting buffer */

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   char *line;                   /* Pointer to dynamic output string */
   char buff[ BUFF_LEN + 1 ];    /* Local formatting buffer */
   int i;                        /* Loop counter for indentation characters */
   int nc;                       /* Number of output characters */

/* Check the global error status. */
   if ( !astOK ) return;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Use the "set" and "helpful" flags, along with the Channel's
   attributes to decide whether this value should actually be
   written. */
   if ( Use( this, set, helpful, status ) ) {

/* Start building a dynamic string with an initial space, or a comment
   character if "set" is zero. Then add further spaces to suit the
   current indentation level. */
      line = astAppendString( NULL, &nc, set ? " " : "#" );
      for ( i = 0; i < current_indent; i++ ) {
         line = astAppendString( line, &nc, " " );
      }

/* Append the name string followed by " = ". */
      line = astAppendString( line, &nc, name );
      line = astAppendString( line, &nc, " = " );

/* Format the value as a decimal string and append this. */
      (void) sprintf( buff, "%d", value );
      line = astAppendString( line, &nc, buff );

/* If required, also append the comment. */
      if ( astGetComment( this ) && *comment ) {
         line = astAppendString( line, &nc, " \t# " );
         line = astAppendString( line, &nc, comment );
      }

/* Write out the resulting line of text. */
      OutputTextItem( this, line, status );

/* Free the dynamic string. */
      line = astFree( line );
   }

/* Undefine macros local to this function. */
#undef BUFF_LEN
}

static void WriteInt64( AstChannel *this, const char *name, int set, int helpful,
                        int64_t value, const char *comment, int *status ) {
/*
*+
*  Name:
*     astWriteInt64

*  Purpose:
*     Write a 64 bit integer value to a data sink.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     void astWriteInt64( AstChannel *this, const char *name,
*                         int set, int helpful,
*                         int64_t value, const char *comment )

*  Class Membership:
*     Channel method.

*  Description:
*     This function writes a named 64 bit integer value, representing the
*     value of a class instance variable, to the data sink associated
*     with a Channel. It is intended for use by class "Dump" functions
*     when writing out class information which will subsequently be
*     re-read.

*  Parameters:
*     this
*        Pointer to the Channel.
*     name
*        Pointer to a constant null-terminated string containing the
*        name to be used to identify the value in the external
*        representation. This will form the key for identifying it
*        again when it is re-read. The name supplied should be unique
*        within its class.
*
*        Mixed case may be used and will be preserved in the external
*        representation (where possible) for cosmetic effect. However,
*        case is not significant when re-reading values.
*
*        It is recommended that a maximum of 6 alphanumeric characters
*        (starting with an alphabetic character) be used. This permits
*        maximum flexibility in adapting to standard external data
*        representations (e.g. FITS).
*     set
*        If this is zero, it indicates that the value being written is
*        a default value (or can be re-generated from other values) so
*        need not necessarily be written out. Such values will
*        typically be included in the external representation with
*        (e.g.) a comment character so that they are available to
*        human readers but will be ignored when re-read. They may also
*        be completely omitted in some circumstances.
*
*        If "set" is non-zero, the value will always be explicitly
*        included in the external representation so that it can be
*        re-read.
*     helpful
*        This flag provides a hint about whether a value whose "set"
*        flag is zero (above) should actually appear at all in the
*        external representaton.
*
*        If the external representation allows values to be "commented
*        out" then, by default, values will be included in this form
*        only if their "helpful" flag is non-zero. Otherwise, they
*        will be omitted entirely. When possible, omitting the more
*        obscure values associated with a class is recommended in
*        order to improve readability.
*
*        This default behaviour may be further modified if the
*        Channel's Full attribute is set - either to permit all values
*        to be shown, or to suppress non-essential information
*        entirely.
*     value
*        The value to be written.
*     comment
*        Pointer to a constant null-terminated string containing a
*        textual comment to be associated with the value.
*
*        Note that this comment may not actually be used, depending on
*        the nature of the Channel supplied and the setting of its
*        Comment attribute.
*-
*/

/* Local Constants: */
#define BUFF_LEN 50              /* Size of local formatting buffer */

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   char *line;                   /* Pointer to dynamic output string */
   char buff[ BUFF_LEN + 1 ];    /* Local formatting buffer */
   int i;                        /* Loop counter for indentation characters */
   int nc;                       /* Number of output characters */

/* Check the global error status. */
   if ( !astOK ) return;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Use the "set" and "helpful" flags, along with the Channel's
   attributes to decide whether this value should actually be
   written. */
   if ( Use( this, set, helpful, status ) ) {

/* Start building a dynamic string with an initial space, or a comment
   character if "set" is zero. Then add further spaces to suit the
   current indentation level. */
      line = astAppendString( NULL, &nc, set ? " " : "#" );
      for ( i = 0; i < current_indent; i++ ) {
         line = astAppendString( line, &nc, " " );
      }

/* Append the name string followed by " = ". */
      line = astAppendString( line, &nc, name );
      line = astAppendString( line, &nc, " = " );

/* Format the value as a decimal string and append this. */
      (void) sprintf( buff, "%" PRId64, value );
      line = astAppendString( line, &nc, buff );

/* If required, also append the comment. */
      if ( astGetComment( this ) && *comment ) {
         line = astAppendString( line, &nc, " \t# " );
         line = astAppendString( line, &nc, comment );
      }

/* Write out the resulting line of text. */
      OutputTextItem( this, line, status );

/* Free the dynamic string. */
      line = astFree( line );
   }

/* Undefine macros local to this function. */
#undef BUFF_LEN
}

int astWriteInvocations_( int *status ){
/*
*+
*  Name:
*     astWriteInvocations

*  Purpose:
*     Returns the number of invocations of the astWrite method.

*  Type:
*     Protected function.

*  Synopsis:
*     #include "channel.h"
*     int astWriteInvocations

*  Class Membership:
*     Channel method.

*  Description:
*     This function returns the number of invocations of astWrite which
*     have been made so far, excluding those made from within the
*     astWriteObject method. An example of its use is to allow a Dump
*     function to determine if a sub-object has already been dumped
*     during the current invocation of astWrite. See the Dump method for
*     the AstUnit class as an example.
*-
*/
   astDECLARE_GLOBALS
   astGET_GLOBALS(NULL);
   return nwrite_invoc;
}

static void WriteIsA( AstChannel *this, const char *class,
                      const char *comment, int *status ) {
/*
*+
*  Name:
*     astWriteIsA

*  Purpose:
*     Write an "IsA" data item to a data sink.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     void astWriteIsA( AstChannel *this, const char *class,
*                       const char *comment )

*  Class Membership:
*     Channel method.

*  Description:
*     This function writes an "IsA" data item to the data sink
*     associated with a Channel. This item delimits the end of the
*     data associated with the instance variables of a class, as part
*     of an overall Object definition.

*  Parameters:
*     this
*        Pointer to the Channel.
*     class
*        Pointer to a constant null-terminated string containing the
*        name of the class whose data are terminated by the "IsA"
*        item.
*     comment
*        Pointer to a constant null-terminated string containing a
*        textual comment to be associated with the "IsA"
*        item. Normally, this will describe the purpose of the class
*        whose data are being terminated.

*  Notes:
*     - The comment supplied may not actually be used, depending on
*     the nature of the Channel supplied.
*-
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   char *line;                   /* Pointer to dynamic output string */
   int i;                        /* Loop counter for indentation characters */
   int indent_inc;               /* Indentation increment */
   int nc;                       /* Number of output characters */

/* Check the global error status. */
   if ( !astOK ) return;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Output an "IsA" item only if there has been at least one item
   written since the last "Begin" or "IsA" item, or if the Full
   attribute for the Channel is greater than zero (requesting maximum
   information). */
   if ( items_written || astGetFull( this ) > 0 ) {

/* Start building a dynamic string with an initial space. Then add
   further spaces to suit the current indentation level, but reduced
   by one to allow the "IsA" item to match the "Begin" and "End" items
   which enclose it. */
      indent_inc = astGetIndent( this );
      line = astAppendString( NULL, &nc, " " );
      for ( i = 0; i < ( current_indent - indent_inc ); i++ ) {
         line = astAppendString( line, &nc, " " );
      }

/* Append the "IsA" keyword followed by the class name. */
      line = astAppendString( line, &nc, "IsA " );
      line = astAppendString( line, &nc, class );

/* If required, also append the comment. */
      if ( astGetComment( this ) && *comment ) {
         line = astAppendString( line, &nc, " \t# " );
         line = astAppendString( line, &nc, comment );
      }

/* Write out the resulting line of text. */
      OutputTextItem( this, line, status );

/* Free the dynamic string. */
      line = astFree( line );

/* Clear the count of items written for this class. */
      items_written = 0;
   }
}

static void WriteObject( AstChannel *this, const char *name,
                         int set, int helpful,
                         AstObject *value, const char *comment, int *status ) {
/*
*+
*  Name:
*     astWriteObject

*  Purpose:
*     Write an Object as a value to a data sink.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     void astWriteObject( AstChannel *this, const char *name,
*                          int set, int helpful,
*                          AstObject *value, const char *comment )

*  Class Membership:
*     Channel method.

*  Description:
*     This function writes an Object as a named value, representing
*     the value of a class instance variable, to the data sink
*     associated with a Channel. It is intended for use by class
*     "Dump" functions when writing out class information which will
*     subsequently be re-read.

*  Parameters:
*     this
*        Pointer to the Channel.
*     name
*        Pointer to a constant null-terminated string containing the
*        name to be used to identify the value in the external
*        representation. This will form the key for identifying it
*        again when it is re-read. The name supplied should be unique
*        within its class.
*
*        Mixed case may be used and will be preserved in the external
*        representation (where possible) for cosmetic effect. However,
*        case is not significant when re-reading values.
*
*        It is recommended that a maximum of 6 alphanumeric characters
*        (starting with an alphabetic character) be used. This permits
*        maximum flexibility in adapting to standard external data
*        representations (e.g. FITS).
*     set
*        If this is zero, it indicates that the value being written is
*        a default value (or can be re-generated from other values) so
*        need not necessarily be written out. Such values will
*        typically be included in the external representation with
*        (e.g.) a comment character so that they are available to
*        human readers but will be ignored when re-read. They may also
*        be completely omitted in some circumstances.
*
*        If "set" is non-zero, the value will always be explicitly
*        included in the external representation so that it can be
*        re-read.
*     helpful
*        This flag provides a hint about whether a value whose "set"
*        flag is zero (above) should actually appear at all in the
*        external representaton.
*
*        If the external representation allows values to be "commented
*        out" then, by default, values will be included in this form
*        only if their "helpful" flag is non-zero. Otherwise, they
*        will be omitted entirely. When possible, omitting the more
*        obscure values associated with a class is recommended in
*        order to improve readability.
*
*        This default behaviour may be further modified if the
*        Channel's Full attribute is set - either to permit all values
*        to be shown, or to suppress non-essential information
*        entirely.
*     value
*        A Pointer to the Object to be written.
*     comment
*        Pointer to a constant null-terminated string containing a
*        textual comment to be associated with the value.
*
*        Note that this comment may not actually be used, depending on
*        the nature of the Channel supplied and the setting of its
*        Comment attribute.
*-
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   char *line;                   /* Pointer to dynamic output string */
   int i;                        /* Loop counter for indentation characters */
   int indent_inc;               /* Indentation increment */
   int nc;                       /* Number of output characters */

/* Check the global error status. */
   if ( !astOK ) return;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Use the "set" and "helpful" flags, along with the Channel's
   attributes to decide whether this value should actually be
   written. */
   if ( Use( this, set, helpful, status ) ) {

/* Start building a dynamic string with an initial space, or a comment
   character if "set" is zero. Then add further spaces to suit the
   current indentation level. */
      line = astAppendString( NULL, &nc, set ? " " : "#" );
      for ( i = 0; i < current_indent; i++ ) {
         line = astAppendString( line, &nc, " " );
      }

/* Append the name string followed by " =". The absence of a value on
   the right hand side indicates an Object value, whose definition
   follows. */
      line = astAppendString( line, &nc, name );
      line = astAppendString( line, &nc, " =" );

/* If required, also append the comment. */
      if ( astGetComment( this ) && *comment ) {
         line = astAppendString( line, &nc, " \t# " );
         line = astAppendString( line, &nc, comment );
      }

/* Write out the resulting line of text. */
      OutputTextItem( this, line, status );

/* Free the dynamic string. */
      line = astFree( line );

/* If the value is not a default, write the Object to the Channel as
   well, suitably indented (this is omitted if the value is commented
   out). */
      if ( set ) {
         indent_inc = astGetIndent( this );
         current_indent += indent_inc;
         (void) astWrite( this, value );
         current_indent -= indent_inc;
      }
   }
}

static void WriteString( AstChannel *this, const char *name,
                         int set, int helpful,
                         const char *value, const char *comment, int *status ) {
/*
*+
*  Name:
*     astWriteString

*  Purpose:
*     Write a string value to a data sink.

*  Type:
*     Protected virtual function.

*  Synopsis:
*     #include "channel.h"
*     void astWriteString( AstChannel *this, const char *name,
*                          int set, int helpful,
*                          const char *value, const char *comment )

*  Class Membership:
*     Channel method.

*  Description:
*     This function writes a named string value, representing the
*     value of a class instance variable, to the data sink associated
*     with a Channel. It is intended for use by class "Dump" functions
*     when writing out class information which will subsequently be
*     re-read.

*  Parameters:
*     this
*        Pointer to the Channel.
*     name
*        Pointer to a constant null-terminated string containing the
*        name to be used to identify the value in the external
*        representation. This will form the key for identifying it
*        again when it is re-read. The name supplied should be unique
*        within its class.
*
*        Mixed case may be used and will be preserved in the external
*        representation (where possible) for cosmetic effect. However,
*        case is not significant when re-reading values.
*
*        It is recommended that a maximum of 6 alphanumeric characters
*        (starting with an alphabetic character) be used. This permits
*        maximum flexibility in adapting to standard external data
*        representations (e.g. FITS).
*     set
*        If this is zero, it indicates that the value being written is
*        a default value (or can be re-generated from other values) so
*        need not necessarily be written out. Such values will
*        typically be included in the external representation with
*        (e.g.) a comment character so that they are available to
*        human readers but will be ignored when re-read. They may also
*        be completely omitted in some circumstances.
*
*        If "set" is non-zero, the value will always be explicitly
*        included in the external representation so that it can be
*        re-read.
*     helpful
*        This flag provides a hint about whether a value whose "set"
*        flag is zero (above) should actually appear at all in the
*        external representaton.
*
*        If the external representation allows values to be "commented
*        out" then, by default, values will be included in this form
*        only if their "helpful" flag is non-zero. Otherwise, they
*        will be omitted entirely. When possible, omitting the more
*        obscure values associated with a class is recommended in
*        order to improve readability.
*
*        This default behaviour may be further modified if the
*        Channel's Full attribute is set - either to permit all values
*        to be shown, or to suppress non-essential information
*        entirely.
*     value
*        Pointer to a constant null-terminated string containing the
*        value to be written.
*     comment
*        Pointer to a constant null-terminated string containing a
*        textual comment to be associated with the value.
*
*        Note that this comment may not actually be used, depending on
*        the nature of the Channel supplied and the setting of its
*        Comment attribute.
*-
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   char *line;                   /* Pointer to dynamic output string */
   int i;                        /* Loop counter for characters */
   int nc;                       /* Number of output characters */
   int quote;                    /* Quote character found? */
   int size;                     /* Size of allocated memory */

/* Check the global error status. */
   if ( !astOK ) return;

/* Get a pointer to the structure holding thread-specific global data. */
   astGET_GLOBALS(this);

/* Use the "set" and "helpful" flags, along with the Channel's
   attributes to decide whether this value should actually be
   written. */
   if ( Use( this, set, helpful, status ) ) {

/* Start building a dynamic string with an initial space, or a comment
   character if "set" is zero. Then add further spaces to suit the
   current indentation level. */
      line = astAppendString( NULL, &nc, set ? " " : "#" );
      for ( i = 0; i < current_indent; i++ ) {
         line = astAppendString( line, &nc, " " );
      }

/* Append the name string followed by " = " and an opening quote
   character (the string will be quoted to protect leading and
   trailing spaces). */
      line = astAppendString( line, &nc, name );
      line = astAppendString( line, &nc, " = \"" );

/* We now append the value string, but must inspect each character so
   that quotes (appearing inside quotes) can be doubled. Determine the
   current size of memory allocated for the dynamic string. */
      size = (int) astSizeOf( line );

/* Loop to inspect each character and see if it is a quote. */
      for ( i = 0; value[ i ]; i++ ) {
         quote = ( value[ i ] == '"' );

/* If more memory is required, extend the dynamic string (allowing for
   doubling of quotes and the final null) and save its new size. */
         if ( nc + 2 + quote > size ) {
            line = astGrow( line, nc + 2 + quote, sizeof( char ) );
            if ( astOK ) {
               size = (int) astSizeOf( line );

/* Quit if an error occurs. */
            } else {
               break;
            }
         }

/* Append the value character to the dynamic string, duplicating each
   quote character. */
         line[ nc++ ] = value[ i ];
         if ( quote ) line[ nc++ ] = '"';
      }

/* Append the closing quote. */
      line = astAppendString( line, &nc, "\"" );

/* If required, also append the comment. */
      if ( astGetComment( this ) && *comment ) {
         line = astAppendString( line, &nc, " \t# " );
         line = astAppendString( line, &nc, comment );
      }

/* Write out the resulting line of text. */
      OutputTextItem( this, line, status );

/* Free the dynamic string. */
      line = astFree( line );
   }
}

/* Functions which access class attributes. */
/* ---------------------------------------- */
/* Implement member functions to access the attributes associated with
   this class using the macros defined for this purpose in the
   "object.h" file. */

/*
*att++
*  Name:
*     SourceFile

*  Purpose:
*     Input file from which to read data.

*  Type:
*     Public attribute.

*  Synopsis:
*     String.

*  Description:
*     This attribute specifies the name of a file from which the Channel
*     should read data. If specified it is used in preference to any source
*     function specified when the Channel was created.
*
*     Assigning a new value to this attribute will cause any previously
*     opened SourceFile to be closed. The first subsequent call to
c     astRead
f     AST_READ
*     will attempt to open the new file (an error will be reported if the
*     file cannot be opened), and read data from it. All subsequent call to
c     astRead
f     AST_READ
*     will read data from the new file, until the SourceFile attribute is
*     cleared or changed.
*
*     Clearing the attribute causes any open SourceFile to be closed. All
*     subsequent data reads will use the source function specified when the
*     Channel was created, or will read from standard input if no source
*     function was specified.
*
*     If no value has been assigned to SourceFile, a null string will be
*     returned if an attempt is made to get the attribute value.

*  Notes:
*     - Any open SourceFile is closed when the Channel is deleted.
*     - If the Channel is copied or dumped
c     (using astCopy or astShow)
f     (using AST_COPY or AST_SHOW)
*     the SourceFile attribute is left in a cleared state in the output
*     Channel (i.e. the value of the SourceFile attribute is not copied).

*  Applicability:
*     FitsChan
*        In the case of a FitsChan, the specified SourceFile supplements
*        the source function specified when the FitsChan was created,
*        rather than replacing the source function. The source file
*        should be a text file (not a FITS file) containing one header per
*        line. When a value is assigned to SourceFile, the file is opened
*        and read immediately, and all headers read from the file are
*        appended to the end of any header already in the FitsChan. The file
*        is then closed. Clearing the SourceFile attribute has no further
*        effect, other than nullifying the string (i.e. the file name)
*        associated with the attribute.

*att--
*/

/* Clear the SourceFile value by closing any open file, freeing the
   allocated memory and assigning a NULL pointer. */
astMAKE_CLEAR(Channel,SourceFile,fn_in,((this->fd_in=(this->fd_in?(fclose(this->fd_in),NULL):NULL)),astFree(this->fn_in)))

/* If the SourceFile value is not set, supply a default in the form of a
   pointer to the constant string "". */
astMAKE_GET(Channel,SourceFile,const char *,NULL,( this->fn_in ? this->fn_in : "" ))

/* Set a SourceFile value by closing any open file, freeing any previously
   allocated memory, allocating new memory, storing the string and saving
   the pointer to the copy. */
astMAKE_SET(Channel,SourceFile,const char *,fn_in,((this->fd_in=(this->fd_in?(fclose(this->fd_in),NULL):NULL)),astStore( this->fn_in, value, strlen( value ) + (size_t) 1 )))

/* The SourceFile value is set if the pointer to it is not NULL. */
astMAKE_TEST(Channel,SourceFile,( this->fn_in != NULL ))

/*
*att++
*  Name:
*     SinkFile

*  Purpose:
*     Output file to which to data should be written.

*  Type:
*     Public attribute.

*  Synopsis:
*     String.

*  Description:
*     This attribute specifies the name of a file to which the Channel
*     should write data. If specified it is used in preference to any sink
*     function specified when the Channel was created.
*
*     Assigning a new value to this attribute will cause any previously
*     opened SinkFile to be closed. The first subsequent call to
c     astWrite
f     AST_WRITE
*     will attempt to open the new file (an error will be reported if the
*     file cannot be opened), and write data to it. All subsequent call to
c     astWrite
f     AST_WRITE
*     will write data to the new file, until the SinkFile attribute is
*     cleared or changed.
*
*     Clearing the attribute causes any open SinkFile to be closed. All
*     subsequent data writes will use the sink function specified when the
*     Channel was created, or will write to standard output if no sink
*     function was specified.
*
*     If no value has been assigned to SinkFile, a null string will be
*     returned if an attempt is made to get the attribute value.

*  Notes:
*     - A new SinkFile will over-write any existing file with the same
*     name unless the existing file is write protected, in which case an
*     error will be reported.
*     - Any open SinkFile is closed when the Channel is deleted.
*     - If the Channel is copied or dumped
c     (using astCopy or astShow)
f     (using AST_COPY or AST_SHOW)
*     the SinkFile attribute is left in a cleared state in the output
*     Channel (i.e. the value of the SinkFile attribute is not copied).

*  Applicability:
*     FitsChan
*        When the FitsChan is destroyed, any headers in the FitsChan will be
*        written out to the sink file, if one is specified (if not, the
*        sink function used when the FitsChan was created is used). The
*        sink file is a text file (not a FITS file) containing one header
*        per line.

*att--
*/

/* Clear the SinkFile value by closing any open file, freeing the allocated
   memory and assigning a NULL pointer. */
astMAKE_CLEAR(Channel,SinkFile,fn_out,((this->fd_out=(this->fd_out?(fclose(this->fd_out),NULL):NULL)),astFree(this->fn_out)))

/* If the SinkFile value is not set, supply a default in the form of a
   pointer to the constant string "". */
astMAKE_GET(Channel,SinkFile,const char *,NULL,( this->fn_out ? this->fn_out : "" ))

/* Set a SinkFile value by closing any open file, freeing any previously
   allocated memory, allocating new memory, storing the string and saving
   the pointer to the copy. */
astMAKE_SET(Channel,SinkFile,const char *,fn_out,((this->fd_out=(this->fd_out?(fclose(this->fd_out),NULL):NULL)),astStore( this->fn_out, value, strlen( value ) + (size_t) 1 )))

/* The SinkFile value is set if the pointer to it is not NULL. */
astMAKE_TEST(Channel,SinkFile,( this->fn_out != NULL ))


/*
*att++
*  Name:
*     Comment

*  Purpose:
*     Include textual comments in output?

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer (boolean).

*  Description:
*     This is a boolean attribute which controls whether textual
*     comments are to be included in the output generated by a
*     Channel. If included, they will describe what each item of
*     output represents.
*
*     If Comment is non-zero, then comments will be included. If
*     it is zero, comments will be omitted.

*  Applicability:
*     Channel
*        The default value is non-zero for a normal Channel.
*     FitsChan
*        The default value is non-zero for a FitsChan.
*     XmlChan
*        The default value is zero for an XmlChan.
*     YamlChan
*        This attribute is ignored by the YamlChan class. Comments are
*        never produced.

*att--
*/

/* This is a boolean value (0 or 1) with a value of -INT_MAX when
   undefined but yielding a default of one. */
astMAKE_CLEAR(Channel,Comment,comment,-INT_MAX)
astMAKE_GET(Channel,Comment,int,0,( this->comment != -INT_MAX ? this->comment : 1 ))
astMAKE_SET(Channel,Comment,int,comment,( value != 0 ))
astMAKE_TEST(Channel,Comment,( this->comment != -INT_MAX ))

/*
*att++
*  Name:
*     Full

*  Purpose:
*     Set level of output detail.

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer.

*  Description:
*     This attribute is a three-state flag and takes values of -1, 0
*     or +1.  It controls the amount of information included in the
*     output generated by a Channel.
*
*     If Full is zero, then a modest amount of
*     non-essential but useful information will be included in the
*     output. If Full is negative, all non-essential information will
*     be suppressed to minimise the amount of output, while if it is
*     positive, the output will include the maximum amount of detailed
*     information about the Object being written.

*  Applicability:
*     Channel
*        The default value is zero for a normal Channel.
*     FitsChan
*        The default value is zero for a FitsChan.
*     StcsChan
*        The default value is zero for an StcsChan. Set a positive value
*        to cause default values to be included in STC-S descriptions.
*     XmlChan
*        The default value is -1 for an XmlChan.
*     YamlChan
*        This attribute is ignored by the YamlChan class.

*  Notes:
*     - All positive values supplied for this attribute are converted
*     to +1 and all negative values are converted to -1.
*att--
*/

/* This ia a 3-state value (-1, 0 or +1) with a value of -INT_MAX when
   undefined but yielding a default of zero. */
astMAKE_CLEAR(Channel,Full,full,-INT_MAX)
astMAKE_GET(Channel,Full,int,0,( this->full != -INT_MAX ? this->full : 0 ))
astMAKE_SET(Channel,Full,int,full,( value > 0 ? 1 : ( value < 0 ? -1 : 0 ) ))
astMAKE_TEST(Channel,Full,( this->full != -INT_MAX ))

/*
*att++
*  Name:
*     Indent

*  Purpose:
*     Specifies the indentation to use in text produced by a Channel.

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer (boolean).

*  Description:
*     This attribute controls the indentation within the output text produced by
f     the AST_WRITE function.
c     the astWrite function.
*     It gives the increase in the indentation for each level in the object
*     heirarchy. If it is set to zero, no indentation will be used. [3]

*  Applicability:
*     Channel
*        The default value is zero for a basic Channel.
*     FitsChan
*        The FitsChan class ignores this attribute.
*     StcsChan
*        The default value for an StcsChan is zero, which causes the entire
*        STC-S description is written out by a single invocation of the sink
*        function. The text supplied to the sink function will not contain
*        any linefeed characters, and each pair of adjacent words will be
*        separated by a single space. The text may thus be arbitrarily large
*        and the StcsLength attribute is ignored.
*
*        If Indent is non-zero, then the text is written out via multiple
*        calls to the sink function, each call corresponding to a single
*        "line" of text (although no line feed characters will be inserted
*        by AST). The complete STC-S description is broken into lines so that:
*
*        - the line length specified by attribute StcsLength is not exceeded
*        - each sub-phrase (time, space, etc.) starts on a new line
*        - each argument in a compound spatial region starts on a new line
*
*        If this causes a sub-phrase to extend to two or more lines, then the
*        second and subsequent lines will be indented by three spaces compared
*        to the first line. In addition, lines within a compound spatial region
*        will have extra indentation to highlight the nesting produced by the
*        parentheses. Each new level of nesting will be indented by a further
*        three spaces.
f
f        Note, the default value of zero is unlikely to be appropriate when
f        an StcsChan is used within Fortran code. In this case, Indent
f        should usually be set non-zero, and the StcsLength attribute set to
f        the size of the CHARACTER variable used to
f        receive the text returned by AST_GETLINE within the sink function.
f        This avoids the possibility of long lines being truncated invisibly
f        within AST_GETLINE.
*     XmlChan
*        The default value for an XmlChan is zero, which results in no
*        linefeeds or indentation strings being added to output text.
*        If any non-zero value is assigned to Indent, then extra linefeed and
*        space characters will be inserted as necessary to ensure that each
*        XML tag starts on a new line, and each tag will be indented by
*        a further 3 spaces to show its depth in the containment hierarchy.
*     YamlChan
*        The default value for a YamlChan is two. Only non-zero values in
*        the range 1-10 are allowed.
*att--
*/

/* This is an integer value with a value of -INT_MAX when undefined,
   yielding a default of 3. Sub-classes may over-ride theis default. */
astMAKE_CLEAR(Channel,Indent,indent,-INT_MAX)
astMAKE_GET(Channel,Indent,int,3,( this->indent != -INT_MAX ? this->indent : 3 ))
astMAKE_SET(Channel,Indent,int,indent,value)
astMAKE_TEST(Channel,Indent,( this->indent != -INT_MAX ))

/*
*att++
*  Name:
*     ReportLevel

*  Purpose:
*     Determines which read/write conditions are reported.

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer.

*  Description:
*     This attribute determines which, if any, of the conditions that occur
*     whilst reading or writing an Object should be reported. These
*     conditions will generate either a fatal error or a warning, as
*     determined by attribute Strict. ReportLevel can take any of the
*     following values:
*
*     0 - Do not report any conditions.
*
*     1 - Report only conditions where significant information content has been
*     changed. For instance, an unsupported time-scale has been replaced by a
*     supported near-equivalent time-scale. Another example is if a basic
*     Channel encounters unexpected data items that may have been introduced
*     by later versions of AST.
*
*     2 - Report the above, and in addition report significant default
*     values. For instance, if no time-scale was specified when reading an
*     Object from an external data source, report the default time-scale
*     that is being used.
*
*     3 - Report the above, and in addition report any other potentially
*     interesting conditions that have no significant effect on the
*     conversion. For instance, report if a time-scale of "TT"
*     (terrestrial time) is used in place of "ET" (ephemeris time). This
*     change has no signficiant effect because ET is the predecessor of,
*     and is continuous with, TT. Synonyms such as "IAT" and "TAI" are
*     another example.
*
*     The default value is 1. Note, there are many other conditions that
*     can occur whilst reading an Object that completely prevent the
*     conversion taking place. Such conditions will always generate errors,
*     irrespective of the ReportLevel and Strict attributes. Note, failure
*     to write an Object to a Channel does not usually generate an error.
*     Instead,
c     astWrite
f     AST_WRITE
*     returns a function value of zero.

*  Applicability:
*     Channel
*        All Channels have this attribute.
*     FitsChan
*        All the conditions selected by the FitsChan Warnings attribute are
*        reported at level 1.
*att--
*/

/* This is an integer value with a value of -INT_MAX when undefined,
   yielding a default of one. */
astMAKE_CLEAR(Channel,ReportLevel,report_level,-INT_MAX)
astMAKE_GET(Channel,ReportLevel,int,1,( this->report_level != -INT_MAX ? this->report_level : 1 ))
astMAKE_SET(Channel,ReportLevel,int,report_level,value)
astMAKE_TEST(Channel,ReportLevel,( this->report_level != -INT_MAX ))

/*
*att++
*  Name:
*     Skip

*  Purpose:
*     Skip irrelevant data?

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer (boolean).

*  Description:
*     This is a boolean attribute which indicates whether the Object
*     data being read through a Channel are inter-mixed with other,
*     irrelevant, external data.
*
*     If Skip is zero (the default), then the source of input data is
*     expected to contain descriptions of AST Objects and comments and
*     nothing else (if anything else is read, an error will
*     result). If Skip is non-zero, then any non-Object data
*     encountered between Objects will be ignored and simply skipped
*     over in order to reach the next Object.

*  Applicability:
*     Channel
*        All Channels have this attribute.
*     FitsChan
*        The FitsChan class sets the default value of this attribute
*        to 1, so that all irrelevant FITS headers will normally be
*        ignored.
*     YamlChan
*        The YamlChan class ignores this attribute.
*att--
*/

/* This ia a boolean value (0 or 1) with a value of -INT_MAX when
   undefined but yielding a default of zero. */
astMAKE_CLEAR(Channel,Skip,skip,-INT_MAX)
astMAKE_GET(Channel,Skip,int,0,( this->skip != -INT_MAX ? this->skip : 0 ))
astMAKE_SET(Channel,Skip,int,skip,( value != 0 ))
astMAKE_TEST(Channel,Skip,( this->skip != -INT_MAX ))

/*
*att++
*  Name:
*     Strict

*  Purpose:
*     Report an error if any unexpected data items are found?

*  Type:
*     Public attribute.

*  Synopsis:
*     Integer (boolean).

*  Description:
*     This is a boolean attribute which indicates whether a warning
*     rather than an error should be issed for insignificant conversion
*     problems. If it is set non-zero, then fatal errors are issued
*     instead of warnings, resulting in the
c     AST error status being set.
f     inherited STATUS variable being set to an error value.
*     If Strict is zero (the default), then execution continues after minor
*     conversion problems, and a warning message is added to the Channel
*     structure. Such messages can be retrieved using the
c     astWarnings
f     AST_WARNINGS
*     function.

*  Notes:
*     - This attribute was introduced in AST version 5.0. Prior to this
*     version of AST unexpected data items read by a basic Channel always
*     caused an error to be reported. So applications linked against
*     versions of AST prior to version 5.0 may not be able to read Object
*     descriptions created by later versions of AST, if the Object's class
*     description has changed.

*  Applicability:
*     YamlChan
*        The YamlChan class ignores this attribute.
*att--
*/

/* This ia a boolean value (0 or 1) with a value of -INT_MAX when
   undefined but yielding a default of zero. */
astMAKE_CLEAR(Channel,Strict,strict,-INT_MAX)
astMAKE_GET(Channel,Strict,int,0,( this->strict != -INT_MAX ? this->strict : 0 ))
astMAKE_SET(Channel,Strict,int,strict,( value != 0 ))
astMAKE_TEST(Channel,Strict,( this->strict != -INT_MAX ))

/* Destructor. */
/* ----------- */
static void Delete( AstObject *obj, int *status ) {
/*
*  Name:
*     Delete

*  Purpose:
*     Destructor for Channel objects.

*  Type:
*     Private function.

*  Synopsis:
*     void Delete( AstObject *obj, int *status )

*  Description:
*     This function implements the destructor for Channel objects.

*  Parameters:
*     obj
*        Pointer to the object to be deleted.
*     status
*        Pointer to the inherited status variable.

*  Notes:
*     This function attempts to execute even if the global error status is
*     set.
*/

/* Local Variables: */
   AstChannel *this;               /* Pointer to Channel */

/* Obtain a pointer to the Channel structure. */
   this = (AstChannel *) obj;

/* Free memory used to store warnings. */
   astAddWarning( this, 0, NULL, NULL, status );

/* Close any open input or output files. */
   if( this->fd_in ) fclose( this->fd_in );
   if( this->fd_out ) fclose( this->fd_out );

/* Free file name memory. */
   this->fn_in = astFree( this->fn_in );
   this->fn_out = astFree( this->fn_out );
}

/* Copy constructor. */
/* ----------------- */
static void Copy( const AstObject *objin, AstObject *objout, int *status ) {
/*
*  Name:
*     Copy

*  Purpose:
*     Copy constructor for Channel objects.

*  Type:
*     Private function.

*  Synopsis:
*     void Copy( const AstObject *objin, AstObject *objout, int *status )

*  Description:
*     This function implements the copy constructor for Channel objects.

*  Parameters:
*     objin
*        Pointer to the object to be copied.
*     objout
*        Pointer to the object being constructed.
*     status
*        Pointer to the inherited status variable.

*  Notes:
*     -  This constructor makes a deep copy.
*/

/* Local Variables: */
   AstChannel *out;                /* Pointer to output Channel */

/* Check the global error status. */
   if ( !astOK ) return;

/* Obtain pointers to the input and output Channels. */
   out = (AstChannel *) objout;

/* Just clear any references to the input memory from the output Channel. */
   out->warnings = NULL;
   out->nwarn = 0;
   out->fd_in = NULL;
   out->fn_in = NULL;
   out->fd_out = NULL;
   out->fn_out = NULL;
}

/* Dump function. */
/* -------------- */
static void Dump( AstObject *this_object, AstChannel *channel, int *status ) {
/*
*  Name:
*     Dump

*  Purpose:
*     Dump function for Channel objects.

*  Type:
*     Private function.

*  Synopsis:
*     void Dump( AstObject *this, AstChannel *channel, int *status )

*  Description:
*     This function implements the Dump function which writes out data
*     for the Channel class to an output Channel.

*  Parameters:
*     this
*        Pointer to the Object whose data are being written.
*     channel
*        Pointer to the Channel to which the data are being written.
*     status
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   AstChannel *this;             /* Pointer to the Channel structure */
   const char *comment;          /* Pointer to comment string */
   int ival;                     /* Integer value */
   int set;                      /* Attribute value set? */

/* Check the global error status. */
   if ( !astOK ) return;

/* Obtain a pointer to the Channel structure. */
   this = (AstChannel *) this_object;

/* Write out values representing the instance variables for the
   Channel class.  Accompany these with appropriate comment strings,
   possibly depending on the values being written.*/

/* In the case of attributes, we first use the appropriate (private)
   Test...  member function to see if they are set. If so, we then use
   the (private) Get... function to obtain the value to be written
   out.

   For attributes which are not set, we use the astGet... method to
   obtain the value instead. This will supply a default value
   (possibly provided by a derived class which over-rides this method)
   which is more useful to a human reader as it corresponds to the
   actual default attribute value.  Since "set" will be zero, these
   values are for information only and will not be read back. */

/* Indent */
/* ------------ */
   set = TestIndent( this, status );
   ival = set ? GetIndent( this, status ) : astGetIndent( this );
   astWriteInt( channel, "Indnt", set, 0, ival, "Indentation increment" );

/* ReportLevel. */
/* ------------ */
   set = TestReportLevel( this, status );
   ival = set ? GetReportLevel( this, status ) : astGetReportLevel( this );
   astWriteInt( channel, "RpLev", set, 0, ival, "Error reporting level" );

/* Skip. */
/* ----- */
   set = TestSkip( this, status );
   ival = set ? GetSkip( this, status ) : astGetSkip( this );
   astWriteInt( channel, "Skip", set, 0, ival,
                ival ? "Ignore data between Objects" :
                       "No data allowed between Objects" );

/* Strict. */
/* ------- */
   set = TestStrict( this, status );
   ival = set ? GetStrict( this, status ) : astGetStrict( this );
   astWriteInt( channel, "Strict", set, 0, ival,
                ival ? "Report errors insead of warnings" :
                       "Report warnings instead of errors" );

/* Full. */
/* ----- */
   set = TestFull( this, status );
   ival = set ? GetFull( this, status ) : astGetFull( this );
   if ( ival < 0 ) {
      comment = "Suppress non-essential output";
   }else if ( ival == 0 ) {
      comment = "Output standard information";
   } else {
      comment = "Output maximum information";
   }
   astWriteInt( channel, "Full", set, 0, ival, comment );

/* Comment. */
/* -------- */
   set = TestComment( this, status );
   ival = set ? GetComment( this, status ) : astGetComment( this );
   astWriteInt( channel, "Comm", set, 0, ival,
                ival ? "Display comments" :
                       "Omit comments" );
}

/* Standard class functions. */
/* ========================= */
/* Implement the astIsAChannel and astCheckChannel functions using the
   macros defined for this purpose in the "object.h" header file. */
astMAKE_ISA(Channel,Object)
astMAKE_CHECK(Channel)

AstChannel *astChannel_( const char *(* source)( void ),
                         void (* sink)( const char * ),
                         const char *options, int *status, ...) {
/*
*+
*  Name:
*     astChannel

*  Purpose:
*     Create a Channel.

*  Type:
*     Protected function.

*  Synopsis:
*     #include "channel.h"
*     AstChannel *astChannel( const char *(* source)( void ),
*                             void (* sink)( const char * ),
*                             const char *options, ..., int *status )

*  Class Membership:
*     Channel constructor.

*  Description:
*     This function creates a new Channel and optionally initialises
*     its attributes.
*
*     A Channel implements low-level input/output for the AST library.
*     Writing an Object to a Channel (using astWrite) will generate a
*     textual representation of that Object, and reading from a
*     Channel (using astRead) will create a new Object from its
*     textual representation.
*
*     Normally, when you use a Channel, you should provide "source"
*     and "sink" functions which connect it to an external data store
*     by reading and writing the resulting text. By default, however,
*     a Channel will read from standard input and write to standard
*     output.

*  Parameters:
*     source
*        Pointer to a "source" function that takes no arguments and
*        returns a pointer to a null-terminated string.
*
*        This function will be used by the Channel to obtain lines of
*        input text. On each invocation, it should return a pointer to
*        the next input line read from some external data store, and a
*        NULL pointer when there are no more lines to read.
*
*        If "source" is NULL, the Channel will read from standard
*        input instead.
*     sink
*        Pointer to a "sink" function that takes a pointer to a
*        null-terminated string as an argument and returns void.
*
*        This function will be used by the Channel to deliver lines of
*        output text. On each invocation, it should deliver the
*        contents of the string supplied to some external data store.
*
*        If "sink" is NULL, the Channel will write to standard output
*        instead.
*     options
*        Pointer to a null-terminated string containing an optional
*        comma-separated list of attribute assignments to be used for
*        initialising the new Channel. The syntax used is identical to
*        that for the astSet function and may include "printf" format
*        specifiers identified by "%" symbols in the normal way.
*     status
*        Pointer to the inherited status variable.
*     ...
*        If the "options" string contains "%" format specifiers, then
*        an optional list of additional arguments may follow it in
*        order to supply values to be substituted for these
*        specifiers. The rules for supplying these are identical to
*        those for the astSet function (and for the C "printf"
*        function).

*  Returned Value:
*     astChannel()
*        A pointer to the new Channel.

*  Notes:
*     - A NULL pointer value will be returned if this function is
*     invoked with the global error status set, or if it should fail
*     for any reason.
*-

*  Implementation Notes:
*     - This function implements the basic Channel constructor which
*     is available via the protected interface to the Channel class.
*     A public interface is provided by the astChannelId_ function.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
   AstChannel *new;              /* Pointer to new Channel */
   va_list args;                 /* Variable argument list */

/* Get a pointer to the thread specific global data structure. */
   astGET_GLOBALS(NULL);

/* Check the global status. */
   if ( !astOK ) return NULL;

/* Initialise the Channel, allocating memory and initialising the
   virtual function table as well if necessary. Supply pointers to
   (local) wrapper functions that can invoke the source and sink
   functions with appropriate arguments for the C language. */
   new = astInitChannel( NULL, sizeof( AstChannel ), !class_init, &class_vtab,
                         "Channel", source, SourceWrap, sink, SinkWrap );

/* If successful, note that the virtual function table has been
   initialised. */
   if ( astOK ) {
      class_init = 1;

/* Obtain the variable argument list and pass it along with the
   options string to the astVSet method to initialise the new
   Channel's attributes. */
      va_start( args, status );
      astVSet( new, options, NULL, args );
      va_end( args );

/* If an error occurred, clean up by deleting the new object. */
      if ( !astOK ) new = astDelete( new );
   }

/* Return a pointer to the new Channel. */
   return new;
}

AstChannel *astChannelId_( const char *(* source)( void ),
                           void (* sink)( const char * ),
                           const char *options, ... ) {
/*
*++
*  Name:
c     astChannel
f     AST_CHANNEL

*  Purpose:
*     Create a Channel.

*  Type:
*     Public function.

*  Synopsis:
c     #include "channel.h"
c     AstChannel *astChannel( const char *(* source)( void ),
c                             void (* sink)( const char * ),
c                             const char *options, ... )
f     RESULT = AST_CHANNEL( SOURCE, SINK, OPTIONS, STATUS )

*  Class Membership:
*     Channel constructor.

*  Description:
*     This function creates a new Channel and optionally initialises
*     its attributes.
*
*     A Channel implements low-level input/output for the AST library.
c     Writing an Object to a Channel (using astWrite) will generate a
f     Writing an Object to a Channel (using AST_WRITE) will generate a
*     textual representation of that Object, and reading from a
c     Channel (using astRead) will create a new Object from its
f     Channel (using AST_READ) will create a new Object from its
*     textual representation.
*
*     Normally, when you use a Channel, you should provide "source"
c     and "sink" functions which connect it to an external data store
f     and "sink" routines which connect it to an external data store
*     by reading and writing the resulting text. By default, however,
*     a Channel will read from standard input and write to standard
*     output. Alternatively, a Channel can be told to read or write from
*     specific text files using the SinkFile and SourceFile attributes,
*     in which case no sink or source function need be supplied.

*  Parameters:
c     source
f     SOURCE = SUBROUTINE (Given)
c        Pointer to a source function that takes no arguments and
c        returns a pointer to a null-terminated string.  If no value
c        has been set for the SourceFile attribute, this function
c        will be used by the Channel to obtain lines of input text. On
c        each invocation, it should return a pointer to the next input
c        line read from some external data store, and a NULL pointer
c        when there are no more lines to read.
c
c        If "source" is NULL and no value has been set for the SourceFile
c        attribute, the Channel will read from standard input instead.
f        A source routine, which is a subroutine which takes a single
f        integer error status argument.   If no value has been set
f        for the SourceFile attribute, this routine will be used by
f        the Channel to obtain lines of input text. On each
f        invocation, it should read the next input line from some
f        external data store, and then return the resulting text to
f        the AST library by calling AST_PUTLINE. It should supply a
f        negative line length when there are no more lines to read.
f        If an error occurs, it should set its own error status
f        argument to an error value before returning.
f
f        If the null routine AST_NULL is suppied as the SOURCE value,
f        and no value has been set for the SourceFile attribute,
f        the Channel will read from standard input instead.
c     sink
f     SINK = SUBROUTINE (Given)
c        Pointer to a sink function that takes a pointer to a
c        null-terminated string as an argument and returns void.
c        If no value has been set for the SinkFile attribute, this
c        function will be used by the Channel to deliver lines of
c        output text. On each invocation, it should deliver the
c        contents of the string supplied to some external data store.
c
c        If "sink" is NULL, and no value has been set for the SinkFile
c        attribute, the Channel will write to standard output instead.
f        A sink routine, which is a subroutine which takes a single
f        integer error status argument.  If no value has been set
f        for the SinkFile attribute, this routine will be used by
f        the Channel to deliver lines of output text. On each
f        invocation, it should obtain the next output line from the
f        AST library by calling AST_GETLINE, and then deliver the
f        resulting text to some external data store.  If an error
f        occurs, it should set its own error status argument to an
f        error value before returning.
f
f        If the null routine AST_NULL is suppied as the SINK value,
f        and no value has been set for the SinkFile attribute,
f        the Channel will write to standard output instead.
c     options
f     OPTIONS = CHARACTER * ( * ) (Given)
c        Pointer to a null-terminated string containing an optional
c        comma-separated list of attribute assignments to be used for
c        initialising the new Channel. The syntax used is identical to
c        that for the astSet function and may include "printf" format
c        specifiers identified by "%" symbols in the normal way.
f        A character string containing an optional comma-separated
f        list of attribute assignments to be used for initialising the
f        new Channel. The syntax used is identical to that for the
f        AST_SET routine.
c     ...
c        If the "options" string contains "%" format specifiers, then
c        an optional list of additional arguments may follow it in
c        order to supply values to be substituted for these
c        specifiers. The rules for supplying these are identical to
c        those for the astSet function (and for the C "printf"
c        function).
f     STATUS = INTEGER (Given and Returned)
f        The global status.

*  Returned Value:
c     astChannel()
f     AST_CHANNEL = INTEGER
*        A pointer to the new Channel.

*  Notes:
c     - Application code can pass arbitrary data (such as file
c     descriptors, etc) to source and sink functions using the
c     astPutChannelData function. The source or sink function should use
c     the astChannelData macro to retrieve this data.
f     - The names of the routines supplied for the SOURCE and SINK
f     arguments should appear in EXTERNAL statements in the Fortran
f     routine which invokes AST_CHANNEL. However, this is not generally
f     necessary for the null routine AST_NULL (so long as the AST_PAR
f     include file has been used).
*     - A null Object pointer (AST__NULL) will be returned if this
c     function is invoked with the AST error status set, or if it
f     function is invoked with STATUS set to an error value, or if it
*     should fail for any reason.
f     - Note that the null routine AST_NULL (one underscore) is
f     different to AST__NULL (two underscores), which is the null Object
f     pointer.
*--

*  Implementation Notes:
*     - This function implements the external (public) interface to
*     the astChannel constructor function. It returns an ID value
*     (instead of a true C pointer) to external users, and must be
*     provided because astChannel_ has a variable argument list which
*     cannot be encapsulated in a macro (where this conversion would
*     otherwise occur).
*     - The variable argument list also prevents this function from
*     invoking astChanel_ directly, so it must be a re-implementation
*     of it in all respects, except for the final conversion of the
*     result to an ID value.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
   AstChannel *new;              /* Pointer to new Channel */
   va_list args;                 /* Variable argument list */

   int *status;                  /* Pointer to inherited status value */

/* Get a pointer to the inherited status value. */
   status = astGetStatusPtr;

/* Get a pointer to the thread specific global data structure. */
   astGET_GLOBALS(NULL);

/* Check the global status. */
   if ( !astOK ) return NULL;

/* Initialise the Channel, allocating memory and initialising the
   virtual function table as well if necessary. Supply pointers to
   (local) wrapper functions that can invoke the source and sink
   functions with appropriate arguments for the C language. */
   new = astInitChannel( NULL, sizeof( AstChannel ), !class_init, &class_vtab,
                         "Channel", source, SourceWrap, sink, SinkWrap );

/* If successful, note that the virtual function table has been
   initialised. */
   if ( astOK ) {
      class_init = 1;

/* Obtain the variable argument list and pass it along with the
   options string to the astVSet method to initialise the new
   Channel's attributes. */
      va_start( args, options );
      astVSet( new, options, NULL, args );
      va_end( args );

/* If an error occurred, clean up by deleting the new object. */
      if ( !astOK ) new = astDelete( new );
   }

/* Return an ID value for the new Channel. */
   return astMakeId( new );
}

AstChannel *astChannelForId_( const char *(* source)( void ),
                              char *(* source_wrap)( const char *(*)( void ), int * ),
                              void (* sink)( const char * ),
                              void (* sink_wrap)( void (*)( const char * ),
                                                  const char *, int * ),
                              const char *options, ... ) {
/*
*+
*  Name:
*     astChannelFor

*  Purpose:
*     Initialise a Channel from a foreign language interface.

*  Type:
*     Public function.

*  Synopsis:
*     #include "channel.h"
*     AstChannel *astChannelFor( const char *(* source)( void ),
*                                char *(* source_wrap)( const char *(*)
*                                                       ( void ), int * ),
*                                void (* sink)( const char * ),
*                                void (* sink_wrap)( void (*)( const char * ),
*                                                    const char *, int * ),
*                                const char *options, ... )

*  Class Membership:
*     Channel constructor.

*  Description:
*     This function creates a new Channel from a foreign language
*     interface and optionally initialises its attributes.
*
*     A Channel implements low-level input/output for the AST library.
*     Writing an Object to a Channel (using astWrite) will generate a
*     textual representation of that Object, and reading from a
*     Channel (using astRead) will create a new Object from its
*     textual representation.
*
*     Normally, when you use a Channel, you should provide "source"
*     and "sink" functions which connect it to an external data store
*     by reading and writing the resulting text. This function also
*     requires you to provide "wrapper" functions which will invoke
*     the source and sink functions. By default, however, a Channel
*     will read from standard input and write to standard output.

*  Parameters:
*     source
*        Pointer to a "source" function which will be used to obtain
*        lines of input text. Generally, this will be obtained by
*        casting a pointer to a source function which is compatible
*        with the "source_wrap" wrapper function (below). The pointer
*        should later be cast back to its original type by the
*        "source_wrap" function before the function is invoked.
*
*        If "source" is NULL, the Channel will read from standard
*        input instead.
*     source_wrap
*        Pointer to a function which can be used to invoke the
*        "source" function supplied (above). This wrapper function is
*        necessary in order to hide variations in the nature of the
*        source function, such as may arise when it is supplied by a
*        foreign (non-C) language interface.
*
*        The single parameter of the "source_wrap" function is a
*        pointer to the "source" function, and it should cast this
*        function pointer (as necessary) and invoke the function with
*        appropriate arguments to obtain the next line of input
*        text. The "source_wrap" function should then return a pointer
*        to a dynamically allocated, null terminated string containing
*        the text that was read. The string will be freed (using
*        astFree) when no longer required and the "source_wrap"
*        function need not concern itself with this. A NULL pointer
*        should be returned if there is no more input to read.
*
*        If "source_wrap" is NULL, the Channel will read from standard
*        input instead.
*     sink
*        Pointer to a "sink" function which will be used to deliver
*        lines of output text. Generally, this will be obtained by
*        casting a pointer to a sink function which is compatible with
*        the "sink_wrap" wrapper function (below). The pointer should
*        later be cast back to its original type by the "sink_wrap"
*        function before the function is invoked.
*
*        If "sink" is NULL, the Channel will write to standard output
*        instead.
*     sink_wrap
*        Pointer to a function which can be used to invoke the "sink"
*        function supplied (above). This wrapper function is necessary
*        in order to hide variations in the nature of the sink
*        function, such as may arise when it is supplied by a foreign
*        (non-C) language interface.
*
*        The first parameter of the "sink_wrap" function is a pointer
*        to the "sink" function, and the second parameter is a pointer
*        to a const, null-terminated character string containing the
*        text to be written.  The "sink_wrap" function should cast the
*        "sink" function pointer (as necessary) and invoke the
*        function with appropriate arguments to deliver the line of
*        output text. The "sink_wrap" function then returns void.
*
*        If "sink_wrap" is NULL, the Channel will write to standard
*        output instead.
*     options
*        Pointer to a null-terminated string containing an optional
*        comma-separated list of attribute assignments to be used for
*        initialising the new Channel. The syntax used is identical to
*        that for the astSet function and may include "printf" format
*        specifiers identified by "%" symbols in the normal way.
*     ...
*        If the "options" string contains "%" format specifiers, then
*        an optional list of additional arguments may follow it in
*        order to supply values to be substituted for these
*        specifiers. The rules for supplying these are identical to
*        those for the astSet function (and for the C "printf"
*        function).

*  Returned Value:
*     astChannelFor()
*        A pointer to the new Channel.

*  Notes:
*     - A null Object pointer (AST__NULL) will be returned if this
*     function is invoked with the global error status set, or if it
*     should fail for any reason.
*     - This function is only available through the public interface
*     to the Channel class (not the protected interface) and is
*     intended solely for use in implementing foreign language
*     interfaces to this class.
*-

*  Implememtation Notes:
*     - This function behaves exactly like astChannelId_, in that it
*     returns ID values and not true C pointers, but it has two
*     additional arguments. These are pointers to the "wrapper
*     functions" which are needed to accommodate foreign language
*     interfaces.
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Declare the thread specific global data */
   AstChannel *new;              /* Pointer to new Channel */
   va_list args;                 /* Variable argument list */
   int *status;                  /* Pointer to inherited status value */

/* Get a pointer to the inherited status value. */
   status = astGetStatusPtr;

/* Check the global status. */
   if ( !astOK ) return NULL;

/* Get a pointer to the thread specific global data structure. */
   astGET_GLOBALS(NULL);

/* Initialise the Channel, allocating memory and initialising the
   virtual function table as well if necessary. */
   new = astInitChannel( NULL, sizeof( AstChannel ), !class_init, &class_vtab,
                         "Channel", source, source_wrap, sink, sink_wrap );

/* If successful, note that the virtual function table has been
   initialised. */
   if ( astOK ) {
      class_init = 1;

/* Obtain the variable argument list and pass it along with the
   options string to the astVSet method to initialise the new
   Channel's attributes. */
      va_start( args, options );
      astVSet( new, options, NULL, args );
      va_end( args );

/* If an error occurred, clean up by deleting the new object. */
      if ( !astOK ) new = astDelete( new );
   }

/* Return an ID value for the new Channel. */
   return astMakeId( new );
}

AstChannel *astLoadChannel_( void *mem, size_t size,
                             AstChannelVtab *vtab, const char *name,
                             AstChannel *channel, int *status ) {
/*
*+
*  Name:
*     astLoadChannel

*  Purpose:
*     Load a Channel.

*  Type:
*     Protected function.

*  Synopsis:
*     #include "channel.h"
*     AstChannel *astLoadChannel( void *mem, size_t size,
*                                 AstChannelVtab *vtab, const char *name,
*                                 AstChannel *channel )

*  Class Membership:
*     Channel loader.

*  Description:
*     This function is provided to load a new Channel using data read
*     from a Channel. It first loads the data used by the parent class
*     (which allocates memory if necessary) and then initialises a
*     Channel structure in this memory, using data read from the input
*     Channel.
*
*     If the "init" flag is set, it also initialises the contents of a
*     virtual function table for a Channel at the start of the memory
*     passed via the "vtab" parameter.


*  Parameters:
*     mem
*        A pointer to the memory into which the Channel is to be
*        loaded.  This must be of sufficient size to accommodate the
*        Channel data (sizeof(Channel)) plus any data used by derived
*        classes. If a value of NULL is given, this function will
*        allocate the memory itself using the "size" parameter to
*        determine its size.
*     size
*        The amount of memory used by the Channel (plus derived class
*        data).  This will be used to allocate memory if a value of
*        NULL is given for the "mem" parameter. This value is also
*        stored in the Channel structure, so a valid value must be
*        supplied even if not required for allocating memory.
*
*        If the "vtab" parameter is NULL, the "size" value is ignored
*        and sizeof(AstChannel) is used instead.
*     vtab
*        Pointer to the start of the virtual function table to be
*        associated with the new Channel. If this is NULL, a pointer
*        to the (static) virtual function table for the Channel class
*        is used instead.
*     name
*        Pointer to a constant null-terminated character string which
*        contains the name of the class to which the new object
*        belongs (it is this pointer value that will subsequently be
*        returned by the astGetClass method).
*
*        If the "vtab" parameter is NULL, the "name" value is ignored
*        and a pointer to the string "Channel" is used instead.

*  Returned Value:
*     A pointer to the new Channel.

*  Notes:
*     - A null pointer will be returned if this function is invoked
*     with the global error status set, or if it should fail for any
*     reason.
*-
*/

/* Local Variables: */
   astDECLARE_GLOBALS            /* Pointer to thread-specific global data */
   AstChannel *new;              /* Pointer to the new Channel */

/* Initialise. */
   new = NULL;

/* Check the global error status. */
   if ( !astOK ) return new;

/* Get a pointer to the thread specific global data structure. */
   astGET_GLOBALS(channel);

/* If a NULL virtual function table has been supplied, then this is
   the first loader to be invoked for this Channel. In this case the
   Channel belongs to this class, so supply appropriate values to be
   passed to the parent class loader (and its parent, etc.). */
   if ( !vtab ) {
      size = sizeof( AstChannel );
      vtab = &class_vtab;
      name = "Channel";

/* If required, initialise the virtual function table for this class. */
      if ( !class_init ) {
         astInitChannelVtab( vtab, name );
         class_init = 1;
      }
   }

/* Invoke the parent class loader to load data for all the ancestral
   classes of the current one, returning a pointer to the resulting
   partly-built Channel. */
   new = astLoadObject( mem, size, (AstObjectVtab *) vtab, name,
                        channel );

   if ( astOK ) {

/* Read input data. */
/* ================ */
/* Request the input Channel to read all the input data appropriate to
   this class into the internal "values list". */
      astReadClassData( channel, "Channel" );

/* Set the pointers to the source and sink functions, and their
   wrapper functions, to NULL (we cannot restore these since they
   refer to process-specific addresses). */
      new->source = NULL;
      new->source_wrap = NULL;
      new->sink = NULL;
      new->sink_wrap = NULL;

/* We do not have any data to pass to the source and sink functions. */
      new->data = NULL;

/* No warnings yet. */
      new->warnings = NULL;
      new->nwarn = 0;

/* Indicate no input or output files have been associated with the
   Channel. */
      new->fd_in = NULL;
      new->fn_in = NULL;
      new->fd_out = NULL;
      new->fn_out = NULL;

/* Now read each individual data item from this list and use it to
   initialise the appropriate instance variable(s) for this class. */

/* In the case of attributes, we first read the "raw" input value,
   supplying the "unset" value as the default. If a "set" value is
   obtained, we then use the appropriate (private) Set... member
   function to validate and set the value properly. */

/* Indent. */
/* ------- */
      new->indent = astReadInt( channel, "indnt", -INT_MAX );
      if ( TestIndent( new, status ) ) SetIndent( new, new->indent, status );

/* ReportLevel. */
/* ------------ */
      new->report_level = astReadInt( channel, "rplev", -INT_MAX );
      if ( TestReportLevel( new, status ) ) SetReportLevel( new,
                                                            new->report_level,
                                                            status );

/* Skip. */
/* ----- */
      new->skip = astReadInt( channel, "skip", -INT_MAX );
      if ( TestSkip( new, status ) ) SetSkip( new, new->skip, status );

/* Strict. */
/* ------- */
      new->strict = astReadInt( channel, "strict", -INT_MAX );
      if ( TestStrict( new, status ) ) SetStrict( new, new->strict, status );

/* Full. */
/* ----- */
      new->full = astReadInt( channel, "full", -INT_MAX );
      if ( TestFull( new, status ) ) SetFull( new, new->full, status );

/* Comment. */
/* -------- */
      new->comment = astReadInt( channel, "comm", -INT_MAX );
      if ( TestComment( new, status ) ) SetComment( new, new->comment, status );

/* If an error occurred, clean up by deleting the new Channel. */
      if ( !astOK ) new = astDelete( new );
   }

/* Return the new Channel pointer. */
   return new;
}

/* Virtual function interfaces. */
/* ============================ */
/* These provide the external interface to the virtual functions
   defined by this class. Each simply checks the global error status
   and then locates and executes the appropriate member function,
   using the function pointer stored in the object's virtual function
   table (this pointer is located using the astMEMBER macro defined in
   "object.h").

   Note that the member function may not be the one defined here, as
   it may have been over-ridden by a derived class. However, it should
   still have the same interface. */
void astGetNextData_( AstChannel *this, int begin, char **name, char **val, int *status ) {
   *name = NULL;
   *val = NULL;
   if ( !astOK ) return;
   (**astMEMBER(this,Channel,GetNextData))( this, begin, name, val, status );
}
char *astGetNextText_( AstChannel *this, int *status ) {
   if ( !astOK ) return NULL;
   return (**astMEMBER(this,Channel,GetNextText))( this, status );
}
void astPutNextText_( AstChannel *this, const char *line, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,Channel,PutNextText))( this, line, status );
}
AstObject *astRead_( AstChannel *this, int *status ) {
   if ( !astOK ) return NULL;
   astAddWarning( this, 0, NULL, NULL, status );
   return (**astMEMBER(this,Channel,Read))( this, status );
}
void astReadClassData_( AstChannel *this, const char *class, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,Channel,ReadClassData))( this, class, status );
}
double astReadDouble_( AstChannel *this, const char *name, double def, int *status ) {
   if ( !astOK ) return 0.0;
   return (**astMEMBER(this,Channel,ReadDouble))( this, name, def, status );
}
int astReadInt_( AstChannel *this, const char *name, int def, int *status ) {
   if ( !astOK ) return 0;
   return (**astMEMBER(this,Channel,ReadInt))( this, name, def, status );
}
int64_t astReadInt64_( AstChannel *this, const char *name, int64_t def, int *status ) {
   if ( !astOK ) return 0;
   return (**astMEMBER(this,Channel,ReadInt64))( this, name, def, status );
}
AstObject *astReadObject_( AstChannel *this, const char *name,
                           AstObject *def, int *status ) {
   if ( !astOK ) return NULL;
   return (**astMEMBER(this,Channel,ReadObject))( this, name, def, status );
}
char *astReadString_( AstChannel *this, const char *name, const char *def, int *status ) {
   if ( !astOK ) return NULL;
   return (**astMEMBER(this,Channel,ReadString))( this, name, def, status );
}
void astWriteBegin_( AstChannel *this, const char *class,
                     const char *comment, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,Channel,WriteBegin))( this, class, comment, status );
}
void astWriteDouble_( AstChannel *this, const char *name, int set, int helpful,
                      double value, const char *comment, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,Channel,WriteDouble))( this, name, set, helpful, value,
                                            comment, status );
}
void astWriteEnd_( AstChannel *this, const char *class, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,Channel,WriteEnd))( this, class, status );
}
void astWriteInt_( AstChannel *this, const char *name, int set, int helpful,
                   int value, const char *comment, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,Channel,WriteInt))( this, name, set, helpful, value,
                                         comment, status );
}
void astWriteInt64_( AstChannel *this, const char *name, int set, int helpful,
                     int64_t value, const char *comment, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,Channel,WriteInt64))( this, name, set, helpful, value,
                                           comment, status );
}
void astWriteIsA_( AstChannel *this, const char *class, const char *comment, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,Channel,WriteIsA))( this, class, comment, status );
}
void astWriteString_( AstChannel *this, const char *name, int set, int helpful,
                      const char *value, const char *comment, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,Channel,WriteString))( this, name, set, helpful, value,
                                            comment, status );
}
void astPutChannelData_( AstChannel *this, void *data, int *status ) {
   if ( !astOK ) return;
   (**astMEMBER(this,Channel,PutChannelData))( this, data, status );
}
AstKeyMap *astWarnings_( AstChannel *this, int *status ){
   if( !astOK ) return NULL;
   return (**astMEMBER(this,Channel,Warnings))( this, status );
}
void astWriteFlush_( AstChannel *this, int *status ){
   if( !astOK ) return;
   return (**astMEMBER(this,Channel,WriteFlush))( this, status );
}

/* Because of the variable argument list, we need to work a bit harder on
   astAddWarning. Functions that provide implementations of the
   astAddWarning method recieve the fully expanded message and so do not
   need a variable argument list. */

void astAddWarning_( void *this_void, int level, const char *fmt,
                     const char *method, int *status, ... ) {
   AstChannel *this;
   char buff[ 201 ];
   va_list args;
   int nc;

   this = astCheckChannel( this_void );

   if( fmt ) {
      if( astOK ) {
         va_start( args, status );
         nc = vsprintf( buff, fmt, args );
         va_end( args );
         if( nc > 200 ) {
            astError( AST__INTER, "astAddWarning(%s): Message buffer size "
                      "exceeded (internal AST programming error).",
                      status, astGetClass( this ) );
         } else {
            (**astMEMBER(this,Channel,AddWarning))( this, level, buff, method, status );
         }
      }
   } else {
      (**astMEMBER(this,Channel,AddWarning))( this, level, NULL, method, status );
   }
}

/* Count the number of times astWrite is invoked (excluding invocations
   made from within the astWriteObject method - see below). The count is
   done here so that invocations of astWrite within a sub-class will be
   included. */
int astWrite_( AstChannel *this, AstObject *object, int *status ) {
   astDECLARE_GLOBALS
   if ( !astOK ) return 0;
   astGET_GLOBALS(this);
   nwrite_invoc++;
   astAddWarning( this, 0, NULL, NULL, status );
   return (**astMEMBER(this,Channel,Write))( this, object, status );
}

/* We do not want to count invocations of astWrite made from within the
   astWriteObject method. So decrement the number of invocations first
   (this assumes that each invocation of astWriteObject will only invoke
   astWrite once). */
void astWriteObject_( AstChannel *this, const char *name, int set,
                      int helpful, AstObject *value, const char *comment, int *status ) {
   astDECLARE_GLOBALS
   if ( !astOK ) return;
   astGET_GLOBALS(this);
   nwrite_invoc--;
   (**astMEMBER(this,Channel,WriteObject))( this, name, set, helpful, value,
                                            comment, status );
}





