/* r4save_2.c   (c)Copyright Sequiter Software Inc., 1991-1994.  All rights reserved. */
#include "d4all.h"

extern int file_version;
extern LIST4 name_list;

int save4long( FILE4SEQ_WRITE *seq, long *lval );
int save4short( FILE4SEQ_WRITE *seq, short *sval );
int ret4long( FILE4SEQ_READ *seq, long *lval );
int ret4short( FILE4SEQ_READ *seq, short *sval );

/************************************************************
 *
 * Function:
 *
 *  PARAMETERS:
 *    DATA4* data - data file
 *    char* index_name - name of the index file to be checked for
 *    char* index_lookup - buffer used in the function
 *    char* current - buffer used in the function
 *
 *  DESCRIPTION:this function performs the same operation as d4index(), but
 *   checks for an index file both with and without path names
 *
 *  RETURNS: 1 - on finding a matching index file, 0 - on failure
 *
 *  By: Raymond Cypher
 *
 *  HISTORY:
 *
 */

/* this function performs the same operation as d4index(), but checks for an
   index file both with and without path names
 */
int S4FUNCTION r4index_lookup_foo( DATA4 *data, char *index_name,
                                   char *index_lookup, char *current )
{
   INDEX4 *index_on ;
   int ttype;


   if ( data == 0 || index_name == 0 )
      #ifdef S4DEBUG
         e4severe( e4parm, E4_D4INDEX ) ;
      #else
         return 0 ;
      #endif

   ttype = report4index_type();
   u4name_piece( index_lookup, 257, index_name, 1, 0 ) ;
   #ifndef S4UNIX
      c4upper( index_lookup ) ;
   #endif

   if( r4cli == ttype || r4ndx == ttype )
   {
      if( d4tag( data, index_lookup ) )
         return 1;
      u4name_piece( index_lookup, 257, index_name, 0, 0 ) ;
      if( d4tag( data, index_lookup ) )
         return 1;
   }
   else
   {
      index_on = (INDEX4 *)l4first( &data->indexes );
      while( index_on )
      {
         u4name_piece( current, 257, index_on->file.name, 1, 0 ) ;
         #ifndef S4UNIX
            c4upper( current ) ;
         #endif
         if ( !strcmp( current, index_lookup ) )    /* check out data->alias? */
            return 1;
         index_on = (INDEX4 *) l4next( &data->indexes, index_on) ;
      }

      u4name_piece( index_lookup, 257, index_name, 0, 0 ) ;
      #ifndef S4UNIX
         c4upper( index_lookup ) ;
      #endif

      index_on = (INDEX4 *)l4first( &data->indexes );
      while( index_on )
      {
         u4name_piece( current, 257, index_on->file.name, 0, 0 ) ;
         #ifndef S4UNIX
            c4upper( current ) ;
         #endif
         if ( !strcmp( current, index_lookup ) )    /* check out data->alias? */
            return 1;
         index_on = (INDEX4 *) l4next( &data->indexes, index_on) ;
      }

   }

   return( 0 );
}

/* wrapper for above function */
int S4FUNCTION r4index_lookup( DATA4 *data, char *index_name )
{
   char *index_lookup, *current;
   int retvalue;

   index_lookup = (char *)u4alloc_free( data->code_base, 258 );
   if( !index_lookup )
      return 0;

   current = (char*)u4alloc_free( data->code_base, 258 );
   if( !current )
   {
      u4free( index_lookup );
      return 0;
   }

   retvalue = r4index_lookup_foo( data, index_name, index_lookup, current );

   u4free( index_lookup );
   u4free( current );
   return retvalue;
}

/************************************************************
 *
 * Function: r4open_data_foo()
 *
 *  PARAMETERS:
 *    char* file_name - full name of the file, potentially including path
 *    char* alias - alias to set for the data file
 *    RELATE4* relate - the relate associated with the current report
 *    CODE4* code_base - CODE4 structure for the app
 *    char* fnbuf - a buffer used by the function
 *
 *  DESCRIPTION: opens a data file, if the file is already open in the relate
 *   appropriate aliasing is performed to open the file a second time
 *
 *  RETURNS: a DATA4 pointer on success, NULL on failure
 *
 *  By: Raymond Cypher
 *
 *  HISTORY:
 *
 */
