/* National Institute of Standards and Technology (NIST)
/* National Computer System Laboratory (NCSL)
/* Office Systems Engineering (OSE) Group
/* ********************************************************************
/*                            D I S C L A I M E R
/*                              (March 8, 1989)
/*  
/* There is no warranty for the NIST NCSL OSE SGML parser and/or the NIST
/* NCSL OSE SGML parser validation suite.  If the SGML parser and/or
/* validation suite is modified by someone else and passed on, NIST wants
/* the parser's recipients to know that what they have is not what NIST
/* distributed, so that any problems introduced by others will not
/* reflect on our reputation.
/* 
/* Policies
/* 
/* 1. Anyone may copy and distribute verbatim copies of the SGML source
/* code as received in any medium.
/* 
/* 2. Anyone may modify your copy or copies of SGML parser source code or
/* any portion of it, and copy and distribute such modifications provided
/* that all modifications are clearly associated with the entity that
/* performs the modifications.
/* 
/* NO WARRANTY
/* ===========
/* 
/* NIST PROVIDES ABSOLUTELY NO WARRANTY.  THE SGML PARSER AND VALIDATION
/* SUITE ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
/* EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
/* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
/* THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS
/* WITH YOU.  SHOULD THE SGML PARSER OR VALIDATION SUITE PROVE DEFECTIVE,
/* YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
/* 
/* IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW WILL NIST BE LIABLE FOR
/* DAMAGES, INCLUDING ANY LOST PROFITS, LOST MONIES, OR OTHER SPECIAL,
/* INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR
/* INABILITY TO USE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA
/* BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY THIRD PARTIES OR A
/* FAILURE OF THE PROGRAM TO OPERATE WITH PROGRAMS NOT DISTRIBUTED BY
/* NIST) THE PROGRAM, EVEN IF YOU HAVE BEEN ADVISED OF THE POSSIBILITY OF
/* SUCH DAMAGES, OR FOR ANY CLAIM BY ANY OTHER PARTY.
*/

/************************************************************************/
/*   TITLE:          SGML PARSER                                        */
/*   SYSTEM:         DOCUMENT PROCESSOR                                 */
/*   SUBSYSTEM:                                                         */
/*   SOURCE FILE:    DIUTIL.C                                           */
/*   AUTHOR:         Steven Lindeman, Fred Maples                       */
/*                                                                      */
/*   DATE CREATED:                                                      */
/*   LAST MODIFIED:                                                     */
/*                                                                      */
/*                  REVISIONS                                           */
/*   WHEN      WHO            WHY                                       */
/************************************************************************/
#include <stdio.h>
#include <ctype.h>
#include "didefs.h"
#include "diglobal.h"

/*----------------------------------------------*/
/*      A D A L L O C                 */
/* Allocates enough memory for one  attri-   */
/* bute description linked list entry. */
/*----------------------------------------------*/
ATTRDESC *adalloc()
{
   /*   char *calloc();*/
   ATTRDESC *retptr;
   if ((retptr=(ATTRPTR) malloc(sizeof(ATTRDESC))) == NULL)
      ourexit(2,"\nInsufficient memory in parse3.\n");
   return(retptr);
}

/*----------------------------------------------*/
/*               O Q A L L O C                  */
/*----------------------------------------------*/
OUT_QUEUE *oqalloc()
{
   /*   char *calloc();*/
   OUT_QUEUE *retptr;
   if ((retptr=(OUT_QUEUE_PTR) malloc(sizeof(OUT_QUEUE))) == NULL)
      ourexit(2,"\nInsufficient memory in parse3.\n");
   return(retptr);
}

/*------------------------------------------------------*/
/*                 C H E C K _ C R                      */
/* This routine dumps out the carriage returns  */
/* that were previously saved.  At this point we   */
/* know they are significant.       */
/*------------------------------------------------------*/
void check_cr(num_cr,cr_found,first_time,end_of_elt)
unsigned *num_cr;
BOOLEAN cr_found,*first_time,end_of_elt;
{ 
   if (*first_time) {
      (*put_ctr)('\n',ctrfp);
      *first_time = FALSE;
   }
   if (cr_found) {
      (*print_ctr)(ctrfp,"|");
      while((*num_cr)-->0)
         (*print_ctr)(ctrfp,"\n[]");
      if (!end_of_elt)
         (*put_ctr)('\n',ctrfp);
   }
   return;
}

/*--------------------------------------------------------------*/
/*                     C H E C K _ E N T I T Y                 */
/* This routine is called when a general entity reference   */
/* has occurred either in the data stream or from an  */
/* attribute value literal.            */
/*--------------------------------------------------------------*/
void check_entity(cdata_attr,num_csdata,entptr,datatext_read,entname,actual_length,inside_attr,inchar)
BOOLEAN cdata_attr;
unsigned *num_csdata;
ENTITYDESC *entptr;
BOOLEAN *datatext_read;
char entname[];
int *actual_length;
BOOLEAN inside_attr;
int *inchar;
{
   char str_to_unget[LITLEN+6];

   entstack[entitylevel] = lookstack();
   if (++entitylevel > ENTLVL)
      ourexit(2,"\nError: Nesting level of entities > ENTLVL\n");
   switch(entptr->entitytype) {
   case KW_MS:
      sprintf(str_to_unget,"<![%s]]>",entptr->entityvalue);
      break;
   case KW_STARTTAG:
      sprintf(str_to_unget,"<%s>",entptr->entityvalue);
      break;
   case KW_ENDTAG:
      sprintf(str_to_unget,"</%s>",entptr->entityvalue);
      break;
   case KW_MD:
      sprintf(str_to_unget,"<!%s>",entptr->entityvalue);
      break;
   case KW_PI:
      if (inside_attr)
         ourexit(2,"\nError: Processing instruction invalid in attribute value literal.\n");
      else {
         (*print_ctr)(ctrfp,"\n[?%s]",entptr->entityvalue);
         (*applic)(PROC_INST,entptr->entityvalue);
      }
      break;
   case KW_CDATA: 
   case KW_SDATA:
      if (inside_attr && !cdata_attr)
         ourexit(2,"\nError: CDATA or SDATA entity invalid in this attribute literal.\n");
      strcpy(str_to_unget,entptr->entityvalue); 
      *datatext_read = TRUE;
      if (inside_attr)
         *num_csdata += 1;  /* count of CDATA or SDATA references */
      break;
   case KW_SYSTEM: 
   case KW_PUBLIC:
      (*print_ctr)(ctrfp,"\n[&%s]",entname);
      /*    if ((fpstack[++fpindx]=fopen("fred","rb")) == NULL)
               ourexit(2,"Error: Unknown system entity reference.\n");
      */
      break;
   default:
      strcpy(str_to_unget,entptr->entityvalue); 
      break;
   }
   if ((*inchar=our_fgetc(indoc)) != REFC)
      if (*inchar == RE) { /* RS and RE are insignificant */
         *inchar = our_fgetc(indoc);  /* get RS */
         if (inside_attr)
            *actual_length += 2;
      }
      else
         our_ungetc(*inchar,indoc);
   unget_entity(str_to_unget);
   return;
}

/*------------------------------------------------------*/
/*               C H E C K _ F I X E D                  */
/*     This routine guarantees that the attribute  */
/*     declared as being FIXED matches the actual  */
/*     value that was given.  If the two do not match */
/*     exactly, an error condition is raised.      */
/*------------------------------------------------------*/
void check_fixed(defcode,str1,str2,length)
ADFLT defcode;
char str1[],str2[];
int length;
{
   if (defcode==A_FIXED && strncmp(str1,str2,length)!=0) {
      sprintf(error_msg,"%s'%s'.\n","\nError: FIXED attribute declared.  Must use ",str2);
      FATAL_ERROR()
   }
   return;
}

