#include "sgmlincl.h"         /* #INCLUDE statements for SGML parser. */
/* MDADL: Process ATTLIST declaration.
*/
VOID mdadl(tbuf)
UNCH *tbuf;                   /* Work area for tokenization (tbuf). */
{
     int i;                   /* Loop counter; temporary variable. */
     int adlim;               /* Number of unused ad slots in al. */
     struct ad *alperm = 0;   /* Attribute definition list. */
     int stored = 0;

     mdname = key[KATTLIST];  /* Identify declaration for messages. */
     subdcl = 0;              /* No subject as yet. */
     parmno = 0;              /* No parameters as yet. */
     mdessv = es;             /* Save es level for entity nesting check. */
     reqadn = noteadn = 0;    /* No required attributes yet. */
     idadn = conradn = 0;     /* No special atts yet.*/
     AN(al) = 0;	      /* Number of attributes defined. */
     ADN(al) = 0;             /* Number of ad's in al (atts + name vals).*/
     /* PARAMETER 1: Element name or a group of them.
     */
     parsemd(tbuf, NAMECASE, &pcblitp, NAMELEN);
     TRACEMD("1: element name or group");
     switch (pcbmd.action) {
     case NAS:
          nmgrp[0] = etddef(tbuf);
          nmgrp[1] = 0;
          break;
     case GRPS:
          parsegrp(nmgrp, &pcbgrnm, tbuf);
          break;
     case RNS:           /* Reserved name started. */
          if (ustrcmp(tbuf+1, key[KNOTATION])) {
               mderr(118, tbuf+1, key[KNOTATION]);
               return;
          }
          mdnadl(tbuf);
          return;
     default:
          mderr(121, (UNCH *)0, (UNCH *)0);
          return;
     }
     /* Save first GI for error msgs. */
     if (nmgrp[0])
	  subdcl = nmgrp[0]->etdgi+1;
     /* PARAMETER 2: Attribute definition list.
     */
     parsemd(tbuf, NAMECASE, &pcblitp, NAMELEN);
     TRACEMD("2: attribute list");
     if (pcbmd.action!=NAS) {
          mderr(120, (UNCH *)0, (UNCH *)0);
          return;
     }
     while (pcbmd.action==NAS) {
	  al[ADN(al)+1].adname = savenm(tbuf);
          if ((adlim = ATTCNT-((int)++ADN(al)))<0) {
	       mderr(111, (UNCH *)0, (UNCH *)0);
	       adlfree(al, 1);
	       return;
	  }
          ++AN(al);
          if (mdattdef(adlim, 0)) {
	       adlfree(al, 1);
	       return;
	  }
          parsemd(tbuf, NAMECASE, &pcblitp, NAMELEN);
     }
     if (AN(al)>0) {   /*  Save list only if 1 or more good atts. */
          if (reqadn)  SET(ADLF(al), ADLREQ);    /* Element must have start-tag. */
          if (noteadn) SET(ADLF(al), ADLNOTE);   /* Element cannot be EMPTY. */
          if (conradn) SET(ADLF(al), ADLCONR);   /* Element cannot be EMPTY. */
          alperm = (struct ad *)rmalloc((1+ADN(al))*ADSZ);
          memcpy((UNIV)alperm, (UNIV)al, (1+ADN(al))*ADSZ );
          ds.attcnt += AN(al);         /* Number of attributes defined. */
          ds.attgcnt += ADN(al) - AN(al);  /* Number of att grp members. */
          TRACEADL(alperm);
     }
     /* Clear attribute list for next declaration. */
     MEMZERO((UNIV)al, (1+ADN(al))*ADSZ);

     /* PARAMETER 3: End of declaration.
     */
     /* Next pcb.action was set during attribute definition loop. */
     TRACEMD(emd);
     if (pcbmd.action!=EMD) {mderr(126, (UNCH *)0, (UNCH *)0); return;}
     if (es!=mdessv) synerr(37, &pcbmd);

     /* EXECUTE: Store the definition for each element name specified.
     */
     TRACEGRP(nmgrp);
     for (i = 0; nmgrp[i]; i++) {
          if (nmgrp[i]->adl) {     /* Error if an ADL exists. */
               mderr(112, (UNCH *)0, (UNCH *)0);
               continue;
          }
          nmgrp[i]->adl = alperm;  /* If virgin, store the adl ptr. */
	  stored = 1;
          if (alperm && nmgrp[i]->etdmod)
	       etdadl(nmgrp[i]); /* Check for conflicts with ETD. */
     }
     if (!stored && alperm) {
	  adlfree(alperm, 1);
	  frem((UNIV)alperm);
     }
}
/* ETDADL: Check compatibility between ETD and ADL.
*/
VOID etdadl(p)
struct etd *p;                /* Pointer to element type definition. */
{
     parmno = 0;
     /* Minimizable element cannot have required attribute. */
     if (GET(p->etdmin, SMO) && GET(p->adl[0].adflags, ADLREQ)) {
          mderr(40, (UNCH *)0, (UNCH *)0);
          RESET(p->etdmin, SMO);
     }
     /* Empty element cannot have NOTATION attribute.
        Attribute is not removed (too much trouble), but we trap
        attempts to specify it on the start-tag in adlval().
     */
     if (GET(p->etdmod->ttype, MNONE)) {
          if (GET(p->adl[0].adflags, ADLNOTE))
               mderr(83, (UNCH *)0, (UNCH *)0);

          /* Empty element cannot have CONREF attribute.
             Attribute is not removed because it just acts
             like IMPLIED anyway.
          */
          if (GET(p->adl[0].adflags, ADLCONR))
               mderr(85, (UNCH *)0, (UNCH *)0);
     }
#if 0
     /* "-" should not be specified for the end-tag minimization if
	the element has a content reference attribute. */
     if (GET(p->adl[0].adflags, ADLCONR) && BITON(p->etdmin, EMM))
	  mderr(153, (UNCH *)0, (UNCH *)0);
#endif
}
/* MDNADL: Process ATTLIST declaration for notation.
           TO DO: Pass deftab and dvtab as parameters so
           that prohibited types can be handled by leaving
           them out of the tables.
*/
VOID mdnadl(tbuf)
UNCH *tbuf;                   /* Work area for tokenization (tbuf). */
{
     int i;                   /* Loop counter; temporary variable. */
     int adlim;               /* Number of unused ad slots in al. */
     struct ad *alperm = 0;   /* Attribute definition list. */
     int stored = 0;

     /* PARAMETER 1: Notation name or a group of them.
     */
     parsemd(tbuf, NAMECASE, &pcblitp, NAMELEN);
     TRACEMD("1: notation name or group");
     switch (pcbmd.action) {
     case NAS:
          nnmgrp[0] = dcndef(tbuf);
          nnmgrp[1] = 0;
          break;
     case GRPS:
          parsngrp(nnmgrp, &pcbgrnm, tbuf);
          break;
     default:
          mderr(121, (UNCH *)0, (UNCH *)0);
          return;
     }
     subdcl = nnmgrp[0]->ename+1;        /* Save first name for error msgs. */
     /* PARAMETER 2: Attribute definition list.
     */
     parsemd(tbuf, NAMECASE, &pcblitp, NAMELEN);
     TRACEMD("2: attribute list");
     if (pcbmd.action!=NAS) {
          mderr(120, (UNCH *)0, (UNCH *)0);
          return;
     }
     while (pcbmd.action==NAS) {
	  al[ADN(al)+1].adname = savenm(tbuf);
          if ((adlim = ATTCNT-((int)ADN(al)++))<0) {
	       mderr(111, (UNCH *)0, (UNCH *)0);
	       adlfree(al, 1);
	       return;
	  }
          ++AN(al);
          if (mdattdef(adlim, 1)) {
	       adlfree(al, 1);
	       return;
	  }
          parsemd(tbuf, NAMECASE, &pcblitp, NAMELEN);
     }
     if (AN(al)>0) {   /*  Save list only if 1 or more good atts. */
          alperm = (struct ad *)rmalloc((1+ADN(al))*ADSZ);
          memcpy((UNIV)alperm, (UNIV)al, (1+ADN(al))*ADSZ );
          ds.attcnt += AN(al);         /* Number of attributes defined. */
          ds.attgcnt += ADN(al) - AN(al);  /* Number of att grp members. */
          TRACEADL(alperm);
     }
     /* Clear attribute list for next declaration. */
     MEMZERO((UNIV)al, (1+ADN(al))*ADSZ);

     /* PARAMETER 3: End of declaration.
     */
     /* Next pcb.action was set during attribute definition loop. */
     TRACEMD(emd);
     if (pcbmd.action!=EMD) {mderr(126, (UNCH *)0, (UNCH *)0); return;}
     if (es!=mdessv) synerr(37, &pcbmd);

     /* EXECUTE: Store the definition for each notation name specified.
     */
     TRACENGR(nnmgrp);
     for (i = 0; nnmgrp[i]; i++) {
          if (nnmgrp[i]->adl) {     /* Error if an ADL exists. */
               mderr(112, (UNCH *)0, (UNCH *)0);
               continue;
          }
          nnmgrp[i]->adl = alperm;  /* If virgin, store the adl ptr. */
	  if (nnmgrp[i]->entsw)
	       fixdatt(nnmgrp[i]);
	  stored = 1;
          TRACEDCN(nnmgrp[i]);
     }
     if (!stored && alperm) {
	  adlfree(alperm, 1);
	  frem((UNIV)alperm);
     }
}