DATA4 *r4open_data_foo( char *file_name, char *alias, RELATE4 *relate, CODE4 *code_base, char *fnbuf )
{
   DATA4   *old, *nnew;
   RELATE4 *relate_on;
   char    abuf[11];

   if( !file_name || !alias )
      return NULL;

   nnew = old = NULL;
   /* if no alias or no relate do a simple d4open() */
   if( alias[0] == '\0' || !relate )
   {
      nnew = d4open( code_base, file_name );
      return nnew;
   }

   #ifndef S4UNIX
      c4upper( file_name );
      c4upper( alias );
   #endif

   /* check to see if the file is already open */
   relate_on = relate;
   while( relate_on )
   {
      strcpy( fnbuf, relate_on->data->file.name );
      #ifndef S4UNIX
         c4upper( fnbuf );
      #endif
      strcpy( abuf, d4alias( relate_on->data ) );
      #ifndef S4UNIX
         c4upper( abuf );
      #endif
      if( strcmp( fnbuf, file_name ) == 0 && strcmp(abuf, alias) == 0 )
         old = relate_on->data;

      relate4next( &relate_on );
   }

   /* if the file is not already open do a d4open() and set the alias */
   if( old == NULL )
   {
      nnew = d4open( code_base, file_name );
      if( nnew && alias[0] != '\0' )
         d4alias_set( nnew, alias );
      return nnew;
   }

   /* save the existing files alias, reset it to xxxxxxx open the new file,
      set the new files alias, then restore the old files alias  */
   strcpy( abuf, d4alias(old) );
   d4alias_set( old, "XXXXXXXXXX" );
   nnew = d4open( code_base, file_name );
   if( nnew )
      d4alias_set( nnew, alias );
   d4alias_set( old, abuf );

   return nnew;

}

/* wrapper for above function */
DATA4 *r4open_data( char *file_name, char *alias, RELATE4 *relate, CODE4 *code_base )
{
   DATA4 *retvalue;
   char *fnbuf;

   fnbuf = (char *)u4alloc_free( code_base, 256 );
   if( !fnbuf )
      return NULL;

   retvalue = r4open_data_foo( file_name, alias, relate, code_base, fnbuf );

   u4free( fnbuf );
   return retvalue;
}


/* frees the list of N4CHANGE structures */
void report4free_name_list()
{
   PN4CHANGE nchange;

   while( (nchange = (PN4CHANGE)l4pop( &name_list )) != NULL )
   {
      if( nchange->old_name )
         u4free( nchange->old_name );

      if( nchange->new_name )
         u4free( nchange->new_name );

      u4free( nchange );
   }
}

/* simple memcompare function that first capitalizes the buffers being compared */
int r4memicmp_foo( char *str1, char *str2, int len, char *buffer )
{
   memset( buffer, 0, len + 1 );
   memcpy( buffer, str1, len );
   #ifndef S4UNIX
      c4upper( buffer );
      c4upper( str2 );
   #endif
   return memcmp( str1, str2, len );
}

/* wrapper for above function */
int r4memicmp( char *str1, char *str2, int len )
{
   char *buffer;
   int retvalue;

   buffer = (char *)u4alloc( len+2 );
   if( !buffer )
      return 1;

   retvalue = r4memicmp_foo( str1, str2, len, buffer );
   u4free( buffer );
   return retvalue;
}