/*------------------------------------------------------*/
/*       C R E A T E C M                      */
/*                                                 */
/*         Called by  :PUSHCREATE                     */
/*                                                */
/*         Returns    :pointer to the new             */
/*                 content model                  */
/*                                                */
/*      Creates a copy of the given content model     */
/*------------------------------------------------------*/
TNODE *createcm(oldptr)
TNODE *oldptr;
{
   TNODE *newptr,*newp,*oldp;

   if (oldptr != NULL) {
      newptr = talloc();     /* allocate a node */
      newptr->nodeid = oldptr->nodeid;    /* copy nodeid to new node */
      newptr->occurind = newptr->copyoi = oldptr->occurind;
      newptr->contreq = newptr->copycontreq = oldptr->contreq;
      newptr->contref_attr = oldptr->contref_attr;

      /* ANDs are treated seperately, must build circular linked list
                     instead of a binary tree.  each element in list is an element
                     of the AND group */
      if (oldptr->nodeid == AND) {
         newp = newptr->u.llptr = createcm(oldptr->u.llptr);
         oldp = oldptr->u.llptr;
         while(oldp->next != oldptr->u.llptr) {
            newp->next = createcm(oldp->next);  
            oldp = oldp->next;
            newp = newp->next;  /* move across both lists */
         }
         newp->next = newptr->u.llptr;   /* finish off circular list */
      }
      else {
         newptr->left = createcm(oldptr->left);   /* create new left pointer */
         newptr->u.right = createcm(oldptr->u.right); /* create new right ptr */
      }
      return(newptr);
   }
   return(NULL);
}

/*------------------------------------------------------*/
/*         C O M P A R E                  */
/*                   */
/*       Called by  :QSORT, BSEARCH                */
/*                   */
/*       Returns    :TRUE, FALSE                   */
/*                   */
/*       String compare to determine if two        */
/*         strings are equal.                      */
/*------------------------------------------------------*/
compare(arg1,arg2)
STENTRY *arg1,*arg2;
{
   return(strcmp((char *)arg1,(char *)arg2));
}

/*------------------------------------------------------*/
/*           D E C R O I                      */
/*      This routine decrements the occurence indic-  */
/* ators.  All that means is that an occurrence    */
/* of this model token has occurred.      */
/*------------------------------------------------------*/
void decroi(ptr)
TNODE *ptr;
{
   switch(ptr->occurind) {
   case '+':          /* one or more   goes to  zero or more  */
   case '*':          /* zero or more  goes to  zero or more  */
      ptr->occurind = '*';
      break;
#ifdef NEW
   case '*':          /* zero or more  goes to  zero or more  */
#endif
   case '?':          /* zero or one  goes to  none  */
   case '1':          /* one          goes to  none  */
      ptr->occurind = '#';
      break;
   default:
      software_fault();
   }
   switch(ptr->contreq) {
   case C_FTOTSO:
      ptr->contreq = ptr->copycontreq = C_SOMETIMESO;
      break;
   case C_FTOWR:
   case C_FTO:
      ptr->contreq = C_NEVERO;
      break;
   case C_SOMETIMESO:
   case C_NEVERO:
   case C_ALWAYSO:
      break;
   default:
      software_fault();
   }
   return;
}

/*------------------------------------------------------*/
/*               D E L E T E _ F I L E S                */
/* Deletes temporary files built by DTD processor. */
/* This routine is called only if the '-F' command */
/* line option is left off.         */
/*------------------------------------------------------*/
void delete_files(path)
char path[];
{
   char filename[PATHLEN];

   sprintf(filename,"%s%s",path,"dtdfile.sgm");
   unlink(filename);
   sprintf(filename,"%s%s",path,"attrfile.sgm");
   unlink(filename);
   sprintf(filename,"%s%s",path,"greffile.sgm");
   unlink(filename);
   sprintf(filename,"%s%s",path,"preffile.sgm");
   unlink(filename);
   sprintf(filename,"%s%s",path,"except.sgm");
   unlink(filename);
   return;
}

/*----------------------------------------------*/
/*         E X A L L O C                 */
/* Allocates enough memory for one        */
/* exception linked list entry.        */
/*----------------------------------------------*/
EXCEPTDESC *exalloc()
{
   /* char *calloc(); */
   EXCEPTDESC *retptr;
   if ((retptr=(EXCEPTPTR) malloc(sizeof(EXCEPTDESC))) == NULL)
      ourexit(2,"\nInsufficient memory in parse3.\n");
   return(retptr);
}

/*------------------------------------------------------*/
/*                 F L U S H _ B U F         */
/*     This routine flushes the output buffer called  */
/*     'outbuf' to the output document called 'outdoc'.  */
/*     Outbuf is assumed to not be null terminated,   */
/*     so a null is placed at the end of the buffer   */
/*     pointed to by bufptr.           */
/*------------------------------------------------------*/
void flush_buf()
{
   if (bufptr != 0) {
      outbuf[bufptr] = '\0';  /* bufptr always points to next entry place */
      (*print_ctr)(ctrfp,"%s",outbuf);
      bufptr = 0;  /* points to end of buffer, nothing in buffer now */
   }
   empty_queue();
   return;
}

/*----------------------------------------------*/
/*      G E A L L O C                 */
/* Allocates enough memory for one entity */
/* description linked list entry.      */
/*----------------------------------------------*/
ENTITYDESC *gealloc()
{
   /* char *calloc(); */
   ENTITYDESC *retptr;
   if ((retptr=(ENTITYPTR) malloc(sizeof(ENTITYDESC))) == NULL)
      ourexit(2,"\nInsufficient memory in parse3.\n");
   return(retptr);
}

/*----------------------------------------------*/
/*      G R A L L O C                 */
/* Allocates enough memory for one group  */
/* description linked list entry.      */
/*----------------------------------------------*/
GROUPDESC *gralloc()
{
   /* char *calloc(); */
   GROUPDESC *retptr;
   if ((retptr=(GROUPPTR) malloc(sizeof(GROUPDESC))) == NULL)
      ourexit(2,"\nInsufficient memory in parse3.\n");
   return(retptr);
}

/*----------------------------------------------*/
/*    I D _ I D R E F _ A L L O C          */
/* Allocates enough memory for one ID or  */
/* IDREF description linked list entry.   */
/*----------------------------------------------*/
ID_IDREF_DESC *id_idref_alloc()
{
   /* char *calloc(); */
   ID_IDREF_DESC *retptr;
   if ((retptr=(ID_IDREF_PTR) malloc(sizeof(ID_IDREF_DESC))) == NULL)
      ourexit(2,"\nInsufficient memory in parse3.\n");
   return(retptr);
}

/*------------------------------------------------------*/
/*                     I N C R O I                      */
/*        Increments the occurence indicators.     */
/*------------------------------------------------------*/
void incroi(ptr)
TNODE *ptr;
{
   switch(ptr->occurind) {
   case '*':
      ptr->occurind = '+';  /* zero or more goes to one or more */
      break;
   case '?':
      ptr->occurind = '1';  /* optional goes to required */
      break;
   default:
      software_fault();
   }
   return;
}

/*------------------------------------------------------*/
/*                   I N P U T _ P S                    */
/* This routine is used to input parameter      */
/* seperators that can be found in the front */
/* part of marked sections.  It gets as many */
/* as possible and returns the number that it   */
/* found.                  */
/*------------------------------------------------------*/
inputps(penthead)
ENTITYDESC *penthead;
{
   int inchar;
   unsigned sepcount = 0;

   for(;;) {
      switch (inchar=our_fgetc(indoc)) {
      case RS:
      case RE:   
      case SPACE:  
         sepcount++;
         break;
      case OUR_EE:
         sepcount++;
         entitylevel--;
         break;
         /* must check if parameter entity reference */
      case PERO:
         if ((inchar = our_fgetc(indoc)) == EOF)
            return(EOF);
         /* if it is a parameter entity reference ...*/
         if (isalpha(inchar)){
            our_ungetc(inchar, indoc);
            if (++entitylevel > ENTLVL)
               ourexit(2,"\nError: Number of open entities > ENTLVL\n");
            reslvpref(penthead);
            sepcount++;
            break;
         }
         else {
            our_ungetc(inchar, indoc);
            our_ungetc(PERO, indoc);
            return(sepcount);
         }
      case '-':
         if ((inchar = our_fgetc(indoc)) == EOF)
            return(EOF);
         if (inchar == '-') {
            inpcomment();
            sepcount++;
            break;
         }
         our_ungetc(inchar, indoc);
         our_ungetc('-', indoc);
         return(sepcount);
      default:
         our_ungetc(inchar, indoc);
         return(sepcount);
      }
   }
}

