/*
outer_brev.c
	version 1.0 came from outer.c  version 2.5, JGOFS 1.5 release
	(as of 10 Jan 98).
    Incompatibilities
	1) outer_brev variable names may not include spaces or percent signs.
	2) Non-numeric data values (including "nd") cannot be selected with
	   numeric operators
	3) When clicking on a datum to use it as a selection for the
	   next level, comparisons are done as strings instead of numbers
	4) Rely on program exit to free dynamically allocated memory
*/
#define OUTER_VERSION "outer_brev version 1.3a 16 Dec 1998"
/*
16 Dec 98	v 1.3a	WJS
    Bug fix: logical vars need to be signed.  Try to learn from v 1.2a!!
    Bug fix: compile-time OTHEROPTS was not being used as default for
      runtime PLOTLINK and vice versa.
	[Released for beta test on synthesis 16 Dec 98]
	[Needs outer.h 1.0a]
	[Needs outer_utils.c 1.0]
	[Needs path_info_routines 1.2 or later (.c & .h)]
	[Begin 1.3a]
10 Dec 98	v 1.3	WJS
    Next-level links use string equality to determine what
      data at next level is displayed (unless GENERATE_ALPHA_SELECTIONS is
      turned off).
    Mods to "top" links
      1) Add per-link display control
      2) If at level 0, display "nolevel0" gif instead of "Level 0" link.
      3) If at last level, display "nolevelN" gif instead of "Level N" link.
      4) Use "Level N" for alternate text for next level button.
      5) If TEST_GIFS defined, test for button gif existence before
	 generating link to gif.  If gif does not exist, display its 
	 "alt" text instead
    Mods to selection/projection code
      1) String operations eq,ne,lt,le,gt,ge,contains,is_contained_in,
	 begins_with,ends_with.  (These ops typically delimited w/blanks -
	 see trigram mod, below, for way to get blanks into query strings)
      2) Parametrize stack size and increase.  Still do not test for overflow
      3) Do some restructuring & commenting.  Remove need for a couple of
	 compile-time constants
    Accept %hh trigram in selection strings and convert it to the (hopefully)
      ASCII character corresponding to the hex value of hh.  To get a
      real percent sign in the query string, use %25.  A leading 0 must
      be present, if appropriate; eg, use %09 for tab, not %9.
    Use iovaldouble_ function instead of iovalreal_ if USE_DOUBLE is
      defined as TRUE
    Dynamically allocate more buffers.  Can remove PATHSIZE from .h file
    Calculate some buffer sizes.  Can remove OUTVARBUFSIZE & OUTBUFSIZE
      from .h file
    More flexibility in BUTTONIMAGESDIR spec.  Accept it with or without
      trailing /.  If no protocol (ie, :// string), prepend file://localhost
      (with another / if necessary)
    Bug fix: was adding port even when MYADDR had port in it.
    Bug fix: remove blank btwn "Documentation" & "Plotting and Other Op"
    Bug fix: v 1.1 handling of query string w/blanks NG since htmlescape
      doesn't "do" blanks.  Improper use of htmlescape anyway - it's
      designed to allow printing of text w/html special chars such that html
      formatting doesn't happen.
    Put htmlescape & some new routines in outer_utils.c
    Put lots of #defines, etc, in outer.h
	[Released for beta test on synthesis]
	[Needs outer.h 1.0]
	[Needs outer_utils.c 1.0]
	[Needs path_info_routines 1.2 or later (.c & .h)]
	[Begin 1.3]
15 May 98	v 1.2a	WJS
    Remove indented macros - cause trouble w/some compilers
    Type parse and error_
    Bug fix: mismatch between format & list of variables in ("&r\n",i)
      Remove the ,i which wasn't getting printed (or used by jdb) anyway
    Bug fix: need signed char for char vars that can go negative.
	[Distributed w/various defgb pkgs]
	[Begin 1.2a]
12 May 98	v 1.2	WJS
    OO_OUTER switch
    Bug fix: need to propagate .brev to documentation button
	[Not sure if 1.2 "got out".  Wasn't intended to...]
	[Unfortunately, it appears it made it to dataone someplace.  Sorry
	 Chris...]
	[Begin 1.2]
 4 Mar 98	v 1.1a	WJS
    Change a couple of variable names & improve a couple of comments
13 Feb 98	v 1.1	WJS
    htmlescape query strings in level n+1 links in case they contain blanks.
    Correct path_info version requirement (always needed 1.2)
	[Distributed w/various defgb pkgs]
	[Needs path_info_routines 1.2 or later (.c & .h)]
	[Begin 1.1]
10 Feb 98	v 1.0	WJS
    Support for "short listing" format.  See outer_brev.doc.
    Propagate SERVER_PORT if not overridden by (new) switch MYPORT
	(and if not port 80)
    Accept all compilation switches (NOTOPLINKS, etc) from environment
        if present there.  If not, use compilation switch value.
      Accept TRUE, FALSE, 1, 0 as values for compilation switches.
      Accept "all reasonable" true/false strings as values for env variables
      Accept affirmative compilation switch names, too; eg, TOPLINKS=FALSE
        is the same as NOTOPLINKS=TRUE (which has always been the same as
	NOTOPLINKS)
      Accept *SERVER with or without leading //s
    Restructure outvarhtml
      Use path_info_routines
      Fix small problem with putting | after text-only links that turn out
	not to be the last in line.
    Get some memory dynamically
      Removes need for some default.h defns (at least PARSAVSIZE)
      Reduce likelihood of SEGV on htmlescape'd strings
    If MYADDR not supplied, use SERVER_NAME
    In QUOTENOLINK environment, don't truncate last char if not a "
    Give explicit types to out* functions
    Add a VMS compilation switch for convenience of WJS
    Comments: invert time order & reformat outer 2.5 comments
	[Needs path_info_routines 1.2 or later (.c & .h)]
	[Begin 1.0]

outer.c  version 2.5
   Jun 97		grf
	lots of fixes for v 1.5
   May 96		grf
	added BUTTON flag for new links if you wish them to be displayed as
	    buttons rather than links
   ??? ??		grf
	added error routine callable from outer or inner - should
            produce sensible output in all modes
26 Feb 96	v 2.1	clh
	    allow more links at top
        Directory Page | Documentation | Plotting and Other Operations
        List at Level 0 | List all at next level | Flat listing
            NOOTHEROPTS compiler option will remove Plotting link
            NOTOPLINKS compiler option will remove all but first link
14 Feb 96	v 2.0	clh
	resolved differences among several outer.c versions
           - grf's version using getenv("OPTIONSERVER")
           - wjs's version implementing a buffer size include file
27 Jan 96		wjs
	1) Added inclusion of buffer size logic via include file outer_bufsize.h
	2) Re-parametrized some sizes
	3) Removed tmp buffer from main since it didn't seem used
23 Jan 96		clh
	added 'void' typing to the functions that this outer method
	    expects from its inners; to avoid warnings from ANSI compilers
13 Sep 95		clh
	added option to NOT print the OTHER OPTIONS links, using a 
           precompiler option 'NOOTHEROPTS'
	added option to NOT generate links from quoted strings
           code taken from outerqn.c, using precompiler option 'QUOTENOLINK'
10 Jul 95		clh
	Include field widths
*/

  /* 
   ===========================================
   Subroutines required:
   ===========================================
  */
int ioopen_();
  /*
   int ioopen_(s,nparams,ntotal)
   char s[][];
   int *nparams,*ntotal;
     s[0..nparams-1]: parameter strings. Inner sets s[j][0]=0 for any 
     strings which it processes; others will be processed by outer. Thus
     selection/projections would normally be ignored by inner.
     nparams: number of parameter strings
     ntotal (returned): total number of variable names
  */
int ioreadrec_();
  /*
   int ioreadrec_(level)
   int *level;
     Read record at appropriate level. Return 0 if end at that level. Return
     1 if ok.
  */
void ioclose_();
  /*
   ioclose_()
     Close files
     Note: this routine should NOT call error_
  */
void iovalreal_();
  /*
   iovalreal_(vn,f)
   int *vn;
   float *f;
     Return real value (f) for variable indexed by vn. -9999 for strings
  */
void iovalstr_();
  /*
   iovalstr_(vn,tmp)
   int *vn;
   char *tmp;
     Return string value (tmp) for variable indexed by vn.
  */
int iovarlevel_();
  /*
   int iovarlevel_(vn)
   int *vn;
     Return level corresponding to variable indexed by vn.   
  */
int ioattrout_();
  /*
   ioattrout_(vn, str)
   int *vn;
   char *str;
     Output next attribute for variable indexed by vn. 0=none left.
  */
void ioname_();
  /*
   ioname_(vn,s)
   int *vn;
   char *s;
     Return name (s) corresponding to variable number vn.
  */
int iowidth_();
  /* 
   int iowidth_(vn);
   int *vn;
     Return length of variable field indexed by vn.
  */

int iocommout_();
  /*
   int iocommout_(s)
   char *s;
     Return next comment string. 0=none left. 
                                -1=next comment contains a URL
  */

  /*  All #includes in outer.h						*/
#include "outer.h"

typedef signed char Logical;	/*  NB: occasionally gets -1,		*/
				/*   hence need signed			*/

  /*  jgofs.a functions...						*/
  /*    HTDoConnect in turn requires some functions, presently found	*/
  /*    jgofs.a.  Some are HT*.c modules; a couple are in jdb.c		*/
  /*  These functions needed only for testing for gifs, which is turned	*/
  /*  off by default.  However, can't eliminate these defns based on	*/
  /*  compile-time TEST_GIFS, since user can spec tests at run time	*/
  /*  (only useful time, if functionality useful at all).		*/
  /*  If requiring these functions causes trouble (eg, no jgofs.a),	*/
  /*  should unconditionally set test_gifs to false.  Suggest testing	*/
  /*  it first, and aborting if true (on grounds that somebody appears	*/
  /*  to want gifs tested, and we're not about to do it)		*/