/* Data attributes have been specified for notation p, but entities
have already been declared with notation p.  Fix up the definitions of
all entities with notation p.  Generate an error for any data
attribute that was required. */

VOID fixdatt(p)
struct dcncb *p;
{
     int i;
     for (i = 0; i < ENTHASH; i++) {
	  struct entity *ep;
	  for (ep = etab[i]; ep; ep = ep->enext)
	       if (ep->estore == ESN && ep->etx.n && ep->etx.n->nedcn == p) {
		    int adn;
		    initatt(p->adl);
		    /* Don't use adlval because if there were required
		       attributes the error message wouldn't say what
		       entity was involved. */
		    for (adn = 1; adn <= ADN(al); adn++) {
			 if (GET(ADFLAGS(al,adn), AREQ)) {
			      sgmlerr(218, &pcbstag, ADNAME(al,adn),
				      ep->ename + 1);
			      SET(ADFLAGS(al,adn), AINVALID);
			 }
			 if (BITON(ADFLAGS(al, adn), AGROUP))
			      adn += ADNUM(al, adn);
		    }
		    storedatt(ep->etx.n);
	       }
     }
}

/* MDATTDEF: Process an individual attribute definition.
             The attribute name is parsed by the caller.
             Duplicate attributes are parsed, but removed from list.
             Returns 0 if successful, otherwise returns 1.
*/
int mdattdef(adlim, datt)
int adlim;                    /* Remaining capacity of al (in tokens).*/
int datt;		      /* Non-zero if a data attribute. */
{
     int deftype;             /* Default value type: 0=not keyword. */
     int errsw = 0;           /* 1=semantic error; ignore att. */
     int novalsw = 0;         /* 1=semantic error; treat as IMPLIED. */
     int attadn = (int)ADN(al);   /* Save ad number of this attribute. */
     struct parse *grppcb = NULL; /* PCB for name/token grp parse. */
     int errcode;             /* Error type returned by PARSEVAL, ANMTGRP. */
     UNCH *advalsv;           /* Save area for permanent value ptr. */

     /* PARAMETER 1: Attribute name (parsed by caller).
     */
     TRACEMD("1: attribute name");
     if (anmget((int)ADN(al)-1, al[attadn].adname)) {
          errsw = 1;
          mderr(99, ADNAME(al,attadn), (UNCH *)0);
     }
     ADNUM(al,attadn) = ADFLAGS(al,attadn) = ADLEN(al,attadn) = 0;
     ADVAL(al,attadn) = 0; ADDATA(al,attadn).x = 0; ADTYPE(al,attadn) = ANMTGRP;
     /* PARAMETER 2: Declared value.
     */
     parsemd(lbuf, NAMECASE, &pcblitp, NAMELEN);
     TRACEMD("2: declared value");
     switch (pcbmd.action) {
     case NAS:                /* Keyword for value type. */
          switch (ADTYPE(al,attadn) = (UNCH)mapsrch(dvtab, lbuf+1)) {
          case 0:
               mderr(100, ADNAME(al,attadn), lbuf+1);
               return 1;
          case ANOTEGRP:
	       if (datt) {
		    errsw = 1;
		    mderr(156, (UNCH *)0, (UNCH *)0);
	       }
               else if (!noteadn) noteadn = ADN(al);
               else {
                    errsw = 1;
                    mderr(101, ADNAME(al,attadn), (UNCH *)0);
               }
               grppcb = &pcbgrnm;         /* NOTATION requires name grp. */
               parsemd(lbuf, NAMECASE, &pcblitp, NAMELEN);/* Get GRPO*/
               break;
          case AID:
	       if (datt) {
		    errsw = 1;
		    mderr(144, (UNCH *)0, (UNCH *)0);
	       }
               else if (!idadn)
		    idadn = attadn;
               else {
                    errsw = 1;
                    mderr(102, ADNAME(al,attadn), (UNCH *)0);
               }
               break;
	  case AIDREF:
	  case AIDREFS:
	       if (datt) {
		    errsw = 1;
		    mderr(155, (UNCH *)0, (UNCH *)0);
	       }
	       break;
	  case AENTITY:
	  case AENTITYS:
	       if (datt) {
		    errsw = 1;
		    mderr(154, (UNCH *)0, (UNCH *)0);
	       }
	       break;
          }
          break;
     case GRPS:
          grppcb = &pcbgrnt;           /* Normal grp is name token grp. */
          break;
     case EMD:
          mderr(103, ADNAME(al,attadn), (UNCH *)0);
          return 1;
     default:
          mderr(104, ADNAME(al,attadn), (UNCH *)0);
          return 1;
     }
     /* PARAMETER 2A: Name token group.
     */
     if (grppcb != NULL) {
	  TRACEMD("2A: name group");
          switch (pcbmd.action) {
          case GRPS:               /* Name token list. */
               SET(ADFLAGS(al,attadn), AGROUP);
               /* Call routine to parse group, create ad entries in adl. */
               errcode = anmtgrp(grppcb, al+attadn,
				 (GRPCNT<adlim ? GRPCNT+1 : adlim+1),
				 &al[attadn].adnum, ADN(al));
               if (errcode<=0) {
		    if (adlim < GRPCNT)
			 mderr(111, (UNCH *)0, (UNCH *)0);
		    else
			 mderr(105, ADNAME(al,attadn), (UNCH *)0);
                    return 1;
               }
               ADN(al) += ADNUM(al,attadn);    /* Add grp size to total ad cnt.*/
               break;
          default:
               mderr(106, ADNAME(al,attadn), (UNCH *)0);
               return 1;
          }
     }
     /* PARAMETER 3: Default value keyword.
     */
     parsemd(lbuf, AVALCASE,
	     (ADTYPE(al,attadn)==ACHARS) ? &pcblitr : &pcblitt, LITLEN);
     TRACEMD("3: default keyword");
     switch (pcbmd.action) {
     case RNS:                /* Keyword. */
          deftype = mapsrch(deftab, lbuf+1);
          switch (deftype) {
          case DFIXED:        /* FIXED */
               SET(ADFLAGS(al,attadn), AFIXED);
               parsemd(lbuf, AVALCASE,
		       (ADTYPE(al,attadn)==ACHARS) ? &pcblitr : &pcblitt,
		       LITLEN);  /* Real default. */
               goto parm3x;   /* Go process specified value. */
          case DCURR:         /* CURRENT: If ID, treat as IMPLIED. */
               if (ADTYPE(al,attadn)==AID) {
                    mderr(80, ADNAME(al,attadn), (UNCH *)0);
                    break;
               }
	       if (datt) {
		    mderr(157, (UNCH *)0, (UNCH *)0);
		    break;
	       }
               SET(ADFLAGS(al,attadn), ACURRENT);
               break;
          case DREQ:          /* REQUIRED */
               SET(ADFLAGS(al,attadn), AREQ); ++reqadn;
               break;
          case DCONR:         /* CONREF */
               if (ADTYPE(al,attadn)==AID) {
                    mderr(107, ADNAME(al,attadn), (UNCH *)0);
                    break;
               }
	       if (datt) {
		    mderr(158, (UNCH *)0, (UNCH *)0);
		    break;
	       }
               SET(ADFLAGS(al,attadn), ACONREF); conradn = 1;
          case DNULL:         /* IMPLIED */
               break;
          default:            /* Unknown keyword is an error. */
               mderr(108, ADNAME(al,attadn), lbuf+1);
               errsw = 1;
          }
          if (errsw) {
	       /* Ignore erroneous att. */
	       adlfree(al, attadn);
	       --AN(al);
	       ADN(al) = (UNCH)attadn-1;
	  }
          return(0);
     default:
          break;
     }
     /* PARAMETER 3x: Default value (non-keyword).
     */
     parm3x:
     TRACEMD("3x: default (non-keyword)");
     if (ADTYPE(al,attadn)==AID) { /* If ID, treat as IMPLIED. */
          mderr(81, ADNAME(al,attadn), (UNCH *)0);
          novalsw = 1;	      /* Keep parsing to keep things straight. */
     }
     switch (pcbmd.action) {
     case LIT:                /* Literal. */
     case LITE:               /* Literal. */
          /* Null string (except CDATA) is error: msg and treat as IMPLIED. */
          if (*lbuf == '\0' && ADTYPE(al,attadn)!=ACHARS) {
               mderr(82, ADNAME(al,attadn), (UNCH *)0);
               novalsw = 1;
          }
	  break;
     case NAS:                /* Name character string. */
     case NMT:                /* Name character string. */
     case NUM:                /* Number or number token string. */
	  /* The name won't have a length byte because AVALCASE was specified. */
          break;
     case CDR:
	  parsetkn(lbuf, NMC, LITLEN);
	  break;
     case EMD:
          mderr(109, ADNAME(al,attadn), (UNCH *)0);
          return 1;
     default:
          mderr(110, ADNAME(al,attadn), (UNCH *)0);
          return 1;
     }
     if (errsw) {
	  /* Ignore erroneous att. */
	  adlfree(al, attadn);
	  --AN(al);
	  ADN(al) = (UNCH)attadn-1;
	  return(0);
     }
     if (novalsw) return(0);

     /* PARAMETER 3y: Validate and store default value.
     */
     if (ADTYPE(al,attadn)==ACHARS) {
	  UNS len = vallen(ACHARS, 0, lbuf);
	  if (len > LITLEN) {
	       /* Treat as implied. */
	       sgmlerr(224, &pcbmd, ADNAME(al,attadn), (UNCH *)0);
	       return 0;
	  }
          /* No more checking for CDATA value. */
          ADNUM(al,attadn) = 0;             /* CDATA is 0 tokens. */
          ADVAL(al,attadn) = savestr(lbuf);/* Store default; save ptr. */
          ADLEN(al,attadn) = len;
          ds.attdef += len;
          return 0;
     }
     /* Parse value and save token count (GROUP implies 1 token). */
     advalsv = (UNCH *)rmalloc(ustrlen(lbuf)+2); /* Storage for tokenized value. */
     errcode = parseval(lbuf, (UNS)ADTYPE(al,attadn), advalsv);
     if (BITOFF(ADFLAGS(al,attadn), AGROUP)) ADNUM(al,attadn) = (UNCH)tokencnt;

     /* If value was invalid, or was a group member that was not in the group,
        issue an appropriate message and set the error switch. */
     if (errcode)
          {sgmlerr((UNS)errcode, &pcbmd, ADNAME(al,attadn), lbuf); errsw = 1;}
     else if ( BITON(ADFLAGS(al,attadn), AGROUP)
          && !amemget(&al[attadn], (int)ADNUM(al,attadn), advalsv) ) {
               sgmlerr(79, &pcbmd, ADNAME(al,attadn), advalsv+1);
               errsw = 1;
     }
     ADLEN(al,attadn) = vallen(ADTYPE(al,attadn), ADNUM(al,attadn), advalsv);
     if (ADLEN(al,attadn) > LITLEN) {
	  sgmlerr(224, &pcbmd, ADNAME(al,attadn), (UNCH *)0);
	  ADLEN(al,attadn) = 0;
	  errsw = 1;
     }
     /* For valid tokenized value, save it and update statistics. */
     if (!errsw) {
	  ADVAL(al,attadn) = advalsv;
          ds.attdef += ADLEN(al,attadn);
          return 0;
     }
     /* If value was bad, free the value's storage and treat as
        IMPLIED or REQUIRED. */
     frem((UNIV)advalsv);          /* Release storage for value. */
     ADVAL(al,attadn) = NULL;         /* And make value NULL. */
     return 0;
}
/* ANMTGRP: Parse a name or name token group, create attribute descriptors
            for its members, and add them to the attribute descriptor list.
            The parse either terminates or returns a good token, so no
            switch is needed.
*/
int anmtgrp(pcb, nt, grplim, adn, adsz)
struct parse *pcb;            /* PCB for name or name token grp. */
struct ad nt[];               /* Buffer for creating name token list. */
int grplim;                   /* Maximum size of list (plus 1). */
UNS *adn;		      /* Ptr to number of names or tokens in grp. */
int adsz;                     /* Size of att def list. */
{
     UNCH adtype = (UNCH)(pcb==&pcbgrnt ? ANMTGRP:ANOTEGRP);/*Attribute type.*/
     int essv = es;           /* Entity stack level when grp started. */

     *adn = 0;                /* Group is empty to start. */
     while (parse(pcb)!=GRPE && *adn<grplim) {
          switch (pcb->action) {
          case NAS_:          /* Name or name token (depending on pcb). */
          case NMT_:
               parsenm(lbuf, NAMECASE);
	       nt[*adn+1].adname = savenm(lbuf);
               if (antvget((int)(adsz+*adn), nt[*adn+1].adname, (UNCH **)0))
                    mderr(98, ntoa((int)*adn+1), nt[*adn+1].adname+1);
               nt[++*adn].adtype = adtype;
               nt[*adn].addef    = NULL;
               continue;

          case EE_:           /* Entity ended (correctly or incorrectly). */
               if (es<essv) {synerr(37, pcb); essv = es;}
               continue;

          case PIE_:          /* PI entity reference (invalid). */
               entpisw = 0;   /* Reset PI entity indicator. */
               synerr(59, pcb);
               continue;

          default:
               break;
          }
          break;
     }
     if (es!=essv) synerr(37, pcb);
     if (*adn==grplim) return -1;
     else return *adn;        /* Return number of tokens. */
}
/* MDDTDS: Process start of DOCTYPE declaration (through MSO).
*/
VOID mddtds(tbuf)
UNCH *tbuf;                   /* Work area for tokenization[LITLEN+2]. */
{
     struct fpi fpicb;        /* Formal public identifier structure. */
     union etext etx;         /* Ptr to entity text. */
     UNCH estore = ESD;       /* Entity storage class. */
     int emdsw = 0;           /* 1=end of declaration found; 0=not yet. */

     mdname = key[KDOCTYPE];  /* Identify declaration for messages. */
     subdcl = NULL;           /* No subject as yet. */
     parmno = 0;              /* No parameters as yet. */
     mdessv = es;             /* Save es for checking entity nesting. */
     dtdrefsw = 0;            /* No external DTD entity as yet. */
     /* PARAMETER 1: Document type name.
     */
     pcbmd.newstate = 0;
     parsemd(tbuf, NAMECASE, &pcblitp, NAMELEN);
     TRACEMD("1: doc type name");
     if (pcbmd.action!=NAS) {mderr(120, (UNCH *)0, (UNCH *)0); return;}
     dtype = savenm(tbuf);
     subdcl = dtype+1;        /* Subject of declaration for error msgs. */

     /* PARAMETER 2: External identifier keyword or MDS.
     */
     parsemd(tbuf, NAMECASE, &pcblitp, NAMELEN);
     TRACEMD("2: extid or MDS");
     switch (pcbmd.action) {
     case NAS:
          if (mdextid(tbuf, &fpicb, dtype+1, &estore, (PNE)0)==0) return;
          if ((etx.x = entgen(&fpicb))==0)
	       mderr(146, dtype+1, (UNCH *)0);
	  else
	       dtdrefsw = 1;  /* Signal external DTD entity. */
          break;
     case MDS:
          goto execute;
     default:
          mderr(128, (UNCH *)0, (UNCH *)0);
          return;
     }
     /* PARAMETER 3: MDS or end of declaration.
     */
     TRACEMD("3: MDS or EMD");
     switch (pcbmd.action) {
     default:                      /* Treat as end of declaration. */
          mderr(126, (UNCH *)0, (UNCH *)0);
     case EMD:
          emdsw = 1;
     case MDS:
          break;
     }
     /* EXECUTE: Store entity definition if an external ID was specified.
     */
     execute:
     if (es!=mdessv) synerr(37, &pcbmd);
     propcb = &pcbmds;        /* Prepare to parse doc type definition (MDS). */
     if (dtdrefsw) {
	  /* TO DO: If concurrent DTD's supported, free existing
	     etext for all but first DTD (or reuse it). */
	  entdef(indtdent, estore, &etx);
	  ++ds.ecbcnt; ds.ecbtext += entlen;
          if (emdsw) {
               REPEATCC;                /* Push back the MDC. */
               *FPOS = lex.d.msc;       /* Simulate end of DTD subset. */
               REPEATCC;                /* Back up to read MSC next. */
               delmscsw = 1;            /* Insert MSC after referenced DTD. */
          }
     }
     indtdsw = 1;                       /* Allow "DTD only" parameters. */
     return;
}
/* MDDTDE: Process DOCTYPE declaration end.
*/
VOID mddtde(tbuf)
UNCH *tbuf;                   /* Work area for tokenization. */
{
     mdessv = es;             /* Save es for checking entity nesting. */
     propcb = &pcbpro;        /* Restore normal prolog parse. */
     indtdsw = 0;             /* Prohibit "DTD only" parameters. */

     mdname = key[KDOCTYPE];  /* Identify declaration for messages. */
     subdcl = dtype+1;        /* Subject of declaration for error msgs. */
     parmno = 0;              /* No parameters as yet. */
     /* PARAMETER 4: End of declaration.
     */
     pcbmd.newstate = pcbmdtk;
     parsemd(tbuf, NAMECASE, &pcblitp, LITLEN);
     TRACEMD(emd);
     if (pcbmd.action!=EMD) mderr(126, (UNCH *)0, (UNCH *)0);
     if (es!=mdessv) synerr(37, &pcbmd);
}
/* MDELEM: Process ELEMENT declaration.
*/
VOID mdelem(tbuf)
UNCH *tbuf;                   /* Work area for tokenization (tbuf). */
{
     UNCH *ranksuff = lbuf;   /* Rank suffix. */
     UNS dctype = 0;          /* Declared content type (from dctab). */
     UNCH fmin = 0;           /* Minimization bit flags. */
     int i;                   /* Loop counter. */
     UNS u;                   /* Temporary variable. */
     struct etd **mexgrp, **pexgrp; /* Ptr to model exceptions array. */
     struct thdr *cmod, *cmodsv;    /* Ptr to content model. */
     UNCH *etdgi;             /* GI of current etd (when going through group).*/
     int minomitted = 0;      /*  Tag minimization parameters omitted. */

     mdname = key[KELEMENT];  /* Identify declaration for messages. */
     subdcl = NULL;           /* No subject as yet. */
     parmno = 0;              /* No parameters as yet. */
     mdessv = es;             /* Save es level for entity nesting check. */
     ranksuff[0] = 0;
     mexgrp = pexgrp = 0;

     /* PARAMETER 1: Element name or a group of them.
     */
     parsemd(tbuf, NAMECASE, &pcblitp, NAMELEN);
     TRACEMD("1: element name or grp");
     switch (pcbmd.action) {
     case NAS:
          nmgrp[0] = etddef(tbuf);
          nmgrp[1] = 0;
          break;
     case GRPS:
          parsegrp(nmgrp, &pcbgrnm, tbuf);
          break;
     default:
          mderr(121, (UNCH *)0, (UNCH *)0);
          return;
     }
     /* Save first GI for trace and error messages. */
     if (nmgrp[0])
	  subdcl = nmgrp[0]->etdgi+1;

     /* PARAMETER 1A: Rank suffix (optional).
     */
     parsemd(tbuf, NAMECASE, &pcblitp, NAMELEN);
     TRACEMD("1A: rank suffix");
     switch (pcbmd.action) {
     case NUM:
          ustrcpy(ranksuff, tbuf);
          parsemd(tbuf, NAMECASE, &pcblitp, NAMELEN);
     default:
          break;
     }
     /* PARAMETER 2A: Start-tag minimization.
     */
     TRACEMD("2A: start min");
     switch (pcbmd.action) {
     case CDR:
          break;
     case NAS:
	  if (!ustrcmp(tbuf+1, key[KO])) {
	       if (OMITTAG==YES) SET(fmin, SMO);
	       break;
	  }
	  /* fall through */
     default:
	  if (OMITTAG==NO) {minomitted=1; break;}
          mderr(129, tbuf+1, (UNCH *)0);
          return;
     }
     /* Must omit omitted end-tag minimization, if omitted 
	start-tag minimization was omitted (because OMITTAG == NO). */
     if (!minomitted) {
	  /* PARAMETER 2B: End-tag minimization.
	   */
	  parsemd(tbuf, NAMECASE, &pcblitp, NAMELEN);
	  TRACEMD("2B: end min");
	  switch (pcbmd.action) {
	  case NAS:
	       if (ustrcmp(tbuf+1, key[KO])) {mderr(129, tbuf+1, (UNCH *)0); return;}
	       if (OMITTAG==YES) SET(fmin, EMO);
	       break;
	  case MGRP:
	       REPEATCC;
	       /* fall through */
	  case CDR:
	       SET(fmin, EMM);
	       break;
	  default:
	       mderr(129, tbuf+1, (UNCH *)0);
	       return;
	  }
	  /* PARAMETER 3: Declared content.
	   */
	  parsemd(tbuf, NAMECASE, &pcblitp, NAMELEN);
     }
     TRACEMD("3: declared content");
     switch (pcbmd.action) {
     case NAS:
          dctype = mapsrch(dctab, tbuf+1);
          if (!dctype) {mderr(24, tbuf+1, (UNCH *)0); return;}
          /* Eliminate incompatibilities among parameters. */
          if (GET(fmin, SMO) && GET(dctype, MNONE+MCDATA+MRCDATA)) {
               mderr(58, (UNCH *)0, (UNCH *)0);
               RESET(fmin, SMO);
          }
          if (GET(dctype, MNONE) && BITON(fmin, EMM)) {
	       mderr(87, (UNCH *)0, (UNCH *)0);
               SET(fmin, EMO);
          }
          /* If valid, process like a content model. */
     case GRPS:
          cmodsv = parsemod((int)(pcbmd.action==GRPS ? 0 : dctype));
          if (cmodsv==0) return;
	  u = (dctype ? 1 : cmodsv->tu.tnum+2) * THSZ;
          cmod = (struct thdr *)rmalloc(u);
          memcpy((UNIV)cmod  , (UNIV)cmodsv, u );
	  ds.modcnt += cmod->tu.tnum;
          TRACEMOD(cmod);
          break;
     default:
          mderr(130, (UNCH *)0, (UNCH *)0);
          return;
     }
     /* PARAMETERS 3A, 3B: Exceptions or end.
     */
     parsemd(tbuf, NAMECASE, &pcblitp, NAMELEN);
     if (BITOFF(cmod->ttype, MCDATA+MRCDATA+MNONE)) {
          /* PARAMETER 3A: Minus exceptions.
          */
          TRACEMD("3A: -grp");
          switch (pcbmd.action) {
          case MGRP:
	       /* We cheat and use nnmgrp for this. */
               mexgrp = copygrp((PETD *)nnmgrp,
				u = parsegrp((PETD *)nnmgrp, &pcbgrnm, tbuf));
               ++ds.pmexgcnt; ds.pmexcnt += u-1;
               TRACEGRP(mexgrp);
               parsemd(tbuf, NAMECASE, &pcblitp, NAMELEN);
          default:
               break;
          }
          /* PARAMETER 3B: Plus exceptions.
          */
          TRACEMD("3B: +grp");
          switch (pcbmd.action) {
          case PGRP:
               pexgrp = copygrp((PETD *)nnmgrp,
				u = parsegrp((PETD *)nnmgrp, &pcbgrnm, tbuf));
               ++ds.pmexgcnt; ds.pmexcnt += u-1;
               TRACEGRP(pexgrp);
               parsemd(tbuf, NAMECASE, &pcblitp, NAMELEN);
          default:
               break;
          }
     }
     /* PARAMETER 4: End of declaration.
     */
     TRACEMD(emd);
     if (pcbmd.action!=EMD) mderr(126, (UNCH *)0, (UNCH *)0);
     if (es!=mdessv) synerr(37, &pcbmd);

     /* EXECUTE: Store the definition for each element name specified.
     */
     TRACEGRP(nmgrp);
     for (i = -1; nmgrp[++i];) {
          etdgi = nmgrp[i]->etdgi;
          if (*ranksuff) {
               if ((tbuf[0] = *etdgi + ustrlen(ranksuff)) - 2 > NAMELEN) {
                    mderr(131, etdgi+1, ranksuff);
                    continue;
               }
               memcpy(tbuf+1, etdgi+1, *etdgi-1);
               ustrcpy(tbuf+*etdgi-1, ranksuff);
               etdcan(etdgi);
               nmgrp[i] = etddef(tbuf);
          }
          if (nmgrp[i]->etdmod) {mderr(56, etdgi+1, (UNCH *)0); continue;}
          etdset(nmgrp[i], fmin+ETDDCL, cmod, mexgrp, pexgrp, nmgrp[i]->etdsrm);
          ++ds.etdcnt;
          if (nmgrp[i]->adl) etdadl(nmgrp[i]); /* Check ETD conflicts. */
          TRACEETD(nmgrp[i]);
     }
}

VOID adlfree(al, aln)
struct ad *al;
int aln;
{
     for (; aln <= ADN(al); aln++) {
	  frem((UNIV)al[aln].adname);
	  if (ADVAL(al, aln))
	       frem((UNIV)ADVAL(al, aln));
	  if (BITON(ADFLAGS(al, aln), AGROUP)) {
	       int i;
	       for (i = 0; i < ADNUM(al, aln); i++)
		    frem((UNIV)al[aln + i + 1].adname);
	       aln += ADNUM(al, aln);
	  }
     }
}

/*
Local Variables:
c-indent-level: 5
c-continued-statement-offset: 5
c-brace-offset: -5
c-argdecl-indent: 0
c-label-offset: -5
comment-column: 30
End:
*/