/*------------------------------------------------------*/
/*                I N P C O M M E N T                   */
/* This routine inputs a comment from indoc. */
/*------------------------------------------------------*/
void inpcomment()
{ 
   int inchar;
   for(;;) {
      if ((inchar=our_fgetc(indoc)) == EOF)
         ourexit(2,"\nError: EOF found while processing comment");
      if (inchar != '-') 
         continue;
      if ((inchar=our_fgetc(indoc)) == EOF)
         ourexit(2,"\nError: EOF found while processing comment");
      if (inchar == '-')
         return;
   }
   return;
}

/*------------------------------------------------------*/
/*         I S A L L O C                  */
/* This routine allocates enough memory for one */
/* input stack entry.                        */
/*------------------------------------------------------*/
INPUT_STACK *isalloc()
{
   /* char *calloc(); */
   INPUT_STACK *retptr;
   if ((retptr=(INPUT_STACK_PTR) malloc(sizeof(INPUT_STACK))) == NULL)
      ourexit(2,"\nInsufficient memory in parse3.\n");
   return(retptr);
}

/*------------------------------------------------------*/
/*              L I N S R C H             */
/*      This routine performs a search on the symbol    */
/*      table for the address of the record correspond- */
/*      ing to the token passed to it.  If there is   */
/*      not an entry in the symbol table for that  */
/*      token, a NULL ptr is returned.       */
/*------------------------------------------------------*/
STENTRY *linsrch(symtable,token,numsym)
STENTRY symtable[];
int token,numsym;
{
   int thissym;

   thissym = 0;
   while(thissym!=numsym && symtable[thissym].tokenid!=token)
      thissym++;
   return(thissym < numsym ? &(symtable[thissym]) : NULL); 
}

/*------------------------------------------------------*/
/*                   O U R E X I T                      */
/*                   */
/*     Called by:     everybody           */
/*                   */
/*     Returns:       doesn't          */
/*                   */
/*     This routine prints an explanatory error    */
/*     message and then terminates program execution.   */
/*------------------------------------------------------*/
void ourexit(errcode,msg)
int errcode;
char *msg;
{
   static int error_count=0;

   fprintf(stdout,"%s\n",msg);
   switch(errcode) {
   case 0:
      break;
   default:
      exit(99);
   }
   if (error_count++ > MAX_ERRORS)
      exit(99);
}

/*------------------------------------------------------*/
/*             P L A C E _ I N _ Q U E U E              */
/*------------------------------------------------------*/
void place_in_queue(thiscode,thisstr1,thisstr2)
int thiscode;
char *thisstr1,*thisstr2;
{
   OUT_QUEUE *ptr,*curr;

   ptr = oqalloc();
   ptr->code = thiscode;
   strcpy(ptr->str1,thisstr1);
   strcpy(ptr->str2,thisstr2);
   ptr->next = NULL;
   if (head == NULL)
      head = ptr;
   else {
      for (curr=head; curr->next!=NULL; curr=curr->next);
      curr->next = ptr;
   }
   return;
}

/*------------------------------------------------------*/
/*                 E M P T Y _ Q U E U E                */
/*------------------------------------------------------*/
void empty_queue()
{
   OUT_QUEUE *curr,*temp;
   for (curr=head; curr!=NULL; temp=curr, curr=curr->next, free((char *)temp)) {
      switch(curr->code) {
      case TAG_NAME:
         (*applic)(TAG_NAME,curr->str1,"");
         break;
      case TAG_ATTR:
         (*applic)(TAG_ATTR,curr->str1,curr->str2);
         break;
      case TAG_END:
         (*applic)(TAG_END,"","");
         break;
      case DATA_STG:
         (*applic)(DATA_STG,curr->str1,"");
         break;
      case PROC_INST:
         (*applic)(PROC_INST,curr->str1,"");
         break;
      case END_TAG_NAME:
         (*applic)(END_TAG_NAME,curr->str1,"");
         break;
      }
   }
   head = NULL;
   return;
}

/*------------------------------------------------------*/
/*                C L E A R _ Q U E U E                 */
/*------------------------------------------------------*/
void clear_queue()
{
   OUT_QUEUE *curr,*temp;
   for (curr=head; curr!=NULL; temp=curr, curr=curr->next, free((char *)temp));
   head = NULL;
   return;
}

/*------------------------------------------------------*/
/*       P U S H                          */
/*                                                 */
/*         Called by  :PUSHCREATE, LOOKSTACK       */
/*                                                 */
/*         Returns    :VOID                        */
/*                                                 */
/*         Pushes a symbol table entry             */
/*           pointer onto a push-down stack.       */
/*------------------------------------------------------*/
void push(ptr)
STENTRY *ptr;
{
   if (sp < TAGLVL)
      stack[sp++]=ptr;    /* push ptr on stack */
   else             /* stack exceeds TAGLVL (24) */
      ourexit(2,"\nError: Number of open elements > TAGLVL.\n");
   return;
}

/*------------------------------------------------------*/
/*       P O P             */
/*          Called by  :POPFREE, LOOKSTACK              */
/*          Returns    :pointer to symbol table entry   */
/*                                                */
/*          Pops a symbol table entry pointer         */
/*        off of a push-down stack               */
/*------------------------------------------------------*/
STENTRY *pop()
{
   if (sp > 0)
      return(stack[--sp]);    /* pop ptr off stack */
   else
      return(NULL);
}

/*------------------------------------------------------*/
/*         P O P F R E E                     */
/*      This routine pops a ptr from content model ptr  */
/* stack and deletes corresponding content model.  */
/*------------------------------------------------------*/
STENTRY *popfree(ptr)
TNODE *ptr;
{
   STENTRY *retptr;
   EXCEPTDESC *p;

   retptr = pop();
   retptr->num_open--;

   if (currexcl!=NULL && retptr->miniexcept&EXCL_MASK && retptr->num_open==0) {
      if (currexcl == retptr->exclusion)
         currexcl = NULL;
      else {
         p = last_sublist(currexcl,retptr->exclusion);  /* P can't be NULL */
         p->nextglobal = NULL;
      }
      retptr->miniexcept &= ~EXCL_MASK;  /* disallow exclusions */
   }

   if (currincl!=NULL && retptr->miniexcept&INCL_MASK && retptr->num_open==0) {
      if (currincl == retptr->inclusion)
         currincl = NULL;
      else {
         p = last_sublist(currincl,retptr->inclusion); /* P can't be NULL */
         p->nextglobal = NULL;
      }
      retptr->miniexcept &= ~INCL_MASK;  /* disallow inclusions */
   }
   delete(ptr);
   return(retptr);
}

/*------------------------------------------------------*/
/*                 P U S H C R E A T E                */
/*                   */
/*      Returns:    ptr to new content model    */
/*                   */
/*      pushes ptr on stack and creates a          */
/*      content model from the virgin copy         */
/*------------------------------------------------------*/
TNODE *pushcreate(tp)
STENTRY *tp;
{
   EXCEPTDESC *p;

   tp->num_open++;   /* number of currently open elements for this one */

   if (!(tp->miniexcept & EXCL_MASK))
      if ((p=last_global(currexcl)) == NULL)
         currexcl = tp->exclusion;
      else
         p->nextglobal = tp->exclusion;
   if (tp->exclusion != NULL)
      tp->miniexcept |= EXCL_MASK;

   if (!(tp->miniexcept & INCL_MASK))
      if ((p=last_global(currincl)) == NULL)
         currincl = tp->inclusion;
      else
         p->nextglobal = tp->inclusion;
   if (tp->inclusion != NULL)
      tp->miniexcept |= INCL_MASK;

   if (find_except(currexcl,tp->tokenid) == TRUE) {
      sprintf(error_msg,"\n%s%s%s\n","Error: Invalid tokenid, ",tp->nametoken," is excluded.");
      FATAL_ERROR()
   }
   push(tp);
   return(createcm(tp->cmptr));
}