int HTDoConnect();
int HTDoRead();

  /*  outer_utils.c functions...					*/
char *htmlescape();
char *trigram();
char *un_trigram();

#if USE_DOUBLE
#define ioval iovaldouble_
typedef double Comp_precision;
#else
#define ioval iovalreal_
typedef float Comp_precision;
#endif

double strtod();
void ioval();

int ntotal;
int minlevel;
int maxlevel;
int reqlevel;
int maxobjectlevel;
int cl;
int brevflag=0;
int htmlflag=0;
int flatflag=0;
int *level=NULL;
int brevstart;
int brevcount;
int brevend;

  /*  PATH_INFO stuff.  Functions are in path_info_routines		*/
char *flat_PATH_INFO,*nextlevel_PATH_INFO,*level0_PATH_INFO;
char *no_extURL_PATH_INFO,*full_list_PATH_INFO;
char *propagate_brev_no_extURL_PATH_INFO;
char *make_PATH_INFO_putenv_string(),*get_jgofs_env_datum();
int get_level();

static char *outnames=NULL;
char *parsav;
char *objsav;

char *getenv();

Logical dirlink,doclink,plotlink,lev0link,levnlink,flatlink;
Logical quotenolink,toplinks,otheropts,oo_outer;
Logical test_gifs;
char *buttonimagesdir;
Logical trailing_buttonimagesdir_slash;
  /*  Format of "server" is node (optionally w/port), followed by cgi	*/
  /*  program name.  Program name by itself can be used if we know	*/
  /*  that node is local.  A node spec does not include a leading //	*/
  /*  (but we'll strip them off found)					*/
char *dirserver_w_dir;
char *optionserver;
char *infoserver_w_obj,*infoserver_extURL;
char *dataserver,*localnodeport;

char *equality_test;	/* Links generate string or numeric test?	*/

/************************************************************************/

void error_(s1,s2)
char *s1,*s2;
{
if(htmlflag){
  printf("<pre><h2>%s: %s</h2></pre>\n",s1,s2);
} else if(flatflag) {
  printf("%s %s\n",s1,s2);
} else {
  printf("&x %s: %s\n",s1,s2);
};
  /*  More dynamically allocated memory could be explicitly freed here,	*/
  /*  and probably should be so freed, but I'm lazy			*/
if(level)free(level);
if(outnames)free(outnames);
ioclose_();
exit(0);
}
  
void out_err(s,t)
char *s,*t;
{
  char *ptr1,*ptr2;
  time_t timbuf;

  ptr1 = (char *) malloc (strlen(s) + 2 + strlen(t) + 1);
  if (ptr1 == NULL) error_(s,t);

  ptr2 = (char *) malloc (20 + 26 + 17 + strlen(OUTER_VERSION) + 1 + 1);
  if (ptr2 == NULL) error_(s,t);

  strcpy (ptr1,s);
  strcat (ptr1,": ");
  strcat (ptr1,t);

  strcpy (ptr2,"\n   This msg issued ");
  time(&timbuf);
  strcat (ptr2,ctime(&timbuf));		/*  ctime includes \n		*/
  strcat (ptr2,"   This msg from ");
  strcat (ptr2,OUTER_VERSION);
  strcat (ptr2,"\n");

  error_(ptr1,ptr2);
  return;
}

void out_errn(s,n,fmt)
char *s,*fmt;
int n;
{
  char intbuf[25];

  sprintf(intbuf,fmt,n);

  out_err(s,intbuf);
  return;
}

void malloc_err(s,n)
char *s;
int n;
{
    /*  Don't want to malloc an error buffer if we're having trouble	*/
    /*  malloc'ing in the first place					*/
    /*    Tried putting malloc in here, too, returning ptr when OK, but	*/
    /*    ran into BUSERR alignment problems.  Don't know why...	*/
#define MAX_S 50
  char idbuf[25 + MAX_S + 15 + 1] = "Could not get memory for ";
  int len;

  if (  (len = strlen(s)) > MAX_S  ) len = MAX_S;
  strncat(idbuf,s,len);
  strcat(idbuf,". nbytes (hex) ");
  out_errn(idbuf,n,"%x");

  return;
}

/*
P-code form:
128..255 exec test(n-128) [negative #'s on Sun]
			  [negativity not used in this code-fortunately.  WJS]
1=or,2=and,3=not
0: end
*/

#define NTESTS 100	/* Number of selections/projections		*/
#define OPSTACKSIZE 20	/* Depth of operation stack; eg (a+b)*(c+d)	*/
			/*  needs a depth of 3				*/
  /*  Within each of next 4 arrays, "equivalent" strings (eg, != & <>)	*/
  /*  are grouped together.  "Group size" is constant (eg, see empty	*/
  /*  strings in testop where there are no equivalent strings).  Groups	*/
  /*  are ordered to drive "switch" statements in code, so order alter-	*/
  /*  ation means code alteration.  Infix array is ordered by operation	*/
  /*  priority as well.  testop is parallel to teststrop as far as it	*/
  /*  goes.  testop defines numeric comparisons; teststrop string	*/
  /*  comparisons.							*/
  /*  Each array ends with NULL						*/
  /*    Recoded not to use prefix array, since "not" prefix (and any	*/
  /*    future one like it) must be tested for in variable section,	*/
  /*    while ! is tested for in "normal" special character section	*/
static char *prefix[]={"!","not",NULL};
static char *infix[]={"||","or","|","&&","and","&",NULL};
static char *testop[]={"<","","=","==",">","","<=","","<>","!=",">=","",NULL};
static char *teststrop[]={"lt","eq","gt","le","ne","ge","contains",
			  "is_contained_in","begins_with","ends_with",NULL};
#define SIZE_PREFIX_GROUP 2
#define SIZE_INFIX_GROUP 3
#define SIZE_TESTOP_GROUP 2
static struct {
  int testlev;
  int testvar;
  int testcode;
  Comp_precision testval;
  char *teststr;
  int lenteststr;
  int testres;
  } tst[NTESTS];
static int tstcnt=0;
static int tstproc[NTESTS];
static int tstproccnt=0;

void parse(s)
char *s;
  /*		Description is approximate.  Seems to me it can do	*/
  /*		weird things with not...				*/
  /*  Parser has 4 states: variable or open paren			*/
  /*			   comparison operator				*/
  /*			   thing being compared				*/
  /*			   logical operator or close paren or end	*/
  /*  Idea is to get next token, determine its type, the process it	*/
  /*  depending on the state the parser is in (possibly changing state)	*/
{
int opstkp;
Comp_precision f;
int opstack[OPSTACKSIZE];
int opprior[OPSTACKSIZE];
char *t,*t1,tok[TOKEN+1],tmp[TOKEN+1];
int i,state,typ,finaland,type_string_op,paren_depth;

paren_depth = 0;
opstkp = 0;
opstack[0] = 0;
opprior[0] = -1;
state = 0;

finaland = (tstproccnt != 0);

strcat(s,"$");
t = s;
while (t != NULL) {

    /*  Scheme: If first char not one of the special ones, save the	*/
    /*  token.  Resulting alpha token can be "not" operator or one of	*/
    /*  the string operators.						*/
    /*  In original outer code, a token beginning with something like @	*/
    /*  would show up as "bad prefix operator".  In this code, I think	*/
    /*  it will show up as "error on conditions"			*/
    /*  The original outer code for this section is commented at the	*/
    /*  end of this file						*/
    /*  Skip leading blanks.  Whole string cannot be blank because of	*/
    /*  appended $, above						*/
  t += strspn(t," ");
  switch (*t) {
    case '(':
      paren_depth++;
      typ = 2;
      t++;
      break;
    case ')':
      if ((--paren_depth) < 0) out_err("Illegally nested parens",s);
      typ = 3;
      t++;
      break;
    case '$':
      typ = 4;
      t = NULL;
      break;
    case '=':  case '<':  case '>':  case '|':  case '&':  case '!':
      tok[0] = *t;
	/*  Check for 2nd char of 2-char ops like !=.  Validity		*/
	/*  of particular 2-char combo checked later			*/
      switch (*(t+1)) {
	case '&':  case '|':  case '=':  case '>':	
	  tok[1] = *(++t);
	  tok[2] = '\0';
	  break;
	case '$':
	  out_err("No argument after last logical operator",s);
	  break;
	default:
	  tok[1] = '\0';
	  break;
      }
      typ = 5;
      t++;
      break;
    default:	/*  alpha token.  Could still be string operator...	*/
      if ( (i = strcspn(t," ()$=<>|&!")) > TOKEN )
	out_err("Token too long",tok);
      strncpy(tok,t,i);
      tok[i] = '\0';
      t += i;

      type_string_op = -1;
      while (teststrop[++type_string_op] != NULL)
	if (strcmp(teststrop[type_string_op],tok) == 0) break;
      if (teststrop[type_string_op] == NULL) {
	  /*  Token is not string operator.  See if it's numeric	*/
        f=strtod(tok,&t1);
        typ = (*t1 == '\0') ? 1 : 0;
      } else {
	type_string_op++;	/* Begins-w-0 vs begins-w-1 mod		*/
        typ = 6;
      }

      break;
  }

  switch (state*10+typ) {
    case 0: /* variable or the string "not" */
    case 6: /* string operator could be a variable name, too		*/
      for (i=0; i<ntotal; i++) {
	ioname_(&i,tmp);
	if (strcmp(tok,tmp) == 0) break;
      }
      if (i == ntotal) {
        if (strcmp(tok,"not") != 0) out_err("Error on conditions",tok);
        opstack[++opstkp]=3;
        opprior[opstkp]=10;
        break;
      }
      tst[tstcnt].testvar=i;
      tst[tstcnt].testlev=iovarlevel_(&i);
      state=1;
      tstproc[tstproccnt++] = tstcnt+128;
      break;
    case 2: /* ( */
      opstack[++opstkp]=4;
      opprior[opstkp]=1;
      break;
    case 5: /* ! */
      if (tok[0] != '!') out_err("Bad prefix operator",tok);
      opstack[++opstkp]=3;
      opprior[opstkp]=10;
      break;
    case 15: /* ==, etc. */
      i = -1;
      while (testop[++i] != NULL)
	if (strcmp(tok,testop[i]) ==0 ) break;
      if (testop[i] == NULL) out_err("Bad condition",tok);
      tst[tstcnt].testcode = (i/SIZE_TESTOP_GROUP)+1;
      state=2;
      break;
    case 16: /* eq, ls, etc  */
      tst[tstcnt].testcode = - type_string_op;
      state=2;
      break;
    case 20: 		/* token being compared to is not numeric 	*/
    case 26:
      if (tst[tstcnt].testcode > 0)	/*  Requested test is numeric	*/
	tst[tstcnt].testcode = - tst[tstcnt].testcode; /* Convert it	*/
	  /*  Deliberate fall-through here				*/
    case 21: 		/* token being compared to IS numeric 		*/
      if (tst[tstcnt].testcode < 0) {	/*  Requested test is string	*/
	tst[tstcnt].lenteststr = strlen(tok);
	i = tst[tstcnt].lenteststr + 1;
	if (  (tst[tstcnt].teststr = (char *) malloc(i)) == NULL  )
							malloc_err("teststr",i);
	strcpy(tst[tstcnt].teststr,tok);
	f = 0.;		/*  Convenient for trick used later in test	*/
      }
      tst[tstcnt++].testval=f;
      state=3;
      break;
    case 30:
    case 35: /* and or */
      i = -1;
      while (infix[++i] != NULL)
	if (strcmp(tok,infix[i]) ==0 ) break;
      if (infix[i] == NULL) out_err("Bad infix operator",tok);
      i = (i/SIZE_INFIX_GROUP)+1;
      while (i+1 <= opprior[opstkp])
	tstproc[tstproccnt++] = opstack[opstkp--];
      opstack[++opstkp] = i;
      opprior[opstkp] = i+1;
      state=0;
      break;
    case 33: /* ) */
      while (1 < opprior[opstkp])
        tstproc[tstproccnt++] = opstack[opstkp--];
      if ((--opstkp) < 0) out_err("Op stack underflow","");
      break;
    case 34: /* $ */
      while (0 <= opprior[opstkp])
        tstproc[tstproccnt++] = opstack[opstkp--];
      if (finaland) tstproc[tstproccnt++] = 2;
#ifdef DEBUG
      printf("finaland = %d, tstproccnt = %d\n",finaland,tstproccnt);
      for(i=0;i<tstproccnt;i++){
	printf("tstproc %d ",tstproc[i]);
	if(tstproc[i]>=128)
	  printf("%d %d %f %s\n",tst[tstproc[i]-128].testvar,
		 tst[tstproc[i]-128].testcode,tst[tstproc[i]-128].testval,
		 tst[tstproc[i]-128].teststr);
	else printf("\n");
      };
#endif
      return;
    default:
      out_err("Parsing error",s);
  }
}

if (paren_depth != 0) out_err("Unpaired parentheses",s);

}