/************************************************************
 *
 * Function: report4nchange()
 *
 *  PARAMETERS:
 *    CODE4* c4 - apps code4 struct
 *    char** psrc - pointer to a pointer to the source string
 *    int can_alloc - flag specifying whether or not to allocate additional
 *     memory as needed
 *    int s_size - size of the source string
 *
 *  DESCRIPTION: in CR when opening a report file, if a data or index file
 *   cannot be found the user is prompted to enter a new path and/or name.
 *   If the name of a data file has changed the name must also be changed in
 *   all the expressions within the report.  To do this a list of N4CHANGE
 *   structures is maintained.  N4CHANGE simply contains the old and new names
 *   for a data file.  This function is passed an expression source and is
 *   responsible for swapping the names.
 *
 *  RETURNS: none
 *
 *  By: Raymond Cypher
 *
 *  HISTORY:
 *
 */
void report4nchange( CODE4 *c4, char **psrc, int can_alloc, int s_size )
{
   char      *orig = NULL, *src = *psrc;
   PN4CHANGE nchange;
   int       pos, src_pos, orig_len;
   int       old_name_len, new_name_len;
   unsigned  src_size = s_size;

   if( strlen(src) == 0 )
      return;

   /* for ever N4CHANGE struct in the name list */
   nchange = (PN4CHANGE)l4first( &name_list );
   while( nchange )
   {
      /* if no names continue */
      if( !nchange->new_name || !nchange->old_name )
         continue;

      old_name_len = strlen(nchange->old_name);

      /* create a buffer to duplicate the expression source */
      orig = (char *)u4alloc_free( c4, strlen(src) + 1 );
      if( !orig )
         return;

      /* copy the expression source */
      src_pos = 0;
      strcpy( orig, src );
      orig_len = strlen(orig);

      /* run through the copy of the expression source */
      for( pos = 0; orig[pos]; pos++ )
      {
         /* we are copying the duplicated expression source back into the
            original buffer, with appropriate insertions */
         src[src_pos++] = orig[pos];

         /* if the expression source buffer is too small due to changes
            do a re-allocation */
         if( src_pos == (int)src_size )
         {
            if( !can_alloc )
            {
               u4free( orig );
               return;
            }
            u4alloc_again( c4, &src, &src_size, src_size+50 );
            if( !src )
               return;
         }

         /* if we're at the end of the expression source, continue */
         if( (orig_len - pos) < old_name_len )
            continue;

         /* is this piece of the expression the current old name */
         if( r4memicmp( orig+pos, nchange->old_name, old_name_len ) != 0 )
            continue;

         if( u4name_char( orig[pos+old_name_len] ) )
            continue;

         if( pos > 0 )
            if( u4name_char( orig[pos-1]) )
               continue;

         /* insert the new name into the expression source, re-allocating space
            if necessary */
         new_name_len = strlen( nchange->new_name );
         if( (int)src_size <= (pos+new_name_len) )
         {
            if( !can_alloc )
            {
               u4free( orig );
               return;
            }

            u4alloc_again( c4, &src, &src_size, src_size + 50 );
            if( !src )
            {
               u4free( orig );
               return;
            }
         }
         memcpy( src+(--src_pos), nchange->new_name, new_name_len );
         src_pos += new_name_len;
         pos += (old_name_len - 1);
      } /* end of loop through copy of expression source */

      /* end the string */
      src[src_pos++] = '\0';

      /* next change name */
      nchange = (PN4CHANGE)l4next( &name_list, nchange );
      /* free the source duplicate buffer */
      if( orig )
      {
         u4free( orig );
         orig = NULL;
      }
   }

   if( orig )
   {
      u4free( orig );
      orig = NULL;
   }

   /* set the pointer to the new source buffer */
   *psrc = src;
}



/************************************************************
 *
 * Function: relate4retrieve_relate_foo()
 *
 *  PARAMETERS:
 *   FILE4SEQ_READ* seq - sequential read struture for the file i/o
 *   int open_files - should the data files for the relate be opened
 *   char* spath - path to look for the data files
 *   the rest of the parameters are simply character buffers used in the fctn
 *
 *  DESCRIPTION: retrieves a relation from a file.  Note: this file and the
 *   r4save.c source file are compiled directly into the CR executable, as well
 *   as into the CodeBase DLL.  As a result certain sections of this code
 *   are specific the the executable and call functions in the executable
 *
 *  RETURNS: a RELATE4 pointer on success, NULL on failure
 *
 *  By: Raymond Cypher
 *
 *  HISTORY:
 *
 */