/*------------------------------------------------------*/
/*            P U T C H A R _ O U T B U F               */
/*        Puts a character into the output buffer.      */
/*------------------------------------------------------*/
void putchar_outbuf(inchar)
int inchar;
{
   outbuf[bufptr++] = inchar;
   if (bufptr >= TAGLEN+200)
      flush_buf();
   return;
}

/*------------------------------------------------------*/
/*              P U T S T R _ O U T B U F               */
/*     Puts a character string into the output buffer   */
/*------------------------------------------------------*/
void putstr_outbuf(string)
char string[];
{
   if (bufptr+strlen(string) >= TAGLEN+200)
      flush_buf();
   if (strlen(string) >= TAGLEN+200)
      (*print_ctr)(ctrfp,"%s",string);
   outbuf[bufptr] = '\0';
   strcat(outbuf,string);
   bufptr += strlen(string); /* don't need the null string yet */
   return;
}

/*------------------------------------------------------*/
/*                   R E D U C E O I                    */
/*      This routine reduces the occurence indicators.  */
/* This is done in the initilization part to make  */
/* sub-models optional if their parent nodes are   */
/* optional.               */
/*------------------------------------------------------*/
void reduceoi(ptr)
TNODE *ptr;
{
   switch(ptr->occurind) {
   case '+':
      ptr->copyoi = ptr->occurind = '*';
      break;
   case '1':
      ptr->copyoi = ptr->occurind = '?';
      break;
   default:
      software_fault();
   }
   return;
}

/*------------------------------------------------------*/
/*                 R E S T O R E O I                    */
/*     This routine restores the occurance indicators   */
/*     of the whole tree to their original values       */
/*------------------------------------------------------*/
void restoreoi(ptr)
TNODE *ptr;
{
   TNODE *currp;

   if (ptr != NULL) {
      if (ptr->nodeid == AND) {
         currp = ptr->u.llptr;
         while(currp->next != ptr->u.llptr) {  /* restore each one in list */
            restoreoi(currp);
            currp = currp->next;
         }
         restoreoi(currp);  /* don't forget to restore last one  */
      }
      else {
         restoreoi(ptr->left);    /* restore left subtree */
         restoreoi(ptr->u.right); /* restore right subtree */
      }
      ptr->occurind = ptr->copyoi;  /* restores oi from unused copy */
      switch(ptr->contreq) {
      case C_NEVERO:
         if (ptr->copycontreq != C_FTO)
            ptr->contreq = ptr->copycontreq;
         break;
      case C_SOMETIMESO:
         if (ptr->copycontreq != C_FTOTSO)
            ptr->contreq = ptr->copycontreq;
         break;
      default:
         ptr->contreq = ptr->copycontreq;
         break;
      }
   }
   return;
}

/*------------------------------------------------------*/
/*    R E Q _ N O T _ P R O C       */
/* Traverses through the attribute list to decide  */
/* if any attributes were REQUIRED but not pro- *
/* cessed.  FALSE is returned if none were found   */
/* and TRUE is returned if at least one was found. */
/*------------------------------------------------------*/
req_not_proc(thisadp)
ATTRDESC *thisadp;
{
   BOOLEAN retval;
   retval = FALSE;
   while(retval==FALSE && thisadp!=NULL)
      if ((thisadp->processed==FALSE) && ((thisadp->defcode==A_REQD) ||
          (thisadp->defcode==A_CURRENT && thisadp->u2.currgrp==NULL)))
         retval = TRUE;
      else
         thisadp = thisadp->next;
   return(retval);
}

/*------------------------------------------------------*/
/*        R E S L V P R E F         */
/* This routine resolves any parameter entity refer-    */
/* ences at a separator level. It inputs the entity     */
/* name, searches a table on the name getting its  */
/* entity text, and 'ungets' the text for further  */
/* processing.                                        */
/*------------------------------------------------------*/
void reslvpref(penthead)
ENTITYDESC *penthead;
{
   char namearray[NAMELEN+1];
   char tarray[LITLEN+6];
   int inchar;
   ENTITYDESC *entptr;

   /* input the parameter entity name */
   if (get_entname(namearray,nullfnc) < NAMELEN-1) {

      if ((entptr=find_entity(penthead,namearray,FALSE)) == NULL) {
         sprintf(error_msg,"\n%s'%s'.\n","Error: Unknown parameter entity reference ",namearray);
         FATAL_ERROR()
      }
      switch(entptr->entitytype){
      case KW_MD:
         sprintf(tarray,"%s%s%s","<!",entptr->entityvalue,">");
         if ((inchar=our_fgetc(indoc)) != EOF)
            ourexit(2,"\nError: EOF found while resolving parameter entity reference.\n");
         if (inchar != REFC)
            if (inchar == RE)   /* RS and RE are insignificant */
               inchar = our_fgetc(indoc);  /* get RS */
            else
               our_ungetc(inchar, indoc);
         unget_entity(tarray);
         break;
      case KW_ENDTAG:
         sprintf(tarray,"%s%s%s","</",entptr->entityvalue,">");
         if ((inchar=our_fgetc(indoc)) != EOF)
            ourexit(2,"\nError: EOF found while resolving parameter entity reference.\n");
         if (inchar != REFC)
            if (inchar == RE)   /* RS and RE are insignificant */
               inchar = our_fgetc(indoc);  /* get RS */
            else
               our_ungetc(inchar, indoc);
         unget_entity(tarray);
         break;
      case KW_STARTTAG:
         sprintf(tarray,"%s%s%s","<",entptr->entityvalue,">");
         if ((inchar=our_fgetc(indoc)) != EOF)
            ourexit(2,"\nError: EOF found while resolving parameter entity reference.\n");
         if (inchar != REFC)
            if (inchar == RE)   /* RS and RE are insignificant */
               inchar = our_fgetc(indoc);  /* get RS */
            else
               our_ungetc(inchar, indoc);
         unget_entity(tarray);
         break;
      case KW_MS:
         sprintf(tarray,"%s%s%s","<![",entptr->entityvalue,"]]>");
         if ((inchar=our_fgetc(indoc)) == EOF)
            ourexit(2,"\nError: EOF found while resolving parameter entity reference.\n");
         if (inchar != REFC)
            if (inchar == RE)   /* RS and RE are insignificant */
               inchar = our_fgetc(indoc);  /* get RS */
            else
               our_ungetc(inchar, indoc);
         unget_entity(tarray);
         break;
      case NULL:
         if ((inchar=our_fgetc(indoc)) == EOF)
            ourexit(2,"\nError: EOF found while resolving parameter entity reference");
         if (inchar != REFC)
            if (inchar == RE)   /* RS and RE are insignificant */
               inchar = our_fgetc(indoc);  /* get RS */
            else
               our_ungetc(inchar, indoc);
         /* unget parameter literal */
         unget_entity(entptr->entityvalue);
         break;
         /* any other syntactic literal is illegal */
      default:
         ourexit(2,"\nError: Illegal use of syntactic literal in a PS entity reference.\n");
         break;
      }
   }
   else
      ourexit(2,"\nError: Reference name not found in PS entity reference.\n");
   return;
}