int test(cl)
int cl;
{
Comp_precision f;
char tmp[DATUMBUFSIZE];
int i,j,m;
  /*  Size below comes from conclusion that in tstproccnt loop below,	*/
  /*  the stack pointer could be incremented once each interaction,	*/
  /*  even though logically, I think the pointer is supposed to end up 0 */
int stk[NTESTS],stkp;

#ifdef DEBUG
  printf("entered test at level %d\n",cl);
#endif
stkp = -1;
stk[0] = TRUE;
for (i=0; i<tstproccnt; i++) {
  if(tstproc[i] >= 128){
    j = tstproc[i]-128;
    if (tst[j].testlev == cl) {
#ifdef DEBUG
      printf("-- %f %d %f -- ",f,tst[j].testcode,tst[j].testval);
#endif
      if (tst[j].testcode < 0) {
	iovalstr_(&(tst[j].testvar),tmp);
	if (tst[j].testcode >= -6) f = (double) strcmp(tmp,tst[j].teststr);
      } else
	ioval(&(tst[j].testvar),&f);

      switch (abs(tst[j].testcode)){
	case 1:
	  m = (f < tst[j].testval);
	  break;
	case 2:
	  m = (f == tst[j].testval);
	  break;
	case 3:
	  m = (f > tst[j].testval);
	  break;
	case 4:
	  m = (f <= tst[j].testval);
	  break;
	case 5:
	  m = (f != tst[j].testval);
	  break;
	case 6:
	  m = (f >= tst[j].testval);
	  break;
	case 7:					/*  contains		*/
	  m = (strstr(tmp,tst[j].teststr) != NULL);
	  break;
	case 8:					/*  is_contained_in	*/
	  m = (strstr(tst[j].teststr,tmp) != NULL);
	  break;
	case 9:					/*  begins_with		*/
	  m = (strncmp(tmp,tst[j].teststr,tst[j].lenteststr) == 0);
	  break;
	case 10:				/*  ends_with		*/
	  m = strlen(tmp) - tst[j].lenteststr;
	  m = (m >= 0) ? (strcmp(tmp+m,tst[j].teststr) == 0) : FALSE;
	  break;
	default:
	  out_errn("Internal error-illegal testcode",tst[j].testcode,"%d");
      }
      tst[j].testres = m;
      stk[++stkp] = m;    
    }

      /*  Use previously saved test result-no need to recalc it		*/
    else if (tst[j].testlev < cl) stk[++stkp] = tst[j].testres;

      /*  Test is at level we haven't gotten to yet.  Return true even	*/
      /*  if data at next level might not pass test.  Therefore, all	*/
      /*  level N data shows up when displaying level N, but might not	*/
      /*  when displaying same object at level N+1.  Only a problem	*/
      /*  when displaying by level...					*/
    else stk[++stkp] = TRUE;

 
 } else {
    switch (tstproc[i]) {
      case 1:
	stk[stkp-1] = (stk[stkp-1] || stk[stkp]);
	stkp--;
	break;
      case 2:
	stk[stkp-1] = (stk[stkp-1] && stk[stkp]);
	stkp--;
	break;
      case 3:
	stk[stkp] = ( ! stk[stkp]);
	break;
      default:
	out_errn("Internal error-illegal tstproc",tstproc[i],"%d");
    }
  }
}
#ifdef DEBUG
  printf("test result = %d\n",stk[0]);
#endif
return stk[0];
}

void print_comments_html()
{
  char tmp[COMMENTLINEBUF];
  char *ptr;
  int j;
  char comment_fmt[]="# %s\n";

  while( (j=iocommout_(tmp)) != 0 )
    if (j>0) {
      ptr = htmlescape(tmp);
      printf(comment_fmt,ptr);
      free(ptr);
    } else
      printf(comment_fmt,tmp);

  return;
}

void outvar()
/* outvar outputs tabs as this is likely being fed into
   another program or being transferred across a network and
   therefore, we wish to minimize size of transfer to one 
   character separating fields */
{
  int i,j;
  char tmp[OUTVARBUFSIZE],attr[TOTATTRSIZE];
  i=1;
  while(iocommout_(tmp)){
    if(i) { 
      printf("&c\n");
      i=0;
    };
    printf("%s\n",tmp);
  };
  for(i=0;i<=maxlevel;i++){
    printf("&v%d\n",i);
    for(j=0;j<ntotal;j++)
      if(*(level+j)==i){
        strcpy(attr,"[");

        while(ioattrout_(&j,tmp)) {
          strcat(attr,tmp);
          strcat(attr,";");
        };


        if(strcmp(attr,"[")) 
          attr[strlen(attr)-1]=']';
        else
          attr[0]=0;
        strcpy(tmp,outnames+j*VARNAMEBUFSIZE);
        strcat(tmp,attr);
        printf("%s\t",tmp);
     };
     printf("\n");
  };
  printf("&r\n");
}

Logical output(firsttime)
Logical firsttime;
/* output outputs tabs as this is likely being fed into
   another program or being transferred across a network and
   therefore, we wish to minimize size of transfer to one 
   character separating fields */
{
  int i,j;
  Logical print_comment_prefix = TRUE;
  char tmp[OUTBUFSIZE];

  if  (  (minlevel < maxlevel) || firsttime  )  {
    while (iocommout_(tmp)) {
      if (print_comment_prefix) {
	printf("&c\n");
	print_comment_prefix = FALSE;
      }
      printf("%s\n",tmp);
    }
    for (i=minlevel; i<=maxlevel; i++)  {
      printf("&d%d\n",i);
      for (j=0; j<ntotal; j++)
	if (*(level+j) == i)  {
	  iovalstr_(&j,tmp);
	  printf("%s\t",tmp);
        }
      printf("\n");
    }
    minlevel = maxlevel;
  } else {
    for (j=0; j<ntotal; j++)
      if (*(level+j) == maxlevel)  {
	iovalstr_(&j,tmp);
	printf("%s\t",tmp);
      }
    printf("\n");
  }

  return 1;
}

void outvarflat()
{
  int j,len;
  char tmp[OUTVARBUFSIZE],attr[TOTATTRSIZE];

  while (iocommout_(tmp))
    ;
  for (j=0; j<ntotal; j++)
    if (*(level+j) >= 0) {
      strcpy(attr,"[");
      while(ioattrout_(&j,tmp)) {
	strcat(attr,tmp);
	strcat(attr,";");
      }
      if(strcmp(attr,"[")) attr[strlen(attr)-1]=']';
      else attr[0]=0;
      strcpy(tmp,outnames+j*VARNAMEBUFSIZE);
      if (strstr(attr,"width=") == NULL) strcat(tmp,attr);
      len=iowidth_(&j);
      printf("%-*s  ",len,tmp);	/* output var name in enough space + 2 */
    }
  printf("\n");
}