RELATE4 * S4FUNCTION   relate4retrieve_relate_foo( FILE4SEQ_READ *seq, int open_files, char *spath,
                          char *dname_buf, char *iname_buf, char *tname_buf,
                          char *str_buf, char *master_expr_buf,
                          char *slave_expr_buf, char *tempname_buf )
{
   RELATE4 *relate = NULL, *master = NULL;
   DATA4   *data;
   TAG4    *tag;
   INDEX4  *index;
   LIST4   indexes;
   CODE4   *c4;
   INAME4  *iname, *nname;
   EXPR4   *expr;
   char alias_buf[11], *cptr;
   short match_len, relation_type, sort_type, error_action, n_links, code;
   int i, err_code, err_flag, repeat_flag, error_code = 0, iindex, tname_err;
   long lExt;
   #ifdef S4CR2
      PN4CHANGE nchange;
      int rc;
   #endif

   /* reset the changed name list */
   memset( &name_list, 0, sizeof(name_list) );

   c4 = seq->file->code_base;

   /* reset the index name list */
   memset( &indexes, 0, sizeof(indexes) );
   err_code = err_flag = 0;

   while( 1 )
   {
      /* get the data file name */
      if( retrieve4string( seq, dname_buf, 256 ) < 0 )
      {
         /* the error codes are used to allow the continued retrieval of
            the relation tree, even if a part of it is not retrievable due
            to a missing data or index file */
         error_code = 1;
         goto CLEANUP;
      }

      /* get the files alias */
      memset( alias_buf, 0, sizeof(alias_buf) );
      if( file_version >= 0x24 )
      {
         if( retrieve4string( seq, alias_buf, sizeof(alias_buf) ) < 0 )
         {
            error_code = 1;
            goto CLEANUP;
         }
      }

      /* retrieve the internal flags of the RELATE4 */
      ret4short( seq, &match_len );
      ret4short( seq, &relation_type );
      ret4short( seq, &sort_type );
      ret4short( seq, &error_action );

      /* retrieve the names of the index files and place them in the index list
       */
      ret4short( seq, &n_links );
      for( i = 0; i < n_links; i++ )
      {
         iname = (INAME4 *)u4alloc_free( c4, sizeof(INAME4) );
         if( retrieve4string( seq, iname_buf, 256 ) < 0 )
         {
            error_code = 1;
            goto CLEANUP;
         }
         iname->index_name = (char *)u4alloc_free( c4, strlen(iname_buf) + 1 );
         iname->name_length = strlen(iname_buf) + 1;
         #ifdef S4WINDOWS
            lstrcpy( iname->index_name, iname_buf );
         #else
            strcpy( iname->index_name, iname_buf );
         #endif
         l4add( &indexes, iname );
      }

      /* get the tag name */
      if( retrieve4string( seq, tname_buf, 256 ) < 0 )
      {
         error_code = 1;
         goto CLEANUP;
      }

      /* retrieve the source for the master expression */
      if( retrieve4string( seq, master_expr_buf, 1024 ) < 0 )
      {
         error_code = 1;
         goto CLEANUP;
      }

      /* check for changed names */
      cptr = master_expr_buf;
      report4nchange( c4, &cptr, 0, 1024 );

      /* the code indicates where the new relate should fall in the tree with
         respect to the last relate.  The code used is the same as the return
         values from relate4next() */
      ret4short( seq, &code );

      if( !err_flag && !err_code )
      {
         repeat_flag = 1;
         while( repeat_flag )
         {
            repeat_flag = 0;
            /* see if data file is already open */
            data = 0;
            u4name_piece( str_buf, 256, dname_buf, 0, 0 );
            data = d4data( c4, alias_buf );
            /* if not already open try to open */
            if( !data )
            {
               /* if not allowed to open, error */
               if( !open_files )
               {
                  e4describe( c4, e4result, E4_RESULT_LCF, dname_buf, (char *)0 );
                  err_flag = 1;
               }

               /* check for an alternate path */
               if( spath && spath[0] != '\0' )
               {
                  u4name_piece( str_buf, 256, dname_buf, 0, 1 );
                  strcpy( tempname_buf, spath );
                  strcat( tempname_buf, "\\" );
                  strcat( tempname_buf, str_buf );
                  /* open the data file */
                  data = r4open_data( tempname_buf, alias_buf, master, c4 );
               }
               else
               {
                  /* open the data file */
                  data = r4open_data( dname_buf, alias_buf, master, c4 );
               }

               /* if the open failed */
               if( !data )
               {
                  /* if this is for the CR executable interactively get an
                     alternate file name */
                  #ifdef S4CR2
                  nchange = (PN4CHANGE)u4alloc_free( c4, sizeof(N4CHANGE) );
                  if( nchange )
                  {
                     u4name_piece( str_buf, 256, dname_buf, 0, 0 );
                     nchange->old_name = (char *)u4alloc_free( c4, strlen(str_buf)+1 );
                     if( nchange->old_name )
                     {
                        strcpy( nchange->old_name, str_buf );
                        rc = AlternateDataFile( dname_buf, 256 );
                        if( rc == 0 )
                        {
                           u4name_piece( str_buf, 256, dname_buf, 0, 0 );
                           nchange->new_name = (char *)u4alloc_free(c4,strlen(str_buf)+1 );
                           if( nchange->new_name )
                           {
                              strcpy( nchange->new_name, str_buf );
                              l4add( &name_list, nchange );
                              repeat_flag  = 1;
                           }
                           else
                           {
                              u4free( nchange->old_name );
                              u4free( nchange );
                              err_flag = 1;
                           }
                        }
                        else
                           err_flag = 1;
                     }
                     else
                     {
                        u4free( nchange );
                        err_flag = 1;
                     }
                  }
                  else
                     err_flag = 1;
                  #else
                  /* if not in the executable report an error */
                  if( spath && spath[0] != '\0' )
                     e4describe( c4, e4report, E4_REP_DFILE, tempname_buf, (char *)0 );
                  else
                     e4describe( c4, e4report, E4_REP_DFILE, dname_buf, (char *)0 );
                  err_flag = 1;
                  #endif
               }
               e4set( c4, 0 );
            }
         }
      }

      if( !err_flag && !err_code )
      {
         /* loop through index file names */
         iname = (INAME4 *)l4first( &indexes );
         while( iname )
         {
            repeat_flag = 1;
            while( repeat_flag )
            {
               repeat_flag = 0;
               index = NULL;
               iindex = 0;

               u4name_piece( str_buf, 256, iname->index_name, 0, 0 );
               tname_err = c4->tag_name_error;
               c4->tag_name_error = 0;

               /* check to see if the index is already open */
               iindex = r4index_lookup( data, str_buf );
               c4->tag_name_error = tname_err;
               e4set( c4, 0 );

               /* if not already open */
               if( !iindex )
               {
                  /* if not allowed to open files report an error */
                  if( !open_files )
                  {
                     e4describe( c4, e4result, E4_RESULT_LCF, iname_buf, (char *)0 );
                     err_flag = 1;
                  }

                  /* by default this compile switch is defined in r4report.h
                     it forces the index file name to have the default extension
                     for the compile flag, regardless of the extension saved in
                     the report.  IF the user is using a different extension
                     he should undefine this switch */
                  #ifdef S4DEFAULT_INDEX
                  lExt = u4switch();
                  if( lExt & 1 )
                     u4name_ext( iname->index_name, strlen(iname->index_name)+1, "cdx", 1 );
                  else
                     if( lExt & 2 )
                        u4name_ext( iname->index_name, strlen(iname->index_name)+1, "ntx", 1 );
                     else
                        if( lExt & 4 )
                           u4name_ext( iname->index_name, strlen(iname->index_name)+1, "mdx", 1 );
                        else
                           if( lExt & 8 )
                              u4name_ext( iname->index_name, strlen(iname->index_name)+1, "ndx", 1 );
                  #endif

                  /* attempt to open the index file */
                  if( spath && spath[0] != '\0' )
                  {
                     u4name_piece( str_buf, 256, iname->index_name, 0, 1 );
                     strcpy( tempname_buf, spath );
                     strcat( tempname_buf, "\\" );
                     strcat( tempname_buf, str_buf );
                     index = i4open( data, tempname_buf );
                  }
                  else
                     index = i4open( data, iname->index_name );

                  /* if open fails */
                  if( !index )
                  {
                     /* if in CR executable prompt for alternate */
                     #ifdef S4CR2
                     lstrcpy( str_buf, iname->index_name );
                     rc = AlternateIndexFile( str_buf, 256 );
                     if( rc == 0 )
                     {
                        repeat_flag = 1;
                        u4free( iname->index_name );
                        iname->index_name = NULL;
                        iname->index_name = (char *)u4alloc( lstrlen(str_buf)+1 );
                        if( iname->index_name )
                           lstrcpy( iname->index_name, str_buf );
                     }
                     else
                        err_flag = 1;
                     #else
                     /* report an error */
                     if( spath && spath[0] != '\0' )
                        e4describe( c4, e4report, E4_REP_IFILE, tempname_buf, (char *)0 );
                     else
                        e4describe( c4, e4report, E4_REP_IFILE, iname_buf, (char *)0 );
                     err_flag = 1;
                     #endif
                  }
                  e4set( c4, 0 );
               }
            }
            iname = (INAME4 *)l4next( &indexes, iname );
         }
      }

      if( !err_flag && !err_code )
      {
         tag = NULL;

         /* if this is the first relate do a relate4init() */
         if( !relate )
         {
            master = relate = relate4init( data );
            if( !relate )
               goto CLEANUP;

            /* set the internal flags */
            relate->match_len = match_len;
            relate->sort_type = sort_type;
            relate->relation_type = relation_type;
            relate->error_action = error_action;

            /* if a tag was specified set it */
            if( strlen(tname_buf) )
            {
               repeat_flag = 1;
               while( repeat_flag )
               {
                  repeat_flag = 0;
                  tag = NULL;
                  tag = d4tag( data, tname_buf );
                  if( tag )
                  {
                     relate->data_tag = tag;
                     d4tag_select( data, tag );
                  }
                  #ifdef S4CR2
                  else
                  {
                     /* if the specified tag is not available and this is the
                        .exe prompt for an alternate */
                     e4set( c4, 0 );
                     rc = AlternateTagName( tname_buf, 256 );
                     if( rc == 0 )
                        repeat_flag = 1;
                  }
                  #else
                  else
                     e4set( c4, 0 );
                  #endif
               }
            }
         }
         else
         {
            /* if not the first relate do a create slave */
            /* start by getting a tag pointer from the tag name */
            if( strlen(tname_buf) )
            {
               repeat_flag = 1;
               while( repeat_flag )
               {
                  repeat_flag = 0;
                  tag = NULL;
                  tag = d4tag( data, tname_buf );
                  if( !tag )
                  #ifdef S4CR2
                  {
                     e4set( c4, 0 );
                     rc = AlternateTagName( tname_buf, 256 );
                     if( rc == 0 )
                        repeat_flag = 1;
                     else
                     {
                        goto CLEANUP;
                     }
                  }
                  #else
                  {
                     e4describe( c4, e4report, E4_REP_NOTAG, (char *)tname_buf, 0 );
                     e4set( c4, 0 );
                     goto CLEANUP;
                  }
                  #endif
               }
            }

            /* try to create the slave */
            repeat_flag = 1;
            while( repeat_flag )
            {
               repeat_flag = 0;
               expr = expr4parse( master->data, master_expr_buf );
               if( expr )
               {
                  expr4free( expr );
                  relate = relate4create_slave( master, data, master_expr_buf, tag );
               }
               #ifdef S4CR2
               else
               {
                  /* if create slave fales prompt for a different master expr */
                  e4set( c4, 0 );
                  rc = AlternateMasterExpression( master_expr_buf, master, 1024 );
                  if( rc == 0 )
                     repeat_flag = 1;
                  else
                     relate = NULL;
               }
               #else
               else
               {
                  /* report an error */
                  e4describe( c4, e4report, E4_REP_NOMEXPR, 0, 0 );
                  e4set( c4, 0 );
                  relate = NULL;
               }
               #endif
            }

            if( !relate )
               goto CLEANUP;
            relate->match_len = match_len;
            relate->sort_type = sort_type;
            relate->relation_type = relation_type;
            relate->error_action = error_action;
         }
      }

      /* free the index name list */
      iname = (INAME4 *)l4first( &indexes );
      while( iname )
      {
         nname = (INAME4 *)l4next( &indexes, iname );
         l4remove( &indexes, iname );
         u4free( iname->index_name );
         u4free( iname );
         iname = nname;
      }

      /* if end of relates leave the loop */
      if( code == 2 )
         break;

      if( err_flag == 0 && err_code > 0 )
      {
         err_code += code;
         if( err_code <= 0 )
         {
            err_code = 0;
            code = 1;
         }
      }

      if( err_flag == 1 )
      {
         err_flag = 0;
         err_code = 1;
      }

      if( err_code <= 0 )
      {
         master = relate;
         while( code++ <= 0 )
            master = master->master;
      }
   }

   /* deal with the sort and query expressions */
   memset( master_expr_buf, 0, 1024 );
   memset( slave_expr_buf, 0, 1024 );
   retrieve4string( seq, master_expr_buf, 1024 );
   cptr = master_expr_buf;
   report4nchange( c4, &cptr, 0, 1024 );
   retrieve4string( seq, slave_expr_buf, 1024 );
   cptr = slave_expr_buf;
   report4nchange( c4, &cptr, 0, 1024 );
   if( master )
   {
      repeat_flag = 1;
      if( strlen( slave_expr_buf) > 0 )
      while( repeat_flag )
      {
         repeat_flag = 0;
         expr = expr4parse( master->data, slave_expr_buf );
         if( expr )
         {
            expr4free( expr );
            relate4sort_set( master, slave_expr_buf );
         }
         #ifdef S4CR2
         else
         {
            e4set( c4, 0 );
            rc = AlternateSortExpression( slave_expr_buf, master, 1024 );
            if( rc == 0 )
               repeat_flag = 1;
         }
         #else
         else
         {
            e4describe( c4, e4report, E4_REP_NOSORT, 0, 0 );
            e4set( c4, 0 );
         }
         #endif
      }

      repeat_flag = 1;
      if( strlen( master_expr_buf ) > 0 )
      while( repeat_flag )
      {
         repeat_flag = 0;
         expr = expr4parse( master->data, master_expr_buf );
         if( expr )
         {
            expr4free( expr );
            relate4query_set( master, master_expr_buf );
         }
         #ifdef S4CR2
         else
         {
            e4set( c4, 0 );
            rc = AlternateQueryExpression( master_expr_buf, master, 1024 );
            if( rc == 0 )
               repeat_flag = 1;
         }
         #else
         else
         {
            e4describe( c4, e4report, E4_REP_NOQUERY, 0, 0 );
            e4set( c4, 0 );
         }
         #endif
      }
   }

   if( relate )
      return( &relate->relation->relate );
   else
      return NULL;

CLEANUP:
   iname = (INAME4 *)l4first( &indexes );
   while( iname )
   {
      nname = (INAME4 *)l4next( &indexes, iname );
      l4remove( &indexes, iname );
      u4free( iname->index_name );
      u4free( iname );
      iname = nname;
   }

   if( master )
   {
      relate4free( master, open_files );
   }
   else
   {
      if( relate )
         relate4free( relate, open_files );
   }

   if( error_code )
   {
      e4describe( c4, e4report, E4_REP_RELERR, 0, 0 );
   }
   return NULL;
}