/*------------------------------------------------------*/
/*                  S A V E _ C R S                     */
/* During the processing of data, carriage returns */
/* are read and saved.  They may be output later   */
/* if it is found that are significant.      */
/*------------------------------------------------------*/
BOOLEAN save_crs(num_cr,inchar)
unsigned *num_cr;
int *inchar;
{
   BOOLEAN cr_found;
   for (cr_found = FALSE; *inchar == RE; (*num_cr)++) {
      cr_found = TRUE;
      if ((*inchar=our_fgetc(indoc)) == RS)  /* should be a RS */
         *inchar = our_fgetc(indoc);
   }
   return(cr_found);
}

/*------------------------------------------------------*/
/*    S O F T W A R E _ F A U L T      */
/* This routine should NEVER be called.  If it  */
/* is called, the parser has taken an unpredicted  */
/* path of execution and the program is halted. */
/*------------------------------------------------------*/
void software_fault()
{
   fprintf(stderr,"Internal parser error.  Contact Jim Heath of NBS @ (301) 975-3350.\n");
   exit(99);
}

/*----------------------------------------------*/
/*      T A L L O C                   */
/*                                              */
/*     Called by   :BUILDTREE                   */
/*                                              */
/*     Returns     :ptr to allocated node       */
/*                                              */
/*     Allocates memory for one tree node.      */
/*----------------------------------------------*/
TNODE *talloc()
{
   /* char *calloc(); */
   TNODE *retptr;
   if ((retptr=(TPTR) malloc(sizeof(TNODE))) == NULL)
      ourexit(2,"\nInsufficient memory in parse3.\n");
   return(retptr);
}

/*------------------------------------------------------*/
/*                    T E S T O I                       */
/*     This routine tests to see if occurance indicator */
/*     is mandatory or optional.                   */
/*------------------------------------------------------*/
OCCURIND testoi(ptr)
TNODE *ptr;
{
   OCCURIND retval;

   switch(ptr->occurind) {
   case '1':         /* occurind is one */
      retval = ONE;
      break;
   case '+':         /* occurind is one or more */
      retval = PLUS;
      break;
   case '?':         /* occurind is zero or one */
   case '*':         /* occurind is zero or more */
      retval = OPT;
      break;
   case '#':         /* occurind is none */
      retval = OI_IS_NULL;
      break;
   default:
      software_fault();
   }
   return(retval);
}

/*------------------------------------------------------*/
/*                U N G E T _ E N T I T Y               */
/* This routine simply 'ungets' the given entity   */
/* string onto the input stack.  First the SGML */
/* 'Ee' (Entity end) signal is pushed on the */
/* stack, then the string is pushed in reverse  */
/* order by unget_string.           */
/*------------------------------------------------------*/
void unget_entity(string)
char string[];
{
   our_ungetc(OUR_EE,indoc);
   unget_string(string);
   return;
}

/*------------------------------------------------------*/
/*                U N G E T _ S T R I N G               */
/* This routine simply 'ungets' the given string   */
/* onto the input stack.   Remember that the    */
/*      string must be processed in reverse order. */
/*------------------------------------------------------*/
void unget_string(string)
char string[];
{
   register int i;
   for (i=strlen(string)-1; i>=0; i--)
      our_ungetc(string[i],indoc);
   return;
}

/*------------------------------------------------------*/
/*          U N G E T T O K E N                  */
/*  Places the given token (tag) back onto the     */
/*       input stream.                        */
/*------------------------------------------------------*/
void ungettoken(token,tp)
int token;
STENTRY *tp;
{
   state = GETOLD;     /* set state to read from input stream */
   holdtoken = token;
   holdtp = tp;        /* get table location from hold area */
   return;
}

/*------------------------------------------------------*/
/*         U N P R O C E S S        */
/* Traverses through the attribute list and  */
/* turns off the flag describing whether this   */
/* name has been processed in this tag.      */
/*------------------------------------------------------*/
void unprocess(thisadp)
ATTRDESC *thisadp;
{
   while(thisadp != NULL) {
      thisadp->processed = FALSE;
      thisadp = thisadp->next;
   }
   return;
}

/*--------------------------------------------------------------*/
/*                 C H E C K _ F O R _ M D O                   */
/* This routine is called when the "<!" has already   */
/* been read while processing PCDATA.        */
/*--------------------------------------------------------------*/
STATUS check_for_mdo(more_subdata,num_cr,cr_found,pcdata_ft)
BOOLEAN *more_subdata;
unsigned *num_cr;
BOOLEAN cr_found,*pcdata_ft;
{
   int inchar;
   STATUS retval;

   if ((inchar=our_fgetc(indoc)) == '-')
      if ((inchar=our_fgetc(indoc)) == '-') {
         unget_string("<!--");
         *more_subdata = FALSE;
      }
      else {
         retval = FOUND;
         check_cr(num_cr,cr_found,pcdata_ft,FALSE);
         (*print_ctr)(ctrfp,"<!-");
         (*applic)(DATA_STG,"<!-","");
         our_ungetc(inchar,indoc);
      }
   else
      if (isalpha(inchar) || inchar=='[' || inchar==MARKUP_END) {
         our_ungetc(inchar,indoc);
         unget_string("<!");   
         *more_subdata = FALSE;
      }
      else {
         retval = FOUND;
         check_cr(num_cr,cr_found,pcdata_ft,FALSE);
         (*print_ctr)(ctrfp,"<!");
         (*applic)(DATA_STG,"<!","");
         our_ungetc(inchar,indoc);
      }
   return(retval);
}

/*------------------------------------------------------*/
/*    C R O S S _ I D _ I D R E F      */
/* Performs a cross reference evaluation for IDs   */
/* and IDREFs to make sure there is an ID found */
/* for every IDREF.  There is no requirement that  */
/* states an IDREF must be found for every ID.  */
/*------------------------------------------------------*/
ID_IDREF_DESC *cross_id_idref(idptr,idrefptr)
ID_IDREF_DESC *idptr,*idrefptr;
{
   while(idrefptr!=NULL && find_id(idptr,idrefptr->name)==TRUE)
      idrefptr = idrefptr->next;
   return(idrefptr);
}

/*------------------------------------------------------*/
/*       D E L E T E                     */
/*                                               */
/*     Called by  :DELETE,POPFREE                  */
/*                                                 */
/*     Returns    :VOID                            */
/*                                               */
/*     Deletes the binary tree pointed to by ptr   */
/*------------------------------------------------------*/
void delete(ptr)
TNODE *ptr;
{
   TNODE *currp,*futp;
   if (ptr != NULL) {
      if (ptr->nodeid == AND) {
         currp = ptr->u.llptr;
         while(currp->next != ptr->u.llptr) {
            futp = currp->next;
            /* must save a ptr to node to be processed next, futp, 
                                              because will 'delete' ptr to it now */
            delete(currp);
            currp = futp;
         }
         delete(currp);  /* don't forget last one in list */
      }
      else {
         delete(ptr->left);    /* delete left subtree */
         delete(ptr->u.right);   /* delete right subtree */
         free((char *)ptr);            /* unallocate memory location */
      }
   }
   return;
}

/*------------------------------------------------------*/
/*         F I N D _ A T T R        */
/* Searches through the attribute list for the  */
/* given name and returns the pointer to list   */
/* entry if found, else returns NULL.     */
/*------------------------------------------------------*/
ATTRDESC *find_attr(thisattr,thisadp)
char thisattr[];
ATTRDESC *thisadp;
{
   while((thisadp!=NULL) && (strcmp(thisattr,thisadp->attrname)!=0))
      thisadp = thisadp->next;
   return(thisadp);
}

/*------------------------------------------------------*/
/*       F I N D _ G R O U P        */
/* Searches through the group list for the   */
/* given name and returns the pointer to list   */
/* entry if found, else returns NULL.     */
/*------------------------------------------------------*/
GROUPDESC *find_group(thisgroup,thisgrp)
char thisgroup[];
GROUPDESC *thisgrp;
{
   while((thisgrp!=NULL) && (strcmp(thisgroup,thisgrp->groupname)!=0))
      thisgrp = thisgrp->next;
   return(thisgrp);
}