Logical outputflat(firsttime)
Logical firsttime;
{
  int j,len;
  char tmp[OUTBUFSIZE];
  while (iocommout_(tmp))
    ;
  for (j=0; j<ntotal; j++)
    if (*(level+j) >= 0)  {
      iovalstr_(&j,tmp);
      len=iowidth_(&j);
      printf("%-*s  ",len,tmp);
    }
  printf("\n");
  minlevel=maxlevel;
  return 1;
}

void make_PATH_INFOs()
{
/*  First "\0" to make_PATH_INFO_putenv_string says "don't start string	*/
/*	with PATH_INFO=".  First NULL says "Use PATH_INFO as default	*/
/*	for unspec'd fields.  For rest, NULL or USE_EXISTING_LEVEL	*/
/*	means "use what's in PATH_INFO; otherwise, replace what's in	*/
/*	PATH_INFO with what was supplied.				*/
/*  May want to review comments about reqlevel vs maxlevel in main.	*/
/*	Due to issues raised there, a user of the "Next level" button	*/
/*	cannot get beyond a level that is "empty" because of		*/
/*	projections							*/

  full_list_PATH_INFO = make_PATH_INFO_putenv_string
			("\0",NULL,NULL,"html",USE_EXISTING_LEVEL,NULL);

    /*  Not sure why there's no extended URL in PATH_INFO for flat 	*/
    /*  Not sure why level should be present either			*/
  flat_PATH_INFO = make_PATH_INFO_putenv_string
			("\0",NULL,NULL,"flat",USE_EXISTING_LEVEL,"\0");

  nextlevel_PATH_INFO = make_PATH_INFO_putenv_string
			("\0",NULL,NULL,NULL,reqlevel+1,NULL);
  level0_PATH_INFO = make_PATH_INFO_putenv_string
			("\0",NULL,NULL,NULL,0,NULL);

    /*  If/when we want to propagate .brev, change "html" to NULL below */
    /*  and get rid of propagate_brev in favor of no_extURL		*/
  no_extURL_PATH_INFO = make_PATH_INFO_putenv_string
			("\0",NULL,NULL,"html",USE_EXISTING_LEVEL,"\0");
  propagate_brev_no_extURL_PATH_INFO = make_PATH_INFO_putenv_string
			("\0",NULL,NULL,NULL,USE_EXISTING_LEVEL,"\0");

    /*  Make string for "Documentation" button.  Although not a PATH_	*/
    /*  INFO, I assume the {} section is logically an extended URL.	*/
    /*  Therefore, in the name of separating the formatting from the	*/
    /*  content, we build it here.  The malloc & sprintf, below,	*/
    /*  should be replaced when we write a "create_extURL" routine	*/
    /*  since the = and , in "dir=whatever," are also extURL format-	*/
    /*  ting and not content.						*/
  infoserver_extURL = NULL;
  if (*infoserver_w_obj == '\0')
    infoserver_extURL = infoserver_w_obj;
  else {
    infoserver_extURL = (char *)malloc
      					(strlen(dirserver_w_dir)
					  + strlen(dataserver) 
					  + strlen(no_extURL_PATH_INFO) 
					  + strlen("{}")
					  + strlen("dir=,")
					  + strlen("data=")
					+ 1);
    if (infoserver_extURL != NULL)
	/*  Assumes we prefer "dir=" to nothing if dirserver_w_dir empty */
      sprintf(infoserver_extURL,"{dir=%s,data=%s%s}",
		dirserver_w_dir, dataserver,propagate_brev_no_extURL_PATH_INFO);
  }

  if (full_list_PATH_INFO == NULL)
			out_err("Cannot make full_list_PATH_INFO","");
  if (flat_PATH_INFO == NULL)
			out_err("Cannot make flat_PATH_INFO","");
  if (nextlevel_PATH_INFO == NULL)
			out_err("Cannot make nextlevel_PATH_INFO","");
  if (level0_PATH_INFO == NULL)
			out_err("Cannot make level0_PATH_INFO","");
  if (no_extURL_PATH_INFO == NULL)
			out_err("Cannot make no_extURL_PATH_INFO","");
  if (infoserver_extURL == NULL)
			out_err("Cannot make infoserver_extURL","");
 return;
}

Logical check_gifs(gifs)
char *gifs[];
  /*  If gif does not exist, replace its pointer with NULL		*/
  /*  Return TRUE if any gifs exist					*/
  /*  By default, this routine only looks at BUTTONIMAGESDIR.  If empty	*/
  /*    no gifs exist; else all gifs exist.  If TEST_GIFS specified,	*/
  /*    this routine will go out on net and look for each gif individ-	*/
  /*	ually.								*/
  /*  Put all tests in one module to try to do all testing w/one	*/
  /*    connect.  Don't know how to do this, however.  May not be poss-	*/
  /*    ible w/HTTP 1.0 routines (I assume HT routines in jdb.a are 1.0	*/
{
  char *buttonhoststart,*buttonfilestart;
  int lenbuttonimagesdir;

  int handle,len,lenimg,lenbuttonfile,len_Host_buf;
  int i;
  Logical null_all_gifs,got_dir,got_a_gif;
  char *ptr,*HEAD_buf,*Host_buf,*htdatabuf;
    /*  Buffer needs to be big enough to hold response to http HEAD	*/
    /*  request.  4096 chosen since that's what was in transfer, but	*/
    /*  I suspect that's for data, not just response to HEAD...		*/
#define HTDATARECSIZE 4096

  got_dir = (*buttonimagesdir != '\0');

    /*  Assume we're not testing for gifs.  In that case, turn off gifs	*/
    /*  if we weren't given a BUTTONIMAGESDIR 				*/
  null_all_gifs = ! got_dir;

  if ( got_dir && test_gifs ) {
    null_all_gifs = TRUE; /* .. in case we can't malloc various buffers	*/
    lenbuttonimagesdir = strlen(buttonimagesdir);
      /*  Set buttonhoststart to point after the protocol:// string	*/
      /*  Set buttonfilestart to point to the / that begins the file	*/
    buttonhoststart = strstr(buttonimagesdir,"://") + 3;
    if (  (buttonfilestart = strchr(buttonhoststart,'/')) == NULL  )
      buttonfilestart = buttonimagesdir + lenbuttonimagesdir;
    trailing_buttonimagesdir_slash = 
      (*(buttonimagesdir + lenbuttonimagesdir - 1) == '/');

      /*  2nd/3rd line buffer is "Host: ", host:port, "\r\n\r\n"	*/
    len_Host_buf = 6 + buttonfilestart - buttonhoststart + 4;
    Host_buf = (char *) malloc (len_Host_buf + 1);
    if (Host_buf != NULL) {
      strcpy (Host_buf,"Host: ");
      strncat (Host_buf, buttonhoststart, buttonfilestart - buttonhoststart);
      strcat (Host_buf, "\r\n\r\n");

      lenimg = 0;
      for (i=0; i<NGIFS; i++) {
	len = strlen(gifs[i]);
	if (len > lenimg) lenimg = len;
      }
	/*    1st line buffer is "HEAD ",filename," HTTP 1.0\r\n"	*/
	/*  buttonimagesdir + lenbuttonimagesdir - buttonfilestart	*/
	/*  is the length of any directory portion for gif that was	*/
	/*  given in buttonimagesdir.					*/
      lenbuttonfile = buttonimagesdir + lenbuttonimagesdir - buttonfilestart
		        + lenimg + 2;  /* +2 = we might need added /s	*/
      HEAD_buf = (char *) malloc (5 + lenbuttonfile + 11 + 1);
      if (HEAD_buf != NULL) {
	if ( (htdatabuf = (char *)malloc(HTDATARECSIZE +1)) != NULL  ) {
	    /*  From here, will be able to handle each gif individual-	*/
	    /*  ly, so don't handle them collectively			*/
	  null_all_gifs = FALSE;

	  for (i=0; i<NGIFS; i++) {
	    /*	Try to see if URL we will generate will point to exist-	*/
	    /*	ing file.  Enter into exchange w/			*/
	    /*	URL's host node.  Assume we know proper http protocols	*/
	    /*	to ask for a file, and to understand the server's	*/
	    /*	response.						*/
	  /*  HTDoConnect ignores the parts of the URL it doesn't need	*/
	  /*  (it only looks at protocol, node, and port, I think), so	*/
	  /*  we don't have to do any parsing.  No such luck if connect	*/
	  /*  succeeds..						*/
	    if (HTDoConnect(buttonimagesdir,"HTTP",80,&handle) == 0) {

	      strcpy (HEAD_buf, "HEAD ");
	      if (*buttonfilestart != '/') strcat(HEAD_buf,"/");
	      strcat (HEAD_buf, buttonfilestart);
	      ptr = gifs[i];
	      if (trailing_buttonimagesdir_slash) {
	        if (*ptr == '/') ptr++;
	      } else {
	        if (*ptr != '/') strcat(HEAD_buf,"/");
	      }
	      strcat (HEAD_buf,ptr);
	      strcat (HEAD_buf," HTTP/1.0\r\n");

	        /*    Code below adapted from transfer.c (G Flierl via	*/
	        /*    Email 18 Feb 98)					*/
	      len = strlen(HEAD_buf);
	      if (write(handle,HEAD_buf,len) != len)
	        out_err("Could not write during inquiry about button gif",
			"HEAD_buf");
	      if (write(handle,Host_buf,len_Host_buf) != len_Host_buf)
	        out_err("Could not write during inquiry about button gif",
			"Host_buf");

	      if (HTDoRead(handle,htdatabuf,HTDATARECSIZE) > 0) {
	          /*  Assume status is 2nd token on returned line	*/
	          /*  Also assume 2nd token fits within HTDATARECSIZE	*/
	          /*  Also assume token seps are blank, tab.  		*/
	          /*  Also assume 200 is only good return.  Aren't	*/
	          /*    assumptions fun?				*/
		ptr = strtok(htdatabuf,"\t ");
		if (ptr != NULL) ptr = strtok(NULL,"\t ");
		if (ptr == NULL)
		  gifs[i] = NULL;			/* Format bogus	*/
		else if (strcmp("200",ptr) != 0)
		  gifs[i] = NULL;	/* Not OK (or format bogus)	*/
	      } else {
		gifs[i] = NULL;				/*  Read failed	*/
	      }
		/*  Skip rest of reply to HEAD request			*/
	      close (handle);
	    } else {
	      gifs[i] = NULL;			/*  Connect failed	*/
	    }
	  }
	  free (htdatabuf);
	}
	free (HEAD_buf);
      }
      free (Host_buf);
    }
  }

  got_a_gif = FALSE;
  if (null_all_gifs)
    for (i=0; i<NGIFS; i++)
      gifs[i] = NULL;
  else
    for (i=0; i<NGIFS; i++)
      if (gifs[i] != NULL) {
	got_a_gif = TRUE;
	break;
      }
    
  return got_a_gif;
}