/* wrapper for above function */
RELATE4 * S4FUNCTION   relate4retrieve_relate( FILE4SEQ_READ *seq, int open_files, char *spath )
{
   char *dname_buf = NULL, *iname_buf = NULL, *tname_buf = NULL, *str_buf = NULL;
   char *master_expr_buf = NULL, *slave_expr_buf = NULL, *tempname_buf = NULL;
   RELATE4 *retvalue = NULL;

   dname_buf = (char *)u4alloc_free( seq->file->code_base, 256 );
   iname_buf = (char *)u4alloc_free( seq->file->code_base, 256 );
   tname_buf = (char *)u4alloc_free( seq->file->code_base, 256 );
   str_buf = (char *)u4alloc_free( seq->file->code_base, 256 );
   master_expr_buf = (char *)u4alloc_free( seq->file->code_base, 1024 );
   slave_expr_buf = (char *)u4alloc_free( seq->file->code_base, 1024 );
   tempname_buf = (char *)u4alloc_free( seq->file->code_base, 512 );
   if( !dname_buf || !iname_buf || !tname_buf || !str_buf ||
       !master_expr_buf || !slave_expr_buf || !tempname_buf )
      goto R4LEAVE;

   retvalue = relate4retrieve_relate_foo( seq, open_files, spath,
                 dname_buf, iname_buf, tname_buf, str_buf, master_expr_buf,
                 slave_expr_buf, tempname_buf );

R4LEAVE:
   if( dname_buf )
      u4free( dname_buf );
   if( iname_buf )
      u4free( iname_buf );
   if( tname_buf )
      u4free( tname_buf );
   if( str_buf )
      u4free( str_buf );
   if( master_expr_buf )
      u4free( master_expr_buf );
   if( slave_expr_buf )
      u4free( slave_expr_buf );
   if( tempname_buf )
      u4free( tempname_buf );

   return retvalue;

}