/*------------------------------------------------------*/
/*          F I N D _ I D        */
/* Searches through the ID list for the given   */
/* name and returns the pointer to the list  */
/* entry if the name is found, else returns NULL.  */
/*------------------------------------------------------*/
BOOLEAN find_id(idptr,thisname)
ID_IDREF_DESC *idptr;
char thisname[];
{
   while(idptr!=NULL && strcmp(idptr->name,thisname)!=0)
      idptr = idptr->next;
   return(idptr!=NULL ? TRUE : FALSE);
}

/*------------------------------------------------------*/
/*           F I N D _ E N T I T Y        */
/* Searches through the ENTITY list for the given  */
/* name and returns the pointer to the list  */
/* entry if the name is found, else returns NULL.  */
/* If there is a #DEFAULT entry and a direct       */
/* match is not found, the ptr to the first        */
/* #DEFAULT entry is returned.                     */
/*------------------------------------------------------*/
ENTITYDESC *find_entity(entptr,thisname,look_for_default)
ENTITYDESC *entptr;
char thisname[];
BOOLEAN look_for_default;
{
   ENTITYDESC *first_def;

   first_def = NULL;  /* points to first #DEFAULT entry in list */
   while(entptr!=NULL && strcmp(entptr->entityname,thisname)!=0) {
      if (first_def==NULL && look_for_default && strcmp(entptr->entityname,"#DEFAULT")==0)
         first_def = entptr;
      entptr = entptr->next;
   }
   return(entptr==NULL ? first_def : entptr);
}

/*------------------------------------------------------*/
/*                F I N D _ E X C E P T                 */
/* This routine searches the exception list  */
/* (inclusion or exclusion) for the identifier  */
/* specified in the parameter list.    */
/*------------------------------------------------------*/
BOOLEAN find_except(exptr,thisid)
EXCEPTDESC *exptr;
int thisid;
{
   while(exptr!=NULL && exptr->tokenid!=thisid)
      exptr = exptr->nextglobal;
   return(exptr!=NULL ? TRUE : FALSE);
}

/*------------------------------------------------------*/
/*          I N S E R T       */
/* This routine inserts IDs or IDREFs at the end   */
/* of their appropriate seperate singly linked  */
/* lists.  A flag is passed to indicate which   */
/* list the name is to be added to.  If the name   */
/* is already present in the list, an error is  */
/* raised.                 */
/*------------------------------------------------------*/
void insert(head,thisname,declvalcode)
ID_IDREF_DESC **head;
char thisname[];
DECLVAL declvalcode;
{
   ID_IDREF_DESC *thisptr,*prevptr;

   thisptr = *head;
   while(thisptr!=NULL && strcmp(thisptr->name,thisname)!=0) {
      prevptr = thisptr;
      thisptr = thisptr->next;
   }
   if (thisptr==NULL && strcmp(prevptr->name,thisname)!=0) {
      if (*head == NULL)
         thisptr = *head = id_idref_alloc();
      else 
          thisptr = prevptr->next = id_idref_alloc();
      strcpy(thisptr->name,thisname);
      thisptr->next = NULL;
   }
   else
      if (declvalcode == ID) {
         sprintf(error_msg,"%s%s%s","\nError: Duplicate ID attribute values of '",thisname,"'.\n");
         FATAL_ERROR()
      }
   return;
}

/*------------------------------------------------------*/
/*               L A S T _ S U B L I S T                */
/* This routine is used to find the end of the  */
/*      sub-exception list.  That is, the list of  */
/*      elements declared as an exception for that */
/*      particular generic identifier.       */
/*------------------------------------------------------*/
EXCEPTDESC *last_sublist(thisglob,thisloc)
EXCEPTDESC *thisglob,*thisloc;
{
   if (thisglob==NULL || thisloc==NULL)
      software_fault();

   while(thisglob->nextglobal!=thisloc && thisglob->nextglobal!=NULL)
      thisglob = thisglob->nextglobal;
   return(thisglob);
}

/*------------------------------------------------------*/
/*               L A S T _ G L O B A L                  */
/* This routine is used to find the end of the  */
/*      entire exception list.  That is, the current  */
/*      list of elements active as an exception.   */
/*------------------------------------------------------*/
EXCEPTDESC *last_global(ptr)
EXCEPTDESC *ptr;
{
   if (ptr != NULL)
      while(ptr->nextglobal != NULL)
         ptr = ptr->nextglobal;
   return(ptr);
}

/*------------------------------------------------------*/
/*                L O O K S T A C K            */
/*                   */
/*        Returns    :top value on the stack            */
/*                                               */
/*        Returns the top value of the stack          */
/*        but does not pop it off.                    */
/*------------------------------------------------------*/
STENTRY *lookstack()
{
   STENTRY *ptr;
   if ((ptr=pop()) != NULL)    /*  pop value  */
      push(ptr);      /*  push it back on the stack  */
   return(ptr);
}

/*------------------------------------------------------*/
/*                    N U L L F N C                     */
/* This routine doesn't do much of anything. */
/* It is called by function prototypes to get   */
/* around calling certain routines.  For example,  */
/* we don't want to call 'our_fputc' when the   */
/* outdoc is not to be built.       */
/*------------------------------------------------------*/
nullfnc(value)
int value;
{
   return(value);
}

/*------------------------------------------------------*/
/*                  O U R _ F G E T C                   */
/*      This routine is similiar to K&R's standard */
/*      fgetc.  However, It does first read from our  */
/*      input stack if something exists on it.     */
/*      Otherwise, it does it's own fgetc.      */
/*------------------------------------------------------*/
our_fgetc(ind)
FILE *ind;
{
   register int inchar,retval;
   INPUT_STACK *delptr;

   /*  We are allowing for future revisions that might require the input
       for the document element parser to come from more than one source.  */

   if (stptr == NULL) {
      while ((retval=((inchar=fgetc(indoc))==CTRLZ ? EOF:inchar)) == EOF && --fpindx>=0) {
         fclose(fpstack[fpindx+1]);
         indoc = fpstack[fpindx];
         entitylevel--;
      }
      putchar(retval);
   }
   else {
      retval = stptr->inchar;
      delptr = stptr;
      stptr = stptr->next;
      free((char *)delptr);
   }
   return(retval);
}

/*------------------------------------------------------*/
/*                O U R _ U N G E T C                   */
/*      This routine is similiar to K&R's standard */
/*      ungetc.  However, It creates and uses its     */
/*      input stack and places the ungot character    */
/*      on it.                                  */
/*------------------------------------------------------*/
void our_ungetc(c,indoc)
int c;
FILE *indoc;
{
   INPUT_STACK *newptr;

   newptr = isalloc();
   newptr->inchar = c;
   newptr->next = stptr;
   stptr = newptr;
   return;
}

/*------------------------------------------------------*/
/*              O U R _ T O U P P E R                   */
/*    We need toupper implemented as a function.        */
/*------------------------------------------------------*/
our_toupper(value)
int value;
{
   if (isascii(value) && isalpha(value) && islower(value))
      return(toupper(value) & 0xFF);
   return(value & 0xFF);
}