void tag_toplink(text,img)
char *text,*img;
  /*  Finish anchor and put tag on our top link.			*/
  /*  Tag is either text or img.  If img, text becomes alternate tag.	*/
  /*    If image does not exist, use text tag				*/
{
  char *slash,*ptr;

  if (img == NULL)
    printf("%s</a>",text);
  else {
    ptr = img;
    slash = "";
    if (trailing_buttonimagesdir_slash) {
      if (*ptr == '/') ptr++;
    } else {
      if (*ptr != '/') slash = "/";
    }
    printf ("<img alt=\"%15s\" border=0 src=\"%s%s%s\"></a>",
	    text,buttonimagesdir,slash,ptr);
  }
  return;
}

void print_vert_bar(gif,set_new_line)
Logical set_new_line;
char *gif;
  /*  Vertical bars are supposed to separate text links from all others	*/
  /*  Print one if:							*/
  /*	1) This is not the first link on a line				*/
  /*	2) The link we are about to output is text			*/
  /*	3) The link we are about to output is image, but the link we	*/
  /*	   output before was text					*/
  /*  This routine maintains state across calls...			*/
  /*    The set_new_line argument is used to set the "first link on a	*/
  /*  line" characteristic.  No vertical bar is output in this case	*/
{
  static Logical first_link;
  static Logical printed_text_link;

  if (set_new_line) {
    first_link = TRUE;
    return;
  }

  if (first_link) first_link=FALSE;
  else if ( (gif == NULL) || printed_text_link) printf (" | ");

  printed_text_link = (gif == NULL);
  return;
}

void outvarhtml()
/* 

  routine to print the heading of all data objects if in HTML mode
 
  The directory button should always direct the browser back to the 
  directory from which it came.

  Send link back to referring host for documentation - the .remoteobjects
  at that site has responsibility of correctly id'ing the INFOSERVER,
  using hyperlinks from a static obj.info file or Redirect to another host
  with HTTPD function (NCSA HTTPD 1.5.1 or gt)

*/

{
  char tmp[ATTRBUFSIZE];
  Logical any_buttons_on_line_1,any_buttons_on_line_2;

  int j;
  char *ptr,*ptr1;
  char next_level[10];	/*  Big enough for "Level 999\0"		*/

  char *gifs[NGIFS];
  gifs[DIR_GIF] = "dir.gif";
  gifs[DOC_GIF] = "doc.gif";
  gifs[PLOT_GIF] = "more.gif";
  gifs[LEVEL0_GIF] = "level0.gif";
  gifs[NOLEVEL0_GIF] = "nolevel0.gif";
  gifs[LEVELN_GIF] = "leveln.gif";
  gifs[NOLEVELN_GIF] = "noleveln.gif";
  gifs[FLAT_GIF] = "flat.gif";

  make_PATH_INFOs();

  check_gifs(gifs);

  printf("<title>%s -- Level %d</title>\n",objsav,maxlevel);

    /*  Make header look good by removing trigrams.  Note that this	*/
    /*  is inconsistent; this version of outer_brev only expects	*/
    /*  trigrams in selections & parsav could have significant % signs	*/
    /*  If that gets to be a problem, we'll have to reparse or make an	*/
    /*  appropriate string while parsing...				*/
  if (  (ptr = un_trigram(parsav,TRIGRAM_KEY,&j)) == NULL  )
    if (j<0) out_err("Could not malloc ptr to hold",parsav);
    else out_err("Bad trigram",parsav+j);
  printf("<h1>%s --%s-- Level %d</h1>\n",objsav,ptr,maxlevel);
  free(ptr);

    /*  No dirlink if we have no directory server			*/
  dirlink = dirlink && (*dirserver_w_dir != '\0');
    /*  No doclink if we have no infoserver				*/
  doclink = doclink && (*infoserver_extURL != '\0');

    /*  Put out toplinks.						*/
    /*      If there are no buttons corresponding to the links, all	*/
    /*    spec'd links go on 1 line.  Separate these text links with	*/
    /*    vertical bars.						*/
    /*      If there ARE buttons, they go on their "correct		*/
    /*    lines; that is, if any of doclink, dirlink, or plotlink	*/
    /*    are spec'd, they go on line 1; if any of lev0link, lev1link,	*/
    /*    or flatlink are spec'd, they go on line 2 (if there was a	*/
    /*    line 1).							*/
    /*      A mixture of text links and button links are allowed.  As	*/
    /*    for formatting, I tried...					*/
    /*									*/
    /*    Logic:							*/
    /*	    1) Put out vertical bar, if needed				*/
    /*	    2) Open the anchor with "<a"				*/
    /*	    3) Fill in href if link is being generated and output ">"	*/
    /*	    4) Call tag_toplink to associate text and, optionally, gif	*/
    /*		with anchor.  tag_toplink issues the close anchor	*/
    /*		"<\a"> string, too					*/
    /*	    5) Do some bookkeeping about what kind of link we just did	*/

  print_vert_bar (NULL,TRUE);
  any_buttons_on_line_1 = FALSE;

  if (dirlink) {
    print_vert_bar (gifs[DIR_GIF],FALSE);
    printf("<a href=\"//%s\">", dirserver_w_dir);
    tag_toplink("Directory", gifs[DIR_GIF]);
    if (gifs[DIR_GIF] != NULL) any_buttons_on_line_1 = TRUE;
  }


  if (doclink)  {
    print_vert_bar (gifs[DOC_GIF],FALSE);
      /*  infoserver wants an object name (no protocol or level),	*/
      /*  and enough info to generate pointers to "our" dir and data	*/
    printf("<a href=\"//%s%s\">", infoserver_w_obj, infoserver_extURL);
    tag_toplink("Documentation", gifs[DOC_GIF]);
    if (gifs[DOC_GIF] != NULL) any_buttons_on_line_1 = TRUE;
  }

  if (plotlink) {
    print_vert_bar (gifs[PLOT_GIF],FALSE);
      /*  optionserver wants a JGOFS object and its selections/proj-	*/
      /*  ections.  I suspect it does not want protocol or level info,	*/
      /*  but historically those have been provided.  It doesn't know	*/
      /*  how to handle extended URL's, so don't pass that either, but	*/
      /*  I suspect that will change, too.  A "JGOFS object" is speci-	*/
      /*  fied by its node/port and its name (in PATH_INFO, along w/	*/
      /*  protocol and level).  optionserver requires the node/port to	*/
      /*  start	with // (again for historical reasons, but we need some	*/
      /*  kind of separator anyway!)					*/
    printf("<a href=\"//%s//%s%s?%s\">",
      optionserver, localnodeport, no_extURL_PATH_INFO, parsav);
    tag_toplink("Plotting and Other Operations...", gifs[PLOT_GIF]);
    if (gifs[PLOT_GIF] != NULL) any_buttons_on_line_1 = TRUE;
  } 

    /*  If we are displaying buttons, and if we've displayed any so	*/
    /*    far, and if we have any left to display, "go to next line"	*/
    /*  I wonder if this should happen in a NOTOPLINKS environment...	*/
    /*    (See <br> comment, below)					*/
    /*  In order to find out about next line's buttons, we have to	*/
    /*  repeat a whole bunch of logic.  Makes changing line structure,	*/
    /*  etc, a drag...							*/
  if (any_buttons_on_line_1) {
    any_buttons_on_line_2 = (flatlink && (gifs[FLAT_GIF] != NULL));
    if ( ! any_buttons_on_line_2 )
      if (lev0link)
	if (maxlevel == 0) any_buttons_on_line_2 = (gifs[NOLEVEL0_GIF] != NULL);
	else any_buttons_on_line_2 = (gifs[LEVEL0_GIF] != NULL);
    if ( ! any_buttons_on_line_2 )
      if (levnlink)
	if (maxlevel == maxobjectlevel)
          any_buttons_on_line_2 = (gifs[NOLEVELN_GIF] != NULL);
	else
          any_buttons_on_line_2 = (gifs[LEVELN_GIF] != NULL);
    if (any_buttons_on_line_2) {
      printf("<p>");
      print_vert_bar (NULL,TRUE);
    }
  }

  if (lev0link) {
    if (maxlevel == 0) {
      ptr = " - At level 0 - ";
      ptr1 = gifs[NOLEVEL0_GIF];
      print_vert_bar (ptr1,FALSE);
      printf("<a>");
    } else {
      ptr = "Level 0";
      ptr1 = gifs[LEVEL0_GIF];
      print_vert_bar (ptr1,FALSE);
	/*  Why doesn't the level 0 button get a query string?		*/
	/*  Don't know, but for "outer for option server", it does per	*/
	/*   Chris (Apr 98)						*/
      if (oo_outer)
        printf ("<a href=\"%s%s?%s\">", 
		JGOFS_DATA_CGI_SPEC, level0_PATH_INFO, parsav);
      else
        printf("<a href=\"%s%s\">", JGOFS_DATA_CGI_SPEC, level0_PATH_INFO);
    }
    tag_toplink(ptr, ptr1);
  }

  if (levnlink) {
    if (maxlevel < maxobjectlevel) {
      ptr = next_level;
      ptr1 = gifs[LEVELN_GIF];
      print_vert_bar (ptr1,FALSE);
      printf ("<a href=\"%s%s?%s\">", 
		JGOFS_DATA_CGI_SPEC, nextlevel_PATH_INFO, parsav);
      sprintf(next_level,"Level %d",maxlevel+1);
    } else {
      ptr = " - At last level - ";
      ptr1 = gifs[NOLEVELN_GIF];
      print_vert_bar (ptr1,FALSE);
      printf("<a>");
    }
    tag_toplink(ptr, ptr1);
  }

  if (flatlink) {
    print_vert_bar (gifs[FLAT_GIF],FALSE);
    printf ("<a href=\"%s%s?%s\">", 
		JGOFS_DATA_CGI_SPEC, flat_PATH_INFO, parsav);
    tag_toplink("Flat list", gifs[FLAT_GIF]);
  }

    /*  If we are displaying buttons, "<br>" (whatever that is)	*/
    /*  I wonder if this should also happen in a NOTOPLINKS environment */
    /*    (See <p> comment, above)					*/
  if (any_buttons_on_line_1 || any_buttons_on_line_2) printf("<br>");

  printf("\n");
  printf("<pre>\n");

  print_comments_html();

    /* look at attributes to create array of widths - see ioattrout_ */
  for(j=0; j<ntotal; j++)
    if (level[j] >= 0)
      while(ioattrout_(&j,tmp))
        ;

  return;
}