/* see the CodeReporter manual */
int S4FUNCTION obj4dataFieldSet( POBJ4 obj, char *fname, char ftype, int flength, int fdec )
{
   POUT4OBJ oobj;
   PREPORT4 report;

   if( !obj )
      return -1;

   report = obj->area->report;
   obj->field_type = ftype;
   obj->field_len = flength;
   obj->field_dec = fdec;
   if( fname )
      u4ncpy( obj->field_name, fname, sizeof(obj->field_name) );
   else

   oobj = (POUT4OBJ)l4first( &report->output_objs );
   while( oobj )
   {
      if( oobj->obj == obj )
      {
         if( fname == NULL || ftype == 0 || flength == 0 )
            l4remove( &report->output_objs, oobj );
         return 0;
      }
      oobj = (POUT4OBJ)l4next( &report->output_objs, oobj );
   }

   oobj = (POUT4OBJ)u4alloc_free( obj->area->report->code_base, sizeof(OUT4OBJ) );
   if( !oobj )
      return -1;
   oobj->obj = obj;
   l4add( &report->output_objs, oobj );
   return 0;
}

/* see the CodeReporter manual */
int S4FUNCTION report4dataFileSet( PREPORT4 report, char *fname )
{
   if( !report )
      return -1;

   if( report->dfile_name )
      u4free( report->dfile_name );
   report->dfile_name = NULL;

   if( fname == NULL )
      return 0;

   report->dfile_name = (char *)u4alloc_er( report->code_base, strlen(fname)+1 );
   if( !report->dfile_name )
      return -1;

   u4ncpy( report->dfile_name, fname, strlen(fname)+1 );
   return 0;
}

/* see the CodeReporter manual */
int S4FUNCTION report4dataGroup( PREPORT4 report, PGROUP4 group )
{
   if( !report )
      return -1;

   report->output_group = group;
   return 0;
}