/*------------------------------------------------------*/
/*                P R O C E S S _ A T T R             */
/*     This routine is a pre-processor for exe_attr     */
/*     that reads from 'indoc' and resolves all    */
/*     references, possibly deleting leading and   */
/*     trailing spaces, then placing the resulting */
/*     string in 'buffer'.          */
/*------------------------------------------------------*/
process_attr(buffer,delim,genthead,dvcode,num_csdata)
char buffer[];
int delim;
ENTITYDESC *genthead;
DECLVAL dvcode;
unsigned *num_csdata;
{
   int inchar,indx,charnum,actual_length,inter_length;
   char entname[NAMELEN+1],number[NAMELEN+1];
   ENTITYDESC *entptr;
   BOOLEAN datatext_read;

   if (dvcode != ENUM_CDATA)
      actual_length = gettilnosep();

   datatext_read = FALSE;
   actual_length = indx = *num_csdata = 0;

   while((inchar=our_fgetc(indoc))!=delim || entitylevel>0)
      if (inchar==ERO && !datatext_read)
         if (isalpha(inchar=our_fgetc(indoc))) {
            our_ungetc(inchar,indoc);
            inter_length = get_entname(entname,nullfnc);
            if (entitylevel == 0)
               actual_length += inter_length+1;  /* includes the '&' */

            if ((entptr=find_entity(genthead,entname,TRUE)) == NULL) {
               sprintf(error_msg,"%s%s%s","\nError: Unknown general entity name '",entname,"'.\n");
               FATAL_ERROR()
            }
            else
               check_entity(dvcode==ENUM_CDATA,num_csdata,entptr,&datatext_read,entname,&actual_length,TRUE,&inchar);
         }
         else
            if (inchar == '#')
               if (isalpha(inchar=our_fgetc(indoc))) {
                  our_ungetc(inchar,indoc);
                  inter_length = get_entname(entname,our_toupper);
                  if (entitylevel == 0)
                     actual_length += inter_length+2;
                  if (strcmp(entname,"TAB") == 0)
                     buffer[indx++] = SEPCHAR;
                  else
                     if (strcmp(entname,"RS") == 0)
                        buffer[indx++] = RS;
                     else
                        if (strcmp(entname,"RE") == 0)
                           buffer[indx++] = RE;
                        else
                           if (strcmp(entname,"SPACE") == 0)
                              buffer[indx++] = SPACE;
                           else {
                              sprintf(error_msg,"%s%s%s","\nError: Unknown character reference '",entname,"'.\n");
                              FATAL_ERROR()
                           }
                  if ((inchar=our_fgetc(indoc)) != REFC)
                     our_ungetc(inchar,indoc);
                  else
                     actual_length++;
               }
               else
                  if (isdigit(inchar)) {
                     our_ungetc(inchar,indoc);
                     inter_length = get_number(number,nullfnc);
                     if (entitylevel == 0)
                        actual_length += inter_length+2;
                     CLEAR_BUF();  /* don't want number printed out */

                     charnum = atoi(number);
                     if ((charnum<CHARSET_LOWBOUND) || (charnum>CHARSET_HIGHBOUND))
                        ourexit(2,"\nError: Invalid character reference number\n");
                     else
                        buffer[indx++] = charnum;
                     if ((inchar=our_fgetc(indoc)) != REFC)
                        our_ungetc(inchar,indoc);
                     else
                        actual_length++;
                  }
                  else {
                     buffer[indx++] = ERO;
                     buffer[indx++] = '#';
                     buffer[indx++] = inchar;
                  }
            else {
               buffer[indx++] = ERO;
               buffer[indx++] = inchar;
            }
      else {
         if (entitylevel == 0)
            actual_length++;
         switch(inchar) {
         case RS:
            break;
         case OUR_EE:
            entitylevel--;
            datatext_read = FALSE;
            break;
         case RE:  
         case SEPCHAR:
            buffer[indx++] = SPACE;
            break;
         default:
            buffer[indx++] = inchar;
            break;
         }
      }
   our_ungetc(inchar,indoc);
   if (dvcode != ENUM_CDATA) {  /* will strip trailing seps from    */
                                /* 'buffer' by decrementing 'indx'. */
      while(SEPERATOR(buffer[indx]))
         indx--;
      indx++;
   }
   buffer[indx] = '\0';
   return(actual_length);
}

/*------------------------------------------------------*/
/*                 T R Y _ E N T R E F                  */
/* This routine process general entity references, */
/* named character references, and numeric   char- */
/* acter references if they exist.        */
/*------------------------------------------------------*/
void try_entref(inchar,genthead,save_char,try_ft)
int inchar;
ENTITYDESC *genthead;
BOOLEAN save_char,*try_ft;
{
   register int charnum;
   ENTITYDESC *entptr;
   char entname[NAMELEN+1],
        number[NAMELEN+1],
        *outstr;
   static BOOLEAN datatext_read=FALSE;

   *try_ft = FALSE;
   outstr = get_char_mem(2);
   number[NAMELEN+1] = '\0';
   if (inchar==ERO && !datatext_read)
      /*  Found a general entity reference */
      if (isalpha(inchar=our_fgetc(indoc))) {
         our_ungetc(inchar,indoc);
         get_entname(entname,nullfnc);
         if ((entptr=find_entity(genthead,entname,TRUE)) == NULL) {
            sprintf(error_msg,"%s%s%s","\nError: Unknown general entity name '",entname,"'.\n");
            FATAL_ERROR()
         }
         else
            check_entity(TRUE,(unsigned int *) 0,entptr,&datatext_read,
                                            entname,(int *) 0,FALSE,&inchar);
      }
      else
         if (inchar == '#')
            /* Found a named character reference */
            if (isalpha(inchar=our_fgetc(indoc))) {
               our_ungetc(inchar,indoc);
               get_entname(entname,our_toupper);
               if (strcmp(entname,"TAB") == 0) {
                  (*put_ctr)(SEPCHAR,ctrfp);
                  *outstr = SEPCHAR;
                  (*applic)(DATA_STG,outstr,"");
               }
               else
                  if (strcmp(entname,"RS") == 0) {
                     (*put_ctr)(RS,ctrfp);
                     *outstr = RS;
                     (*applic)(DATA_STG,outstr,"");
                  }
                  else
                     if (strcmp(entname,"RE") == 0) {
                        (*put_ctr)(RE,ctrfp);
                        *outstr = RE;
                        (*applic)(DATA_STG,outstr,"");
                     }
                     else
                        if (strcmp(entname,"SPACE") == 0) {
                           (*put_ctr)(SPACE,ctrfp);
                           *outstr = SPACE;
                           (*applic)(DATA_STG,outstr,"");
                        }
                        else {
                           sprintf(error_msg,"%s%s%s","\nError: Invalid named character reference '",entname,"'.\n");
                           FATAL_ERROR()
                        }

               if ((inchar=our_fgetc(indoc)) != REFC)
                  if (inchar == RE)  /* RS and RE are insignificant */
                     inchar = our_fgetc(indoc);  /* get RS */
                  else
                     our_ungetc(inchar,indoc);

            }
            else
               /* Found a numeric character reference */
               if (isdigit(inchar)) {
                  our_ungetc(inchar,indoc);
                  get_number(number,nullfnc);
                  CLEAR_BUF();  /* don't want number printed out */
                  charnum = atoi(number);
                  if ((charnum<CHARSET_LOWBOUND) || (charnum>CHARSET_HIGHBOUND)) {
                     sprintf(error_msg,"%s%s%s","\nError: Invalid numeric character reference '",number,"'.\n");
                     FATAL_ERROR()
                  }
                  else
                     if (!SGMLCHAR(charnum)) {
                        (*print_ctr)(ctrfp,"\n[#%d]",charnum);
                        *try_ft = TRUE;
                     }
                     else
                        (*put_ctr)(charnum,ctrfp);
                  *outstr = charnum;
                  (*applic)(DATA_STG,outstr,"");
                  if ((inchar=our_fgetc(indoc)) != REFC) {
                     if (inchar == RE)  /* RS and RE are insignificant */
                        inchar = our_fgetc(indoc);  /* get RS */
                     else
                        our_ungetc(inchar,indoc);
                  }
               }
               else {
                  our_ungetc(inchar,indoc);
                  (*print_ctr)(ctrfp,"%c#",ERO);
                  *outstr = ERO;
                  (*applic)(DATA_STG,outstr,""); 
                  (*applic)(DATA_STG,"#","");
               }
         else {
            our_ungetc(inchar,indoc);
            (*print_ctr)(ctrfp,"%c",ERO);
            *outstr = ERO;
            (*applic)(DATA_STG,outstr,"");
         }
   else
      if (inchar == OUR_EE) {
         datatext_read = FALSE;
         entitylevel--;
      }
      else
         if (save_char)
            our_ungetc(inchar,indoc);
         else
            if (inchar != EOF) {
               (*put_ctr)(inchar,ctrfp);
               *outstr = inchar;
               (*applic)(DATA_STG,outstr,"");
            }
   free(outstr);
   return;
}