void print_html_data_line(print_data_as_links,level_to_print)
Logical print_data_as_links;
int level_to_print;
/*  print_html_data_line scans the list of all variables, selecting	*/
/*  those at level level_to_print.  It prints the data corresponding	*/
/*  to each selected variable as either a text string, or a link to	*/
/*  the next level with the text string as a label.  The choice of	*/
/*  of which way to print is governed by print_data_as_links arg and	*/
/*  the quotenolink global variable.  The rules:			*/
/*    1) At most one level is displayed as links.			*/
/*    2) The candidate level is the last level being displayed		*/
/*    3) If that level is the last of the object, do not display as	*/
/*	as links							*/
/*    4) if quotenolink: regardless of level, if datum is enclosed	*/
/*	in "s, remove them and print the rest as text			*/
/*  Rule 4 is implemented here.  Rules 1, 2, & 3 are "put into"		*/
/*  print_data_as_links by outputhtml					*/
{
  int j,len_datum,field_width;
  char tmp[DATUMBUFSIZE];
  char *comma,*ptr;

  comma = (*parsav == '\0') ? "\0" : ",";

  for(j=0;j<ntotal;j++)
    if (*(level+j) == level_to_print) {

      iovalstr_(&j,tmp);
      len_datum = strlen(tmp);
      field_width = iowidth_(&j);

      if (  quotenolink && (*tmp == '\"')  )   {
        if (tmp[len_datum-1] == '\"') tmp[len_datum-1] = '\0';
        printf("%-*s  ",field_width,tmp+1);

      } else if (print_data_as_links) {
	  /*  Might be able to trigram tmp as well as htmlescape it in	*/
	  /*  order to allow data to contain embedded blanks, etc.  If	*/
	  /*  so, do it BEFORE htmlescape.  Logically, trigramming	*/
	  /*  protects characters from http; htmlescape from html.	*/
	  /*  Note that html un-htmlescapes stuff, while trigrammed	*/
	  /*  stuff needs explicit un-trigramming			*/
	ptr = htmlescape(tmp);
        printf("<a href=\"%s%s?%s%s%s%s%s\">%-s</a>%*s",
          JGOFS_DATA_CGI_SPEC, nextlevel_PATH_INFO, 
	  parsav, comma, outnames+j*VARNAMEBUFSIZE, equality_test, ptr,
          tmp,
	  field_width-len_datum+2, " "
	);
	free (ptr);
      }
      else printf("%-*s  ",field_width,tmp);
    }

  printf("\n");

  return;
}

void print_full_list_link (s)
char *s;
{
#define BREVLINKINDENT 16

  printf ("%*s%s\n",BREVLINKINDENT," ",s);
  printf ("%*s<a href=\"%s%s?%s\">Full data listing</a>\n",
    BREVLINKINDENT, " ", JGOFS_DATA_CGI_SPEC, full_list_PATH_INFO, parsav);

  return;
}

Logical outputhtml(firsttime)
Logical firsttime;
{
    /* linecount for abbreviated listing feature			*/
  static int linecount;

  int i,j;

    /* minlevel != maxlevel for first record of each maxlevel file	*/
    /* in a multilevel display						*/
  if (minlevel<maxlevel || firsttime) {

    linecount = 0;

    print_comments_html();

      /*  Print headers for all levels; print data for levels less	*/
      /*  than maxlevel.						*/
    for (i=minlevel; i<=maxlevel; i++){
      printf("=========================\n");
      for (j=0; j<ntotal; j++)
        if (level[j] == i)
	  printf("%-*s  ", iowidth_(&j), outnames+j*VARNAMEBUFSIZE);
      printf("\n");
      printf("-------------------------\n");

      if (i == maxlevel) {
	  /*  Done w/headers & data for low levels; header for last	*/
	  /*  level.  Set minlevel so we don't do headers again		*/
        minlevel=maxlevel;
	break;
      }

      print_html_data_line(FALSE,i);
    }

  }

	/***								*/
	/*  ... at last level to be displayed				*/
	/***								*/

    /*  Leave if abbreviated listing and line isn't in range		*/
    /*  Abbreviated listing only applies to bottom level of object	*/
  if (brevflag && (maxlevel == maxobjectlevel))  {
    if (linecount == brevend) {
      print_full_list_link("[ ... truncating listing here]");
      return FALSE;
    }
    if (linecount++ < brevstart) {
	/*  Put link at beginning (only once!) as well as end in case	*/
	/*  data ends before brevstart is reached			*/
      if (linecount == 1)
	print_full_list_link("[ ... skipping up to first listing line]");
      return TRUE;
    }
  }

    /*  Print data as links unless we're at bottom level of object	*/
  print_html_data_line(  (maxlevel != maxobjectlevel), maxlevel  );

  return TRUE;
}

Logical get_logical_val (string)
char *string;
/*  Return 1 if string is synonym for TRUE, 0 for FALSE, -1 if not a synonym */
/*   (Duplicate of defgb_utils' get_logical_value function. Sigh)	*/
{
  switch (*string) {
    case 't': case 'T': case 'y': case 'Y': case '1':
      if (*(string+1) == '\0') return TRUE;
      if ( (strcmp("YES",string) == 0)		|| 
	 (strcmp("yes",string) == 0)		||
         (strcmp("TRUE",string) == 0)		||
	 (strcmp("true",string) == 0)		   )  return 1;
      break;
    case 'f': case 'F': case 'n': case 'N': case '0':
      if (*(string+1) == '\0') return FALSE;
      if ( (strcmp("NO",string) == 0)		||
	 (strcmp("no",string) == 0)		||
	 (strcmp("FALSE",string) == 0)		||
	 (strcmp("false",string) == 0)		   )  return 0;
      break;
    default:		/*  For completeness				*/
      ;
      break;
  }

  return -1;
}

Logical reconciliation(assert,negate,assert_default)
char *assert,*negate;
Logical assert_default;
/*  Given 2 environment variables assert and negate, where one is	*/
/*  logically the negative of the other (eg; TOPLINKS and NOTOPLINKS),	*/
/*  reconcile their values (if present in any combination) into a	*/
/*  single consistent logical value					*/
/*    Negate is optional.  If not there, this routine validates assert,	*/
/*  if present, and returns it.  If no assert, return default		*/
{
  Logical assert_val,negate_val;
  char *ptr;

  if (  (ptr = getenv(assert)) == NULL  )  assert_val = UNSPECIFIED;
  else if (  (assert_val = get_logical_val(ptr)) < 0  )
				out_err("Invalid logical value of env var",ptr);

  if (negate == NULL) negate_val = UNSPECIFIED;
  else if (  (ptr = getenv(negate)) == NULL  )  negate_val = UNSPECIFIED;
  else if (  (negate_val = get_logical_val(ptr)) < 0  )
				out_err("Invalid logical value of env var",ptr);

    /*  Both true or both false is an error.  				*/
    /*  Note cannot just test (assert_val && negate_val) since they can	*/
    /*    have values other than TRUE or FALSE				*/
  if (  (  (assert_val == TRUE)  &&  (negate_val == TRUE) )  ||
	(  (assert_val == FALSE) &&  (negate_val == FALSE))     )
    out_err("Conflicting values of logical env vars",assert);

    /*  If we get here, anything user defined is consistent, so we can	*/
    /*  use either defined value, if any.				*/
    /*		assert defined and negate defined: use assert		*/
    /*		assert defined and negate undefined: use assert		*/
    /*		assert undefined and negate defined: use NOT negate	*/
    /*		assert undefined and negate undefined: use default	*/
  if (assert_val == UNSPECIFIED)
    assert_val = (negate_val == UNSPECIFIED) ? assert_default : !negate_val;

  return assert_val;
}

void get_outer_params()
  /*  Parameters for this invocation of outer come from the http-	*/
  /*  supplied environment variables PATH_INFO and QUERY_STRING and	*/
  /*  from other environment variables often set by serv from entries	*/
  /*  in the .objects file for the object we are about to process.	*/
  /*  The PATH_INFO environment variable carries lots of info.  See	*/
  /*  path_info_routines.c for lots of doc about contents and formatting */
  /*    Based on parameters, this routine also sets up an html or non-	*/
  /*  html environment.  Logically that should happen after this	*/
  /*  routine returns, but the environment is needed to allow pro-	*/
  /*  cessing of any errors that happen in this routine, so setup must	*/
  /*  happen as soon as possible					*/
{
  char *ptr,*ptr1;
  int i,j;

  if (  (parsav = getenv("QUERY_STRING")) == NULL  ) parsav = "\0";

  if (getenv("PATH_INFO") == NULL) {
    printf("Content-type: text/plain\n\n");
    return;
  }

/*  Get output protocol (html, flat, etc) from PATH_INFO env variable	*/

    /*  Note that in case of error getting protocol, error_ might	*/
    /*  not be able to get environment correct & a "Server 500"	*/
    /*  could happen							*/

  if (  (ptr = get_jgofs_env_datum("protocol",NULL)) == NULL  )
    out_err("Incorrectly formatted PATH_INFO",getenv("PATH_INFO"));
  brevflag = (strcmp(ptr,"brev") == 0);
  htmlflag = (  (strcmp(ptr,"html") == 0) || brevflag  );
  if (htmlflag) printf("Content-type: text/html\n\n");
  else {
    printf("Content-type: text/plain\n\n"); 
    flatflag = (strcmp(ptr,"flat") == 0);
  }

  if (  !(htmlflag || flatflag)  ) return;

/*	We're displaying stuff.  Need lots of info			*/

    /*  Get misc logical environment variables				*/
  oo_outer = reconciliation("OO_OUTER",NULL,OO_OUTER);
  quotenolink = reconciliation("QUOTENOLINK",NULL,QUOTENOLINK);
  toplinks = reconciliation("TOPLINKS","NOTOPLINKS",TOPLINKS);
  dirlink = reconciliation("DIRLINK","NODIRLINK",DIRLINK);
  doclink = reconciliation("DOCLINK","NODOCLINK",DOCLINK);
  plotlink = reconciliation("PLOTLINK","NOPLOTLINK",PLOTLINK);
  lev0link = reconciliation("LEV0LINK","NOLEV0LINK",LEV0LINK);
  levnlink = reconciliation("LEVNLINK","NOLEVNLINK",LEVNLINK);
  flatlink = reconciliation("FLATLINK","NOFLATLINK",FLATLINK);
    /*  Note PLOTLINK in next line.  Assures that run-time PLOTLINK	*/
    /*  supersedes compile-time OTHEROPTS and vice versa.  See outer.h	*/
  otheropts = reconciliation("OTHEROPTS","NOOTHEROPTS",PLOTLINK);
  test_gifs = reconciliation("TEST_GIFS","NOTEST_GIFS",TEST_GIFS);
  
    /*  Set up for generating numeric or alpha selections when outer	*/
    /*  generates a "next-level-with-selection" link			*/
  if (  reconciliation ("GENERATE_ALPHA_SELECTIONS",
			"NOGENERATE_ALPHA_SELECTIONS",
			GENERATE_ALPHA_SELECTIONS)      ) {
      /*  Next line returns "%20eq%20"					*/
    equality_test = trigram(" eq "," ",TRIGRAM_KEY,NULL);
    if (equality_test == NULL)
      out_err("Cannot get memory for equality_test","");
  } else
    equality_test = "=";

    /*  If both plotlink and otheropts are specified, they must match	*/
    /*  If either is specified, use that val; else use TRUE (or TOPLINK?) */
  if (plotlink == UNSPECIFIED) 
    plotlink = (otheropts == UNSPECIFIED) ? TRUE : otheropts;
  else
    if ((otheropts != UNSPECIFIED) && (plotlink != otheropts))
      out_err("Conflicting values of PLOTLINK and OTHEROPTS","");

  if (doclink == UNSPECIFIED) doclink = toplinks;
  if (lev0link == UNSPECIFIED) lev0link = toplinks;
  if (levnlink == UNSPECIFIED) levnlink = toplinks;
  if (flatlink == UNSPECIFIED) flatlink = toplinks;

    /*  Get misc "address information" environment variables that	*/
    /*  may need to appear in links					*/

    /*  localnodeport is, logically, the node:port spec of the		*/
    /*  http server that's running us.  This is the info in		*/
    /*  SERVER_NAME and SERVER_PORT.  However, for hosts, we		*/
    /*  normally prefer to output our www alias on URLs we generate	*/
    /*  - that's what's in MYADDR.  Don't know why MYADDR should con-	*/
    /*  tain a port at all; ie, I don't know why SERVER_PORT should not	*/
    /*  always be used.  ?? Oh well...  For parallelism w/MYADDR, allow	*/
    /*  a MYPORT, too.  outer 2.5 and earlier did			*/
    /*  not propagate the port at all, so we don't propagate port 80 	*/
  if (  (localnodeport = getenv("MYADDR")) == NULL  )  
    if (   ( *(localnodeport = MYADDR) == '\0' )   )
      if (  (localnodeport = getenv("SERVER_NAME")) == NULL  )
	localnodeport = MYADDR;		/*  address of empty string	*/
  if (  (ptr = getenv("MYPORT")) == NULL  )  
    if (   ( *(ptr = MYPORT) == '\0' )   )
      if (  (ptr = getenv("SERVER_PORT")) == NULL  )
	ptr = MYPORT;			/*  address of empty string	*/
  if (*ptr == ':') ptr++;		/*  Just in case...		*/
  i = (int) strtol(ptr,&ptr1,10);
  if (*ptr1 == '\0') {
    if (  ! ((i == 0) || (i == 80))  )   {
	/*  Ready to add port if localnodeport didn't already have one.	*/
	/*   Assume colon is node/port separator.  If nothing after	*/
	/*   colon, pretend it isn't there				*/
      if (  (ptr1 = strchr(localnodeport,':')) != NULL)
	if (*(++ptr1) == '\0') {
	  *(ptr1-1) = '\0';
	  ptr1 = NULL;
	}
      if (ptr1 == NULL) {
	ptr1 = localnodeport;
	j = strlen(ptr1) + 1 + strlen(ptr) + 1;
	if (  (localnodeport = (char *)malloc(j)) == NULL )
				malloc_err("localnodeport",j);
	sprintf(localnodeport,"%s:%s", ptr1, ptr);
      }
    }
  } else
    out_err("Non-numeric port in MYPORT/SERVER_PORT",ptr);

  j = strlen(localnodeport) + strlen(JGOFS_DATA_CGI_SPEC) + 1;
  if (  (dataserver = (char *)malloc(j)) == NULL  )
				malloc_err("dataserver",j);
  strcpy(dataserver,localnodeport);
  strcat(dataserver,JGOFS_DATA_CGI_SPEC);

    /*  buttonimagesdir is expected to include the front part of a	*/
    /*  URL, not just a dir.  Stick prefix on if necessary		*/
  if (  (buttonimagesdir = getenv("BUTTONIMAGESDIR")) == NULL  )
					buttonimagesdir = BUTTONIMAGESDIR;
  if (buttonimagesdir == NULL) buttonimagesdir = "";
  if (*buttonimagesdir != '\0') {
    if (strstr(buttonimagesdir,"://") == NULL) {
      ptr = buttonimagesdir;
      j = strlen("file://localhost") + 1 + strlen(ptr) + 1;
      if (  (buttonimagesdir = (char *)malloc(j)) == NULL  )
				malloc_err("buttonimagesdir",j);
      strcpy(buttonimagesdir,"file://localhost");
      if (*ptr != '/') strcat (buttonimagesdir,"/");
      strcat (buttonimagesdir,ptr);
    }
  }

  if (brevflag) {
    if (  (ptr = getenv("BREVSTART")) == NULL  ) brevstart=BREVSTART;
    else {
      brevstart = (int) strtol(ptr,&ptr1,10);
      if (*ptr1 != '\0') out_err("Non-numeric BREVSTART",ptr);
    }
    if (brevstart < 1) out_err("Illegal BREVSTART",ptr);
    if (  (ptr = getenv("BREVCOUNT")) == NULL  ) brevcount=BREVCOUNT;
    else {
      brevcount = (int) strtol(ptr,&ptr1,10);
      if (*ptr1 != '\0') out_err("Non-numeric BREVCOUNT",ptr);
    }
    if (brevcount < 1) out_err("Illegal BREVCOUNT",ptr);
    brevstart--;		/* start w/0 vs start w/1 adjustment	*/
    brevend = brevstart + brevcount;
  }

    /* syntax of OPTIONSERVER (like all *SERVERs) is			*/
    /*		machine.domain/jg/script-or-binary			*/
    /* optionally preceded by //					*/

    /*  Very hard for there not to be an OPTIONSERVER in		*/
    /*  environment.  serv will put one there from the .objects		*/
    /*  file, or from a value compiled into it (usually from		*/
    /*  build-env).  Therefore, only time we use the constant		*/
    /*  OPTIONSERVER compiled into outer is when we're being run	*/
    /*  standalone.  In particular, you cannot build a method		*/
    /*  with "its own" OPTIONSERVER by compiling outer with that	*/
    /*  option server.  Use the .objects file instead...		*/
  if (  (optionserver = getenv("OPTIONSERVER")) == NULL  )
						optionserver = OPTIONSERVER;
    /* Make sure whatever we have does NOT start with // since 		*/
    /* printf puts them in						*/
  if (strncmp("//",optionserver,2) == 0) optionserver += 2;

    /*  Get object name, level, dir routine that got us here, and	*/
    /*  info routine we should use.  All this may have been encoded	*/
    /*  into the PATH_INFO env variable, or may be in individual	*/
    /*  environment variables, or may be in compilation constants,	*/
    /*  or may be we just defaulted it in this module			*/
  if (  (reqlevel = get_level(NULL)) == LEVEL_ERROR  )
    out_err("Incorrectly formatted PATH_INFO",getenv("PATH_INFO"));

  if (  (objsav = get_jgofs_env_datum("object",NULL)) == NULL  )
    out_err("Incorrectly formatted PATH_INFO",getenv("PATH_INFO"));

    /*  If dir= is in extended URL, it probably includes a direct-	*/
    /*  ory spec.  A dirserver w/o a directory spec will operate	*/
    /*  on a default directory; hence it is also logically includes	*/
    /*  a directory spec.  Who knows what somebody would put in		*/
    /*  the environment?  We assume it's just a server, but it		*/
    /*  doesn't have to be						*/
  if (  (dirserver_w_dir = get_jgofs_env_datum("dir",NULL)) == NULL  )
    out_err("Incorrectly formatted PATH_INFO",getenv("PATH_INFO"));
  if (*dirserver_w_dir == '\0') {
    free (dirserver_w_dir);
    if (  (dirserver_w_dir = getenv("DIRSERVER")) == NULL  )
      dirserver_w_dir = DIRSERVER;
  }
  if (strncmp("//",dirserver_w_dir,2) == 0) dirserver_w_dir += 2;

    /*  If info= is in extended URL, it includes objsav.  Too bad;	*/
    /*  would be cleaner to just get server here and add objsav		*/
    /*  when creating the "Documentation" link.  However, this way	*/
    /*  allows info to come from a source whose name is not related	*/
    /*  to the object name.  If that's the intent, this comment		*/
    /*  should be changed to say so					*/
  if (  (infoserver_w_obj = get_jgofs_env_datum("info",NULL)) == NULL  )
    out_err("Incorrectly formatted PATH_INFO",getenv("PATH_INFO"));
  if (*infoserver_w_obj == '\0') {
    free (infoserver_w_obj);
    if (  (ptr = getenv("INFOSERVER")) == NULL  ) ptr = INFOSERVER;
    j = strlen(ptr) + strlen(objsav) + 1;
    if (  (infoserver_w_obj = (char *) malloc(j)) == NULL  )
				malloc_err ("infoserver_w_obj",j);
    strcpy (infoserver_w_obj,ptr);
    strcat (infoserver_w_obj,objsav);
  }	
  if (strncmp("//",infoserver_w_obj,2) == 0) infoserver_w_obj += 2;

  return;
}