/*------------------------------------------------------*/
/*                R E S O L V E _ A T T R               */
/* This routine resolves the defaults that were */
/* not explicitly specified in the attribute */
/* specifications.  The defaults are outputted  */
/* to 'outdoc' and IDs and IDREFs are added to  */
/* their appropriate linked lists.        */
/*------------------------------------------------------*/
resolve_attr(headadp,print_to_file)
ATTRDESC *headadp;
int print_to_file;
{
   char outvalue[LITLEN+1];  /* allow for null terms */
   char buffer[NAMELEN+1];
   register int i,j;
   unsigned num_id_idref;
   BOOLEAN more_def_desc;
   ATTRDESC *thisadp;

   num_id_idref = 0;
   for (thisadp=headadp; thisadp!=NULL; thisadp=thisadp->next) {
      if (print_to_file == FALSE)
         putstr_outbuf("\n ");
      else
         (*print_ctr)(ctrfp,"\n ");
      if ((thisadp->defcode==A_IMPLIED||thisadp->defcode==A_CONREF) &&
          thisadp->processed==FALSE) {
         if (print_to_file == FALSE) {
            putchar_outbuf(' '); 
            putstr_outbuf(thisadp->attrname);
            putstr_outbuf("=#IMPLIED");
            place_in_queue(TAG_ATTR,thisadp->attrname,"");
         }
         else {
            if (thisadp->dvcode==ID||thisadp->dvcode==IDREF||thisadp->dvcode==IDREFS)
               num_id_idref++;
            (*print_ctr)(ctrfp," %s=#IMPLIED",thisadp->attrname);
            (*applic)(TAG_ATTR,thisadp->attrname,"");
         }
      }
      else {
         if (thisadp->dvcode==GROUP || thisadp->dvcode==NOTATION)
            strcpy(outvalue,thisadp->u2.currgrp->groupname);
         else        
             strcpy(outvalue,thisadp->u2.currdef);
         switch(thisadp->defcode) {
         case A_CURRENT:   /* if current and IDREF, already in list */
            if (thisadp->dvcode == ID) {
               sprintf(error_msg,"%s%s'.\n","\nError: ID values not unique after default resolution '",thisadp->attrname);
               FATAL_ERROR()
            }
            if (thisadp->dvcode==IDREF || thisadp->dvcode==IDREFS)
               num_id_idref++;
            break;
         case A_UNFIXED:  
         case A_FIXED:
         case A_REQD:     
         case A_IMPLIED: 
         case A_CONREF:
            if (thisadp->dvcode==ID || thisadp->dvcode==IDREF) {
               num_id_idref++;
#ifdef OLD
               memset(buffer,'\0',NAMELEN+1);
#else
               memset(buffer,'\0',sizeof(buffer));
#endif
               for (i=0; i<strlen(outvalue) && i<NAMELEN; i++)
                  buffer[i] = outvalue[i];
#ifdef OLD
               if (i >= NAMELEN)
                  ourexit(2,"\nError: ID-IDREF name violates NAMELEN quantity\n");
#else
               if (strlen(buffer) > NAMELEN)
                  ourexit(2,"\nError: ID-IDREF name violates NAMELEN quantity\n");
#endif
               if (thisadp->dvcode == ID)
                  insert(&idhead,buffer,thisadp->dvcode);
               else
                  insert(&idrefhead,buffer,thisadp->dvcode);
            }
            else
               if (thisadp->dvcode == IDREFS) {
                  more_def_desc = TRUE;
                  i = 0;
                  while(more_def_desc) {
                     num_id_idref++;
                     while(thisadp->u2.currdef[i] == ' ')
                        i++;
                     memset(buffer,'\0',NAMELEN+1);
                     j = 0;
                     while(!(SEPERATOR(thisadp->u2.currdef[i])) && thisadp->u2.currdef[i]!='\0') {
                        buffer[j] = thisadp->u2.currdef[i]; 
                        i++;  j++;
                     }
                     insert(&idrefhead,buffer,thisadp->dvcode);
                     more_def_desc = (thisadp->u2.currdef[i] != '\0');
                  }
               }
            break;
         }
         if (print_to_file == FALSE) {
            putchar_outbuf(' '); 
            putstr_outbuf(thisadp->attrname);    
            putstr_outbuf("=\"");
            output_with_lb(strlen(thisadp->attrname)+3,outvalue,FALSE);
            putstr_outbuf("\"");
            place_in_queue(TAG_ATTR,thisadp->attrname,outvalue);
         }
         else {
            if (thisadp->dvcode==ID||thisadp->dvcode==IDREF||thisadp->dvcode==IDREFS)
               num_id_idref++;
            (*print_ctr)(ctrfp," %s=\"",thisadp->attrname);
            output_with_lb(strlen(thisadp->attrname)+3,outvalue,TRUE);
            (*print_ctr)(ctrfp,"\"");
            (*applic)(TAG_ATTR,thisadp->attrname,outvalue);
         }
      }
   }
   return(num_id_idref);
}

/*--------------------------------------------------------------*/
/*             O U T P U T _ W I T H _ L B                      */
/* The CTR requires that output of data or attribute  */
/* values exceeding a column position (nominally 60)  */
/* be broken into multiple lines with a line-break    */
/* character placed at the end of each line.    */
/*--------------------------------------------------------------*/
void output_with_lb(currcol,outstr,print_to_file)
int currcol;
char *outstr;
BOOLEAN print_to_file;
{
   char *cptr,buffer[13];

   for (cptr=outstr; *cptr != '\0'; cptr++)
      if (++currcol > MAXCOL) {
         if (print_to_file)
            if (SGMLCHAR(*cptr))
               (*print_ctr)(ctrfp,"|\n%c",*cptr);
            else
               (*print_ctr)(ctrfp,"|\n[#%d]",*cptr);
         else {
            if (SGMLCHAR(*cptr)) {
               putstr_outbuf("|\n");
               putchar_outbuf(*cptr);
            }
            else {
               sprintf(buffer,"|\n[#%d]",*cptr);
               putstr_outbuf(buffer);
            }
         }
         currcol = 0;
      }
      else
         if (print_to_file)
            if (SGMLCHAR(*cptr))
               (*print_ctr)(ctrfp,"%c",*cptr);
            else
               (*print_ctr)(ctrfp,"\n[#%d]",*cptr);
         else
            if (SGMLCHAR(*cptr))
               putchar_outbuf(*cptr);
            else {
               sprintf(buffer,"\n[#%d]",*cptr);
               putstr_outbuf(buffer);
            }
   return;
}

/*------------------------------------------------------*/
/*       F I L L U P          */
/* Used to fill up the rest of name, nmtoken,   */
/* or nutoken array.  Input is taken from the   */
/* input document and the same restrictions  */
/* apply here in terms of NAMELEN.        */
/*------------------------------------------------------*/
void fillup(string,indx,capitalize)
char string[];
int *indx;
int (*capitalize)();
{
   int inchar;
   inchar = our_fgetc(indoc);
   while(NAMECHAR(inchar) && *indx<=NAMELEN) {
      string[*indx] = (*capitalize)(inchar);  /* case insensitive */
      putchar_outbuf(string[(*indx)++]);
      inchar = our_fgetc(indoc);
   }
   our_ungetc(inchar,indoc);
   return;
}

/*------------------------------------------------------*/
/*------------------------------------------------------*/
/*------------------------------------------------------*/
void fillup2(string,indx,capitalize)
char string[];
int *indx;
int (*capitalize)();
{
   int inchar;
   inchar = our_fgetc(indoc);
   while(NAMECHAR(inchar) && *indx<=NAMELEN) {
      string[*indx] = (*capitalize)(inchar);  /* case insensitive */
      (*indx)++;
      inchar = our_fgetc(indoc);
   }
   our_ungetc(inchar,indoc);
   return;
}