main(argc,argv)

int argc;
char *argv[];
{
  int i,j,k,nparams;
  int any_projections = FALSE;
  char *stmp;
  Logical firsttime,output_next_line;

    /*  Characters which, if found in argument, show that argument is	*/
    /*  selection.  Since arguments are selections or projections, and	*/
    /*  since projections are just variable names, we should be able to	*/
    /*  distinguish the 2 by seeing if the string is a legal C variable	*/
    /*  name (per an agreement at a CLH GRF RCG WJS meeting a long	*/
    /*  time ago (<= 1997)).  However, I'm going to be more conserva-	*/
    /*  tive.  New string operators are of the form "blank op blank"	*/
    /*  with the blanks possibly escaped via trigrams.  Thus I'm	*/
    /*  assuming 1) no blanks in var names  2) no %s in var names &	*/
    /*  3) we won't ever want to escape a character legal to a var name	*/
    /*  (Got assent to #2 via Email from Bob & Chris 19 Nov 98)		*/
  char selection_indicators[] = {'>','<','=',' ',TRIGRAM_KEY,'\0'};

  reqlevel=LEVEL_NOT_SPECIFIED;		/* from path_info_routines.h	*/
  get_outer_params();
  if (reqlevel == LEVEL_NOT_SPECIFIED) reqlevel = 999;	/* 999 from orig code */

  nparams=argc-1;
  maxobjectlevel=ioopen_(argv+1,&nparams,&ntotal);

  i = ntotal*sizeof(int);
  if (  (level = (int *) malloc(i)) == NULL  )  malloc_err("level array",i);
  for (i=0;i<ntotal;i++) *(level+i)= -iovarlevel_(&i)-1;

  j = ntotal*VARNAMEBUFSIZE;
  if (  (outnames = (char *)malloc(j)) == NULL  )  malloc_err("outnames",j);
  for (j=0; j<ntotal; j++)
    ioname_(&j,outnames+j*VARNAMEBUFSIZE);

 
    /*  inner has removed any arguments it processed.  outer handles	*/
    /*  the rest.  Right now, these consist of projections and selec-	*/
    /*  tions.  Selections need to be parsed				*/
  for (i=1; i<argc; i++)
      /*  *argv[i] is '\0' for args processed by inner			*/
    if (*argv[i] != '\0')
      if (strpbrk(argv[i],selection_indicators)) {
	  /*  Selection							*/
	if (  (stmp = un_trigram(argv[i],TRIGRAM_KEY,&j)) == NULL  )
	  if (j<0) out_err("Could not malloc stmp to hold",argv[i]);
	  else out_err("Bad trigram",argv[i]+j);
	parse(stmp);
	free(stmp);
      } else {
	  /*  Projection						*/
	for (k=0; k<ntotal; k++)
          if (strcmp(argv[i],outnames+k*VARNAMEBUFSIZE)==0) break;
	if (k==ntotal) out_err("Bad Name",stmp);
	if (  (*(level+k)<0) && (*(level+k) >= -reqlevel-1)  )
	  *(level+k)= - *(level+k)-1;
	any_projections = TRUE;
      }

  if ( ! any_projections )
    for (i=0; i<ntotal; i++)
      if (*(level+i) >= -reqlevel-1) *(level+i)= - *(level+i)-1;

  maxlevel=0;
  for (j=0; j<ntotal; j++)
    if ( *(level+j) > maxlevel )  maxlevel= *(level+j);

  /*  The following 3 lines appear in the jgofs 1.5 outer, commented	*/
  /*  out there as they are here.					*/
/*
  for (j=0;j<tstproccnt;j++)
    if (tstproc[j] >= 128)
      if (tst[j].testlev > maxlevel) maxlevel=tst[j].testlev;
*/

  if (reqlevel>maxlevel) reqlevel=maxlevel;
/*  There may be some ambiguity between reqlevel, maxlevel, and the	*/
/*	level in PATH_INFO.  maxlevel is the "deepest" level we will	*/
/*	display.  reqlevel starts out as what was in PATH_INFO.  It	*/
/*	is used to set an upper bound on maxlevel (the "bottom" level	*/
/*	of the object is also an upper bound indirectly via the calls	*/
/*	to iovarlevel_).  maxlevel can further be reduced by proj-	*/
/*	ections.  Finally, maxlevel is used as an upper bound on	*/
/*	reqlevel.  Thus, after everything is over, I think reqlevel	*/
/*	ends up equal to maxlevel.  Logically, however, if reqlevel	*/
/*	means "requested level", that's what's in the URL.  Everything	*/
/*	else is logically maxlevel.					*/
/*  reqlevel is used in the "Next level" button.  I think it would work	*/
/*	better if the above "if" were removed, in which case reqlevel	*/
/*	would represent the min of the user requested level & the	*/
/*	number of levels in the object					*/

#ifdef DEBUG
  printf("maxlevel %d\n",maxlevel);
#endif

  minlevel=maxlevel;
  cl=0;
  firsttime=TRUE;
  output_next_line=TRUE;

  if (htmlflag) outvarhtml();
  else if (flatflag) outvarflat();
  else outvar();

  if (  (stmp = (char *)malloc(COMMENTLINEBUF)) == NULL  )
				malloc_err ("comment buffer",COMMENTLINEBUF);

  while(1){
    if (output_next_line && ioreadrec_(&cl))  {
#ifdef DEBUG
      printf("succ. read at lev %d\n",cl);
#endif

      if(test(cl))
	if(cl<maxlevel){
	  if(cl<minlevel)minlevel=cl;
	  cl++;
	} else {
	  if (htmlflag) output_next_line = outputhtml(firsttime);
	  else if (flatflag) output_next_line = outputflat(firsttime);
	  else output_next_line = output(firsttime);
	  firsttime = FALSE;
	}
    } else {
#ifdef DEBUG
      printf("failed read at lev %d\n",cl);
#endif
      if (cl == 0) break;
        /* flush comment buffer */
      while (iocommout_(stmp))
	;
      cl--;
      output_next_line=TRUE;
    }
  }

  ioclose_();

  if (htmlflag) printf ("</pre>\n"); 
  else if (!flatflag) printf("&e\n");

  exit(0);
}
/********************			**********************		*/
/*		Replaced token processing code from parse in original outer
  if(isdigit(*t) || *t=='.' || *t=='+' || *t=='-'){
    f=strtod(t,&t1);
    if(strchr(" ()$=<>|&!",*t1)){
      typ=1;
      t=t1;
    } else {
      i=strcspn(t," ()$=<>|&!");
      strncpy(tok,t,i);
      tok[i]='\0';
      t += i;
      typ=0;
    };
  } else if(isalpha(*t) || *t=='#'){
    i=strcspn(t," ()$=<>|&!");
    strncpy(tok,t,i);
    tok[i]='\0';
    t += i;
    typ=0;
  } else if(*t == '('){
    typ=2;
    t++;
  } else if(*t == ')'){
    typ=3;
    t++;
  } else if(*t == '$'){
    typ=4;
    t=NULL;
  } else {
    tok[0]= *t;
    if((int)strspn(t+1,"&|=>")>0){
      tok[1]= *(++t);
      tok[2]='\0';
    } else
      tok[1]='\0';
    t++;
    typ=5;
  };
*/
