/*******************************************************************************
* YORKTOWN ADVANCED SGML PARSER (YASP)                                         *
*         An SGML Parser conforming to International Standard ISO 8879 --      *
*         Standard Generalized Markup Language                                 *
*------------------------------------------------------------------------------*
* Written 1987 - 1993 by                                                       *
*         Pierre G. Richard                                                    *
*         IBM T.J. Watson Research Center                                      *
*         Yorktown Heights, New York                                           *
*         petrus@watson.ibm.com                                                *
*------------------------------------------------------------------------------*
* FUNCTION NAME    = YASP / Document Instance Parsing                          *
* STATUS           = Version 0, Release 1, Level 34 and up                     *
* COMPONENT NAME   = stower.c                                                  *
* DESCRIPTIVE NAME = Do the required actions for DATA, STG, ETG                *
* DATE_WR          = 03/06/1988                                                *
* AUTHOR           = Pierre G. Richard - PETRUS at YKTVMX                      *
*                                                                              *
* FUNCTIONAL DESCRIPTION =                                                     *
*     This module manages the tcb_stack, bufferizes data and true RE.          *
*     All these functions are used only by parse_doc, and grouped here         *
*     to clarify the code.  The information exchange is done (as usual)        *
*     by a COMMON structure (defined in parsedoc.h).                           *
*                                                                              *
* "stw_otcb" when a new element is encountered                                 *
*                                                                              *
* "stw_etcb" when an element must be ended                                     *
*                                                                              *
* "stw_dtcb" when data must be entered: check how data is tolerated            *
*            at this point, set up a convenient context                        *
*                                                                              *
* "stw_data" is used to enter a new data character                             *
*                                                                              *
* "stw_stRE" is used to enter a Record End                                     *
*                                                                              *
* "stw_note" returns a pointer to the last character entered                   *
*                                                                              *
* "stw_flsh" flushes the buffer                                                *
*                                                                              *
* "stw_xpel" flushes partially the buffer                                      *
*                                                                              *
*     For "state" handling, see comments in parsedoc.                          *
*                                                                              *
*                                                                              *
* DEPENDENCIES =  (see Include list)                                           *
*                                                                              *
* IMPORT=                                                                      *
*                                                                              *
* EXPORT=                                                                      *
*                                                                              *
* IMPLEMENTATION NOTES =                                                       *
*                                                                              *
* Data bufferization:                                                          *
* In fact the "buffer" is a double buffer: when buffer #1 is full,             *
* buffer #2 is flushed, then used.  And vice-versa.                            *
* Each buffer being (at least) DTAGLEN long, there cannot be any               *
* matching DATATAG in a flushed buffer.                                        *
* Also, RE (entered in the buffer) need to be handle specially                 *
* to be able to stop and tell Application at each RE while flushing the buffer.*
*                                                                              *
*                                                                              *
* CHANGE HISTORY =                                                             *
* Number Date     Inits    Change & Reason (brief)                             *
*      1 10/21/89 PETRUS   Rules at 7.4.1.1 are now applied                    *
*      2 12/24/89 PETRUS   Amendment 1 simplifications: RE is data done        *
*                          during recognition of markup, no shortref reparsing *
*      3 05/18/90 PETRUS   Fixed a problem with RE and included element        *
*      4 12/18/90 PETRUS   Added STW_BASE to imply base elt start-tag          *
*      5 04/11/91 PETRUS   TLLSTACK overflow: now mallocated                   *
*      6 06/06/91 PGR/GPD  Tell application about TAG minimization             *
*******************************************************************************/

/*-------------+
|Include Files |
+-------------*/
#include "banner.h"    /* This module includes code depending on YASP version */
#include "machine.h"
#include "portable.h"
#include "yaspapi.h"
#include "global.h"
#include "usrlib.h"
#include "sgml.h"
#include "gtype.h"
#include "tag.h"
#include "modele.h"
#include "stower.h"
#include "teller.h"

/*----------------------+
|Local Data and Typedefs|
+----------------------*/
typedef struct {         /* Tell Stack Entry */
   ELT *elt_p;           /* to stack tells (STG, ETG, RE) in case of backup */
   uchar ded_is;         /* DED bit-field */
}TSE;

#define TSE_INCR 5       /* Stack increase amount */

typedef struct {
   int  rcount;          /* number of TSE unused on the stack */
   int  size;            /* current size of this structure */
   TSE *p;               /* Pointer to the highest used TSE */
   TSE  stack[TSE_INCR]; /* The stack itself (can be increased) */
}TSHDR;                  /* Tell Stack Header */

/*--------------+
|Local Functions|
+--------------*/
static void    LP_FCT set_rank_suffices(GA_P, ELT *);
static void    LP_FCT adjust_rcgn_mode (COM_P);
static void    LP_FCT stack_tse        (COM_P, TSHDR **, ELT *, uchar);
static void    LP_FCT unstack_tse      (
   COM_P, TSHDR *, ATD *, SRM *, ELT *, uchar, uchar
);
static void    LP_FCT start_elt        (COM_P, TSHDR **, MS);
static boolean LP_FCT end_elt          (COM_P, TSHDR **, boolean);
static void    LP_FCT flush_buffer     (COM_P, ODBF STACK_BASED *);

/*--------------------------------------------------------------------stw_gfgi-+
| Get a full generic identifier, returning a pointer to the ELT structure.     |
| If this FGI cannot be found, issue an error, return a NULL pointer.          |
+-----------------------------------------------------------------------------*/
ELT * P_FCT stw_gfgi(GA_P gbla_p)
{
   uchar name[NAMELEN_MAX+1];
   ELT *elt_p;

   get_name(gbla_p,name);
   if (!(elt_p=(ELT *)shsh_fnd(GA(elt_htab),name))) {
      STS *sts_p;
      int suffix;
      if ((sts_p = (STS *)shsh_fnd(GA(sts_htab),name)) != (STS *)0) {
         if ((suffix = sts_p->suffix) != -1) {
            uchar *c_p, *d_p;
            int i;
            c_p = d_p = name + (i=strlen(name)-1);
            while (++i < NAMELEN_MAX) {
               *++c_p = (uchar)('0' + (suffix%10));
               if (!(suffix/=10)) break;
            }
            *++c_p = '\0';
            while (--c_p > ++d_p) {               /* reverse the string */
               uchar d;
               d=*d_p; *d_p=*c_p; *c_p=d;
            }
            elt_p=(ELT *)shsh_fnd(GA(elt_htab),name);
         }
      }
   }
   if (!elt_p) errorh(gbla_p,INVELTGI,ERH_W_SEV+ERH_LOCATE,name);
   return(elt_p);
}

/*STATIC-----------------------------------------------------set_rank_suffices-+
| Set the current rank suffix in all stems when a full generic identifier      |
| has been validated.                                                          |
+-----------------------------------------------------------------------------*/
static void LP_FCT set_rank_suffices(GA_P gbla_p, ELT *elt_p)
{
   RKL *rkl_p, *rkl_pN;
   STS *sts_p;
   int count, suffix;
   uchar *c_p, *d_p;

   if (!IS_BITON(GA(opt).ft,RANK_FT)) return;

   sts_p = (rkl_p = elt_p->rkl.p)->sts.p;
   /*-------------+
   |Get the suffix|
   +-------------*/
   c_p = sts_p->name.p - 1;
   d_p = elt_p->name.p - 1;
   while (*++d_p == *++c_p);
   --d_p;
   suffix = 0;
   while (*++d_p) suffix = ((suffix+(suffix<<2))<<1) + *d_p - '0';

   /*----------------------------+
   |Reset the suffix in all stems|
   |concerned by 7.8.1.1         |
   +----------------------------*/
   sts_p->suffix = suffix;
   count = sts_p->count;
   while (count--) {
      for (rkl_pN = rkl_p->grp.p; rkl_pN != rkl_p; rkl_pN = rkl_pN->grp.p) {
         rkl_pN->sts.p->suffix = suffix;
      }
      rkl_pN = ++rkl_p;
   }
}

/*STATIC------------------------------------------------------adjust_rcgn_mode-+
| As stated in 9.1 and note of 4.263, an "end-tag" in RCDATA-content           |
| is not recognized as such if it occurs in an imbedded entity.                |
| If RCDATA is specified, remember in cn_enty_level the current                |
| entity level, to avoid false "/", "</", or "</>" recognition.                |
| "check_contextual_constraint" in dlfind.c will then deny a delimiter to be   |
| an end-tag if it occurs at a higher entity level than the entity_level       |
| the RCDATA-content started with.                                             |
+-----------------------------------------------------------------------------*/
static void LP_FCT adjust_rcgn_mode(COM_P M_p)
{
   GA_P gbla_p = M_p->gbla_p;
   ELT *elt_p = M_p->tcb_p->elt_p;

   switch (elt_p->is.dcdata) {              /* ISO 8879 - Note at 7.6 */
   case NONR_DCDATA:
      set_mode(gbla_p,FCD_MODE);
      break;
   case RPLC_DCDATA:
      set_mode(gbla_p,RCD_MODE);
      GA(cn_enty_level) = GA(enty_level);   /* avoid false end-tag rcgn */
      break;
   default:                                 /* DENY_DCTATA */
      set_mode(gbla_p,CON_MODE);
      break;
   }
}

/*STATIC-------------------------------------------------------------stack_tse-+
|Stack tells                                                                   |
+-----------------------------------------------------------------------------*/
static void LP_FCT stack_tse(COM_P M_p,TSHDR **tshdr_pp,ELT *elt_p,uchar ded_is)
{
   int offset;
   TSHDR *tshdr_p = *tshdr_pp;
   TSE *tse_p;
   GA_P gbla_p;

   if (!tshdr_p) {                              /* TS hdr not created */
      gbla_p = M_p->gbla_p;
      *tshdr_pp = tshdr_p = malloc(sizeof(TSHDR));
      tshdr_p->size = sizeof(TSHDR);
      tshdr_p->rcount = TSE_INCR - 1;
      tse_p = tshdr_p->p = tshdr_p->stack;
   }else if (--(tshdr_p->rcount) < 0) {         /* Stack is full */
      gbla_p = M_p->gbla_p;
      offset = (uchar *)tshdr_p->p - (uchar *)tshdr_p;
      tshdr_p = *tshdr_pp = realloc(
         tshdr_p, tshdr_p->size += (TSE_INCR*sizeof(TSE))
      );
      tshdr_p->rcount += TSE_INCR;
      tse_p = tshdr_p->p = (TSE *)((uchar *)tshdr_p + offset) + 1;
   }else {
      tse_p = ++(tshdr_p->p);
   }
   tse_p->elt_p = elt_p;
   tse_p->ded_is = ded_is;
}

/*STATIC-----------------------------------------------------------unstack_tse-+
|Unstack tells after an element or data have been validated by the             |
|content model.  Also check for shortref remapping.                            |
|                                                                              |
|IMPLEMENTATION NOTES:                                                         |
|   "atd_p" is used only when an STG_U is found (only one STG_I could exist).  |
| - If "atd_p" is real (not (ATD *)0 ), this means tg_parse has been called    |
|   for this STG_I, and a copy of the ATD array (reflecting the new attributes)|
|   has been created at "atd_p"                                                |
| - If atd_p is 0, the copy was not done:                                      |
|   - either because no attributes were given,                                 |
|   - or no attributes could exist,                                            |
|   - or this is a STAGO_TAGC kind of STG_U                                    |
+-----------------------------------------------------------------------------*/
static void LP_FCT unstack_tse(
   COM_P M_p,
   TSHDR *tshdr_p,
   ATD *atd_p,
   SRM *srm_p,
   ELT *by_elt_p,
   uchar tagmin,
   uchar impldby)
{
   GA_P gbla_p = M_p->gbla_p;
   uint erh_action = ERH_W_SEV + ERH_LOCATE;
   TSE *tse_p;
   DED ded;
   int count;

   if (!tshdr_p) return;              /* just in case ... */
   for (count = 1+(tshdr_p->p-(tse_p=tshdr_p->stack)); --count >= 0; ++tse_p) {
      ded.is = tse_p->ded_is;         /* Do not merge with next: WATC Bug(?)*/
      if (!ded.is) {                  /* Record end */
#if YASP_RVL_GE(0,1,26)
         tll_RE(gbla_p, &(GA(RE_position)));
#else
         tll_RE(gbla_p, (POS *)0);
#endif
      }else {
         ded.elt_p = tse_p->elt_p;
         ded.srm_p = M_p->tcb_p->srm_p;
         if (ded.is & STG_LF) {       /* start tag */
            if (ded.is & IMP_LF) {    /* Implied: not explicitely entered: */
               ded.tagmin = NO_MNF;   /* Minimization is irrelevant */
               ded.impldby = impldby; /* What it was implied by */
               ded.by_elt_p=by_elt_p; /* Name of the elt */
               ded.atd_p = (ATD *)0;  /* No tag attr could have been passed */
               if (!IS_BITON(GA(opt).ft,OMITTAG_FT)) {
                  errorh(gbla_p,INVSTGOMI,erh_action,ded.elt_p->name.p);
                  erh_action = ERH_W_SEV;
               }else if (ded.elt_p->is.reqd_open_tag) { /* if it had to ...*/
                  errorh(gbla_p,INVSTRTAG,erh_action,ded.elt_p->name.p);
                  erh_action = ERH_W_SEV;
               }
            }else {
               ded.tagmin = tagmin;   /* Take minimization into account */
               ded.impldby = NO_IMF;  /* Was not implied */
               ded.atd_p = atd_p;     /* and the tag attributes */
            }
            /* Check if the element was declared vs. generated by odtcomp */
            if ((ded.elt_p->is.any) && (!ded.elt_p->is.pcdata)) {
               errorh(gbla_p,INVELTGID,erh_action,ded.elt_p->name.p);
               ded.elt_p->is.pcdata = TRUE;    /* fix pcdata */
            }
            if (ded.elt_p->rkl.p) set_rank_suffices(gbla_p,ded.elt_p);
            ded.atd_p = tg_vldat(gbla_p,ded.elt_p,ded.atd_p);
            if ((ded.elt_p->is.conref) && (ded.elt_p->is.empty)) {
               SET_BITON(ded.is, ECR_LF);   /* Element has Explicit CONREF: */
            }
            tll_STG(gbla_p, &ded);
         }else {                      /* end tag */
            if (ded.is & PMT_LF) {    /* premature */
               erh_action += (ERH_E_SEV - ERH_W_SEV);  /* E severity */
               errorh(gbla_p,OOCETG,erh_action,ded.elt_p->name.p);
               erh_action = ERH_W_SEV;
               /* fall thru */
            }
            if (ded.is & IMP_LF) {    /* Implied: not explicitely entered: */
               ded.tagmin = NO_MNF;   /* Minimization is irrelevant */
               ded.impldby = impldby; /* What it was implied by */
               ded.by_elt_p=by_elt_p; /* Name of the elt (if not EOF_IMF) */
               if (!IS_BITON(GA(opt).ft,OMITTAG_FT)) {
                  errorh(gbla_p,INVETGOMI,erh_action,ded.elt_p->name.p);
                  erh_action = ERH_W_SEV;
               }else if (ded.elt_p->is.reqd_end_tag) { /* if it had to ...*/
                  errorh(gbla_p,INVENDTAG,erh_action,ded.elt_p->name.p);
                  erh_action = ERH_W_SEV;
               }
            }else {
               ded.tagmin = tagmin;   /* Take minimization into account */
               ded.impldby = NO_IMF;  /* Was not implied */
            }
            if (ded.is & EMP_LF) {    /* Content was empty and not allowed to */
               errorh(gbla_p,INVEMPCON,erh_action,ded.elt_p->name.p);
               erh_action = ERH_W_SEV;
            }
            ded.atd_p = (ATD *)0;  /* No attributes in other end-tags */
            M_p->last_ended_elt_p = ded.elt_p;
            tll_ETG(gbla_p, &ded);
         }
      }
   }
   if (M_p->is_shortref_in_process) {
      if ((!srm_p) || ((srm_p + M_p->sr_id)->ety_p != M_p->sr_ety_p)) {
         errorh(
            gbla_p,INVREMAP,ERH_W_SEV+ERH_LOCATE,
            M_p->sr_id + 1, M_p->sr_ety_p->name._.p
         );
      }
   }
   free(tshdr_p);
}

/*--------------------------------------------------------------------stw_base-+
| Imply the start-tag for the document element.                                |
|                                                                              |
| Given [10], [11], [12] and [13], if the document instance set does not       |
| start with a STAGO or a STAGO_TAGC, the start-tag for the document element   |
| must be implied.                                                             |
+-----------------------------------------------------------------------------*/
void P_FCT stw_base(COM_P M_p)
{
   TSHDR *tshdr_p = (TSHDR *)0;       /* to stack STG, ETG, RE */
   ELT *elt_p = M_p->tcb_stack[0].elt_p->mdl.p->l.elt.p; /* base element */
   SRM  *prev_srm_p;

   mdl_goto(M_p->tcb_p, elt_p);       /* Must always return MATCH_MS */
   prev_srm_p = M_p->tcb_p->srm_p;
   start_elt(M_p,&tshdr_p,REQRD_MS);
   unstack_tse(M_p, tshdr_p, (ATD *)0, prev_srm_p, (ELT *)0, NO_MNF, DOC_IMF);
   adjust_rcgn_mode(M_p);
}

/*--------------------------------------------------------------------stw_otcb-+
| Register a change in the tag structure, when opening new elements.           |
|                                                                              |
| If something's wrong, restore the environment (COMMON) and return FALSE.     |
| If everything is fine, return TRUE.                                          |
|                                                                              |
| Note:                                                                        |
| An element with a #CONREF attribute can never be EMPTY when declared.        |
| It becomes EMPTY if a CONREF attribute is specified (a CONREF attribute      |
| is an IMPLIABLE attribute).                                                  |
| If a CONREF attribute has been specified, elt_p->is.empty is                 |
| artificially (and temporary) forced to TRUE (see 4.53): return-code 2 of     |
| tg_parse() signals this event.  The elt_p->is.empty bit is reset to FALSE    |
| after all relevant functions (unstack_tse(), tg_vldat()) have been called.   |
+-----------------------------------------------------------------------------*/
boolean P_FCT stw_otcb(COM_P M_p)
{
   GA_P gbla_p = M_p->gbla_p;
   TSHDR *tshdr_p = (TSHDR *)0;    /* to stack STG, ETG, RE */
   ELT *elt_p;
   ATD *atd_p;                     /* copy of the specified attributes if any */
   MS rct;
   uchar ini_state;
   boolean force_empty;
   COMMON *N_p = (COMMON *)(gbla_p->N_p);
   ulong stag_len;
   SRM  *prev_srm_p;
   uchar tagmin;

   /*--------------------------------+
   |Set elt_p pointing the start-tag |
   +--------------------------------*/
   switch (GA(curr_token)) {
   case STAGO_TK:
      stag_len = em_ofs(gbla_p);
      if (!(elt_p=stw_gfgi(gbla_p))) {        /* Get the full GI */
         tg_parse(gbla_p,(ELT *)0,(ATD **)0); /* Get rid of any attrib */
         *(M_p->state_p) = STG_STATE;         /* Discard RE if any */
#if YASP_RVL_GE(0,1,26)
         tll_SKIP(gbla_p,ERROR_SDC);
#endif
         return(FALSE);                       /* Tag was invalid */
      }
      break;
   default: /* case STAGO_TAGC_TK */          /* STAGO_TAGC "<>" */
      /*-------------------------------------------------+
      |7.4.1.1: A GI specification is implied for an     |
      |empty start tag before to determine whether       |
      |any tags were omitted before it                   |
      |a) if (OMITTAG==YES) <> == most recently started  |
      |b) if (OMITTAG==NO)  <> == most recently ended    |
      |c) or the base document element                   |
      +-------------------------------------------------*/
      if (IS_BITON(GA(opt).ft,OMITTAG_FT)) {                 /* 7.4.1.1 a) */
         if (!M_p->tcb_p->is.b.zero) {
            elt_p = M_p->tcb_p->elt_p;
            break;
         }
      }else {
         if ((elt_p = M_p->last_ended_elt_p) != (ELT *)0) {  /* 7.4.1.1 b) */
            break;
         }
      }
      elt_p = M_p->tcb_stack[0].elt_p->mdl.p->l.elt.p;       /* 7.4.1.1 c) */
      break;
   }
   *N_p = *M_p;                               /* Save environment.... */
   ini_state = *(M_p->state_p);

   /*------------------------+
   |Now, try to start the tag|
   +------------------------*/
   for(;;) {
      switch(rct = mdl_goto(M_p->tcb_p, elt_p)) {
      case REQRDX_MS:                   /* New required is invalid exclusion */
         errorh(gbla_p,INVMEX,ERH_W_SEV,(M_p->tcb_p+1)->elt_p->name.p);
         rct = REQRD_MS;
         /* fall thru */
      case REQRD_MS:
         /*-----------------------------------------------------------+
         |In case the requested element content is CDATA or RCDATA    |
         |start and end it, so we will recognize the current (invalid)|
         |start-tag.  Note that start-tag minimization was anyway     |
         |prohibited... Also remember never PCDATA could be REQUIRED. |
         +-----------------------------------------------------------*/
         if ((M_p->tcb_p+1)->elt_p->is.dcdata) {
            start_elt(M_p,&tshdr_p,REQRD_MS);
            end_elt(M_p,&tshdr_p,FALSE);      /* don't tell it's implied */
         }else {                              /* i.e DENY_DCATA */
            start_elt(M_p,&tshdr_p,REQRD_MS);
         }
         continue;                            /* and try again */
      case FINISH_MS:
         end_elt(M_p,&tshdr_p,TRUE);          /* implied */
         break;                               /* check still alive, try again */
      case EXCLUS_MS:
         {
            int i = (M_p->tcb_p+1)->is.all_bits; /* "mex_lvl" */

            while (--i >= 0) {                /* imply end for mex_lvl elts */
               if (!end_elt(M_p,&tshdr_p,TRUE)) { /* if REQ ms (never OOB) */
                  start_elt(M_p,&tshdr_p,REQRD_MS);
                  break;
               }
            }
         }
         break;                               /* check still alive, try again */
      case MATCHX_MS:                         /* OK, but invalid exclusion */
         errorh(gbla_p,INVMEX,ERH_W_SEV,elt_p->name.p);
         rct = MATCH_MS;
         /* fall thru */
      case INCLUS_MS:
      case MATCH_MS:
         force_empty = FALSE;
         /*----------------------------------------------------+
         |For a non-empty start_tag, parse now the attributes: |
         |Save the gotten copy of the ATD array in atd_p.      |
         |If a CONREF attribute makes the element EMPTY,       |
         |do a temporary change in elt_p.                      |
         |Note that "tg_parse" changes curr_token.             |
         +----------------------------------------------------*/
         if (GA(curr_token) == STAGO_TAGC_TK) {
            tagmin = EMPTY_ST_MNF;
            atd_p = (ATD *)0;
         }else {
            if (tg_parse(gbla_p,elt_p,&atd_p)) {      /* CONREF specified */
               elt_p->is.empty = force_empty = TRUE;
            }
            stag_len = em_ofs(gbla_p) - stag_len;
            switch (GA(curr_token)) {
            case NET_TK:
               tagmin = NET_ST_MNF;
               if (!IS_BITON(GA(opt).ft,SHORTTAG_FT)) {  /* rule 7.4.1 */
                  errorh(gbla_p,NOTAGC,ERH_W_SEV+ERH_LOCATE);
               }
               stag_len -= GA(curr_count);
               break;
            case TAGC_TK:
               tagmin = NO_MNF;
               stag_len -= GA(curr_count);
               break;
            default:
               tagmin = UNCL_ST_MNF;
               break;
            }
            if (stag_len > TAGLEN) {
               errorh(gbla_p,BIGTAGLEN,ERH_W_SEV+ERH_LOCATE,stag_len);
            }
         }
         prev_srm_p = M_p->tcb_p->srm_p;
         start_elt(M_p,&tshdr_p,rct);
         unstack_tse(M_p, tshdr_p, atd_p, prev_srm_p, elt_p, tagmin, STG_IMF);
         if (tagmin == NET_ST_MNF) {
            if (!elt_p->is.empty)  {      /* if *NOT* empty */
               M_p->tcb_p->is.b.net_enabling = TRUE;
               M_p->tcb_p->is.b.net_enabled = TRUE;
            }else {
               errorh(gbla_p,INVMTYNET,ERH_W_SEV+ERH_LOCATE);
            }
         }
         if (force_empty) elt_p->is.empty = FALSE; /* CONREF is never EMPTY */
         adjust_rcgn_mode(M_p);
         return(TRUE);
      case INVALID_MS:
         M_p->tcb_p->is.b.zero = TRUE;    /* Trick: commit suicide */
         break;
      }
      if (M_p->tcb_p->is.b.zero) {        /* check we are still alive */
         *M_p = *N_p;                     /* Restore environment.... */
         *M_p->state_p = STG_STATE;       /* discard RE */
         if (tshdr_p) free(tshdr_p);      /* discard current tell stk */
         if (ini_state > 2) {             /* RE was pending ? */
            ini_state = 0;                /* try without RE (could be an "S") */
            tshdr_p = (TSHDR *)0;         /* reset tell_stk */
            continue;                     /* try again */
         }
         errorh(gbla_p,OOCSTG,ERH_W_SEV+ERH_LOCATE);
         if (GA(curr_token) != STAGO_TAGC_TK) {
            tg_parse(gbla_p, elt_p,(ATD **)0); /* Get a rid of any attributes */
         }
#if YASP_RVL_GE(0,1,26)
         /* Flush already done */
         tll_SKIP(gbla_p,ERROR_SDC);
#endif
         return(FALSE);
      }
   }
}

/*--------------------------------------------------------------------stw_dtcb-+
| Set up a convenient TCB structure before to enter DATA (including DATA RE)   |
|                                                                              |
|  1) If DATA is OOC, do not unstack the "tells" to Application                |
|     and restore the environment (COMMON) as it was on entry.                 |
|  2) Otherwise if DATA is OK (MATCH_MS)                                       |
|     unstack the "tells, and return TRUE                                      |
|                                                                              |
|  NOTE: Within this function the CON mode is always set.                      |
|        If on entry the current active element is CDATA or RCDATA,            |
|        return TRUE immediately.                                              |
|        If a DCDATA tag is required, it is opened and closed immediately,     |
|        in conformance with 7.3.1.1 a).                                       |
|        Therefore, there is no need to "adjust_rcgn_mode".                    |
+-----------------------------------------------------------------------------*/
boolean P_FCT stw_dtcb(COM_P M_p)
{
   GA_P gbla_p = M_p->gbla_p;
   TSHDR *tshdr_p = (TSHDR *)0;            /* to stack STG, ETG, RE */
   COMMON *N_p = (COMMON *)(gbla_p->N_p);

   if (M_p->tcb_p->elt_p->is.dcdata) return(TRUE);
   *N_p = *M_p;                            /* Save environment.... */
   for (;;) {
      switch(mdl_goto(M_p->tcb_p, (ELT *)0)) {
      case INVALID_MS:
         *M_p = *N_p;                      /* Restore environment.... */
         if (tshdr_p) free(tshdr_p);       /* discard current tell stk */
         return(FALSE);                    /* DATA is OOC */
      case MATCH_MS:
/*****case MATCHX_MS:              **/     /* cannot exist for PCDATA */
/*****case INCLUS_MS:              **/     /* cannot exist for PCDATA */
         unstack_tse(
            M_p, tshdr_p, (ATD *)0, M_p->tcb_p->srm_p, (ELT *)0,
            NO_MNF, DATA_IMF
         );
         return(TRUE);                     /* Data is now OK */
      /*-------------------------------------------+
      |A start-tag or an end-tag is first requested|
      +-------------------------------------------*/
      case REQRDX_MS:                      /* invalid exclusion found */
         errorh(M_p->gbla_p,INVMEX,ERH_W_SEV,(M_p->tcb_p+1)->elt_p->name.p);
         /* fall thru */
      case REQRD_MS:
         /*----------------------------------------------------------+
         |In case the requested element content is CDATA or RCDATA   |
         |start and end it: start-tag minimization is prohibited.    |
         |(Otherwise, we would have to call adjust_recognition_mode).|
         +----------------------------------------------------------*/
         switch ((M_p->tcb_p+1)->elt_p->is.dcdata) {
         case NONR_DCDATA:
         case RPLC_DCDATA:                  /* see 7.3.1.1 */
            start_elt(M_p,&tshdr_p,REQRD_MS);
            end_elt(M_p,&tshdr_p,FALSE);    /* don't tell it's implied */
            break;
         default:                           /* i.e DENY_DCATA */
            start_elt(M_p,&tshdr_p,REQRD_MS);
            break;
         }
         break;
      default:                           /* FINISH_MS (no MEX for #PCDATA) */
         end_elt(M_p,&tshdr_p,TRUE);     /* implied */
         if (M_p->tcb_p->is.b.zero) {    /* check we are still alive */
            *M_p = *N_p;                 /* Restore environment.... */
            if (tshdr_p) free(tshdr_p);  /* discard current tell stk */
            return(FALSE);               /* Data OOC, and ended document  */
         }
         break;
      }
   }
}

/*--------------------------------------------------------------------stw_etcb-+
| Issue ecnt times "end_elt".                                                  |
| The ecnt-1 first times, is_implied is TRUE.                                  |
| ecnt==0 is special for ETAGO_TAGC                                            |
|                                                                              |
| Note: impldby field is one of ETG_IMF, EDTG_IMF, EOF_IMF                     |
+-----------------------------------------------------------------------------*/
void P_FCT stw_etcb(COM_P M_p, uint ecnt, uchar tagmin, uchar impldby)
{
   TSHDR *tshdr_p = (TSHDR *)0;              /* to stack STG, ETG, RE */
   SRM *prev_srm_p;

   if (!ecnt) {                              /* ETAGO_TAGC */
      prev_srm_p = M_p->tcb_p->srm_p;
      while (!end_elt(M_p,&tshdr_p,FALSE)) { /* end, until one worked */
         start_elt(M_p,&tshdr_p,REQRD_MS);   /* REQRD found */
         prev_srm_p = M_p->tcb_p->srm_p;     /* save map before end tag */
      }
   }else do {
      prev_srm_p = M_p->tcb_p->srm_p;        /* save map before end tag */
      if (!end_elt(M_p, &tshdr_p, (boolean)((ecnt > 1) ? TRUE : FALSE))) {
         start_elt(M_p,&tshdr_p,REQRD_MS);   /* start it */
         ++ecnt;                             /* count one more */
      }else {
         --ecnt;
      }
   }while(ecnt && !M_p->tcb_p->is.b.zero);
   adjust_rcgn_mode(M_p);
   unstack_tse(
      M_p, tshdr_p, (ATD *)0, prev_srm_p, (M_p->tcb_p+1)->elt_p,
      tagmin, impldby
   );
}

/*STATIC-------------------------------------------------------------start_elt-+
| Start an element - if rct == REQRD_MS, the element is implied                |
|                                                                              |
| "mdl_goto" must have been called before to insure the validity:              |
| it set the next valid elt at (tcb_p+1)->elt_p and dtp_p if any.              |
|                                                                              |
| NOTE: Care must be exercised not to confuse:                                 |
|   - an activated EMPTY map (srm_p not 0)                                     |
|   - no map currently activated (srm_p == 0)                                  |
+-----------------------------------------------------------------------------*/
static void LP_FCT start_elt(COM_P M_p, TSHDR **tshdr_pp, MS rct)
{
   /* is_implied = (rct==REQRD_MS); */
   TCB_P tcb_p;
   ELT *elt_p;
   uchar ded_is;
   GA_P gbla_p = M_p->gbla_p;

   M_p->tcb_p->is.b.bad_empty = FALSE;           /* previous tag wasn't empty */
   elt_p = (tcb_p = ++M_p->tcb_p)->elt_p;        /* set by mdl_goto */
   if (++M_p->tcb_stk_ix >= (int)TAGLVL) {
      errorh(M_p->gbla_p,BIGTAGLVL,ERH_T_SEV+ERH_LOCATE,elt_p->name.p);
      /* --M_p->tcb_stk_ix;   If it's desirable to continue,        */
      /* --M_p->tcb_p;        check tcb_stk_ix >= 0 when ending elt */
   }else {
      if (M_p->tcb_stk_ix > M_p->tcb_stk_max) {
         ++M_p->tcb_stk_max;
      }
   }
   tcb_p->is.all_bits = 0;
   ded_is = STG_LF;
   if (rct == INCLUS_MS) {
      ded_is |= PEX_LF;
      tcb_p->is.b.pex = TRUE;
      FG_OTHER(M_p);
      *++M_p->state_p = STG_STATE;
   }else {
      /* tcb_p->is.b.pex = FALSE; (already OFF) */
      FG_STG(M_p);
      if (rct == REQRD_MS) {                     /* if omitted, */
         ded_is |= IMP_LF;                       /* tell it's implied */
         tcb_p->is.b.bad_empty = TRUE;           /* must check empty */
      }
   }
   stack_tse(M_p, tshdr_pp, elt_p, ded_is);      /* signal P/I STG */
   if (elt_p->is.empty) {
      /*-----------------------------------+
      |If the element is empty, end it now,|
      |so it's never "really" tcb_stacked. |
      +-----------------------------------*/
      ded_is = ETG_LF;
      --M_p->tcb_p;
      --M_p->tcb_stk_ix;
      if (rct == INCLUS_MS) {
         ded_is |= PEX_LF;
         --M_p->state_p;
      }else {
         FG_ETG(M_p);                            /* ETG state */
         if (rct == REQRD_MS) ded_is |= IMP_LF;
      }
      stack_tse(M_p, tshdr_pp, elt_p, ded_is);
   }else {
      tcb_p->srm_p = (elt_p->srm.p) ?            /* || (!M_p->tcb_stk_ix)) */
         elt_p->srm.p : (tcb_p-1)->srm_p;        /* propagate shortref map */
      tcb_p->is.b.dtag_enabled =(tcb_p->dtp_p) ? /* set by mdl_goto */
         TRUE : (boolean)((tcb_p-1)->is.b.dtag_enabled);       /* propagate */
      tcb_p->is.b.net_enabled = (tcb_p-1)->is.b.net_enabled;   /* propagate */
      tcb_p->is.b.ms = MATCH_MS;                 /* net_enabling set later */
      tcb_p->grp_p = (tcb_p->grp_p0)-1;          /* current group (dummy) */
      tcb_p->grp_p0->mdl_p = elt_p->mdl.p;       /* current token */
   }
}

/*STATIC---------------------------------------------------------------end_elt-+
| End an element                                                               |
|                                                                              |
| Returns FALSE if unable to, a required element being needed.                 |
+-----------------------------------------------------------------------------*/
static boolean LP_FCT end_elt(COM_P M_p, TSHDR **tshdr_pp, boolean is_implied)
{
   uchar ded_is = ETG_LF;

   switch(mdl_fini(M_p->tcb_p)) {
   case REQRDX_MS:                      /* invalid exclusion found */
      errorh(M_p->gbla_p,INVMEX,ERH_W_SEV,(M_p->tcb_p+1)->elt_p->name.p);
      /* fall thru */
   case REQRD_MS:
      return(FALSE);
   case INVALID_MS:
      ded_is |= PMT_LF;
      break;                           /* end it anyway... is that wrong ? */
   case FINISH_MS:
      break;
   }
   if (is_implied) ded_is |= IMP_LF;
   if (M_p->tcb_p->is.b.pex) {
      ded_is |= PEX_LF;
      --M_p->state_p;                  /* signal_EITG */
   }else {
      FG_ETG(M_p);                     /* ETG state */
      if (M_p->tcb_p->is.b.bad_empty) {   /* omitted and empty */
         ded_is |= EMP_LF;
      }
   }
   stack_tse(M_p, tshdr_pp, M_p->tcb_p->elt_p, ded_is);  /* signal P/I ETG */
   --M_p->tcb_stk_ix;
   --M_p->tcb_p;
   return(TRUE);
}

/*STATIC----------------------------------------------------------flush_buffer-+
|                                                                              |
+-----------------------------------------------------------------------------*/
static void LP_FCT flush_buffer(COM_P M_p, ODBF STACK_BASED *odbf_p)
{
   GA_P gbla_p = M_p->gbla_p;
   register uchar STACK_BASED       *c_p0 = odbf_p->c_p0;
   register uchar STACK_BASED * const c_pM = odbf_p->c_p;
#if YASP_RVL_GE(0,1,26)
   register POS   STACK_BASED * p_p0 = odbf_p->pos_p0;
#endif

   if (c_p0 == c_pM) return;             /* makes it faster */
   M_p->tcb_p->is.b.bad_empty = FALSE;
   for (;;) {
      if (*c_p0) {
#if YASP_RVL_GE(0,1,26)
         tll_DATA(gbla_p,c_p0+1, *c_p0, p_p0++);
#else
         tll_DATA(gbla_p,c_p0+1, *c_p0, (POS *)0);
#endif
         c_p0 += *c_p0;
      }
      if (++c_p0 > c_pM) break;
#if YASP_RVL_GE(0,1,26)
      tll_RE(gbla_p, p_p0++);
   }
   *(odbf_p->c_p = odbf_p->cnt_p = odbf_p->c_p0 = odbf_p->buf) = 0;
   odbf_p->pos_p = odbf_p->pos_p0 = odbf_p->pos;
   odbf_p->pos_p--;
#else
      tll_RE(gbla_p, (POS *)0);
   }
   *(odbf_p->c_p = odbf_p->cnt_p = odbf_p->c_p0 = odbf_p->buf) = 0;
#endif
}

/*--------------------------------------------------------------------stw_xpel-+
|Flush all bufferized data that have been stored before and including          |
|the character noted by "stw_note".                                            |
+-----------------------------------------------------------------------------*/
void P_FCT stw_xpel(COM_P M_p, uchar STACK_BASED *noted_c_p)
{
   GA_P gbla_p = M_p->gbla_p;
   register uchar STACK_BASED *c_p0;
   register uchar STACK_BASED * const c_pM = noted_c_p;
   ODBF STACK_BASED *odbf_p;
   POS  STACK_BASED *p_p0;
   boolean data_told = FALSE;
   int rest;

   /*-------------------------------------------------+
   |Make odbf_p points the ODBF that c_pM belongs to. |
   +-------------------------------------------------*/
   odbf_p = GA(odbf_pC);
   if ((c_pM  >= (uchar STACK_BASED *)GA(odbf_base)[1].buf)
#if (defined(C2_00))  /* C2: ANSI trigraph for XOR is *not* recognized */
      ^
#else
      ??'
#endif
      (odbf_p == (ODBF STACK_BASED *)GA(odbf_base)+1)) {
      odbf_p = odbf_p->next_p;                /* in previous buffer */
   }else {                                    /* in this bf */
      flush_buffer(M_p,odbf_p->next_p);       /* flush previous one */
   }
   /*--------------------+
   |Do the partial flush |
   +--------------------*/
   c_p0 = odbf_p->c_p0;
#if YASP_RVL_GE(0,1,26)
   p_p0 = odbf_p->pos_p0;
#endif
   for (;;) {
      if (*c_p0) {
         if ((rest = ((c_p0 + *c_p0) - c_pM)) > 0) {
            if (c_pM > c_p0) {
               data_told = TRUE;
#if YASP_RVL_GE(0,1,26)
               p_p0->col -= rest;
               p_p0->ofs -= rest;
               tll_DATA(gbla_p,c_p0+1, c_pM - c_p0, p_p0);
               p_p0->col += rest;
               p_p0->ofs += rest;
#else
               tll_DATA(gbla_p,c_p0+1, c_pM - c_p0, (POS *)0);
#endif
               *c_pM = (uchar)rest;  /* C2 V1.1 BUG ? */
            }
            break;
         }else {
            data_told = TRUE;
#if YASP_RVL_GE(0,1,26)
            tll_DATA(gbla_p,c_p0+1, *c_p0, p_p0++);
#else
            tll_DATA(gbla_p,c_p0+1, *c_p0, (POS *)0);
#endif
            c_p0 += *c_p0;
         }
      }
      if (++c_p0 > c_pM) {
         *c_pM = 0;                                     /* C2 V1.1 BUG ? */
         break;
      }
      data_told = TRUE;
#if YASP_RVL_GE(0,1,26)
      tll_RE(gbla_p, p_p0++);
#else
      tll_RE(gbla_p, (POS *)0);
#endif
   }
   odbf_p->cnt_p = odbf_p->c_p0 = c_pM;
#if YASP_RVL_GE(0,1,26)
   odbf_p->pos_p0 = p_p0;
#endif
   if (data_told) M_p->tcb_p->is.b.bad_empty = FALSE;
}

/*--------------------------------------------------------------------stw_flsh-+
|                                                                              |
+-----------------------------------------------------------------------------*/
void P_FCT stw_flsh(COM_P M_p)
{
   GA_P gbla_p = M_p->gbla_p;

   flush_buffer(M_p,GA(odbf_pC)->next_p);
   flush_buffer(M_p,GA(odbf_pC));
}

#ifndef stw_note
/*--------------------------------------------------------------------stw_note-+
|Returns a pointer to the last character entered                               |
|Note: stw_note is #defined by a macro in stower.h                             |
|      otherwise, this would have been the code                                |
+-----------------------------------------------------------------------------*/
uchar * P_FCT stw_note(GA_P gbla_p)
{
   return(GA(odbf_pC)->c_p);
}
#endif

/*--------------------------------------------------------------------stw_init-+
| Create the double buffer on the heap and initialize it:                      |
| Save the addresses in the Global Area                                        |
+-----------------------------------------------------------------------------*/
void P_FCT stw_init(GA_P gbla_p)
{
   ODBF STACK_BASED *odbf_p;

   odbf_p = GA(odbf_pC) = GA(odbf_base);
   (odbf_p->next_p = odbf_p+1)->next_p = odbf_p;
   stw_thro(gbla_p);
}

/*--------------------------------------------------------------------stw_thro-+
|Discard the buffer                                                            |
+-----------------------------------------------------------------------------*/
void P_FCT stw_thro(GA_P gbla_p)
{
   register ODBF STACK_BASED *odbf_p = GA(odbf_pC);

   *(odbf_p->c_p = odbf_p->cnt_p = odbf_p->c_p0 = odbf_p->buf) = 0;
#if YASP_RVL_GE(0,1,26)
   odbf_p->pos_p = (odbf_p->pos_p0 = odbf_p->pos) - 1;
#endif
   odbf_p = odbf_p->next_p;
   *(odbf_p->c_p = odbf_p->cnt_p = odbf_p->c_p0 = odbf_p->buf) = 0;
#if YASP_RVL_GE(0,1,26)
   odbf_p->pos_p = (odbf_p->pos_p0 = odbf_p->pos) - 1;
#endif
}

/*--------------------------------------------------------------------stw_stRE-+
|                                                                              |
+-----------------------------------------------------------------------------*/
void P_FCT stw_stRE(COM_P M_p)
{
   GA_P gbla_p = M_p->gbla_p;
   register ODBF STACK_BASED *odbf_p;

   odbf_p = GA(odbf_pC);
   if (odbf_p->c_p >= (uchar STACK_BASED *)odbf_p->buf + ODBF_SIZE - 1) {
      flush_buffer(M_p, GA(odbf_pC) = odbf_p = odbf_p->next_p);
   }
   *(odbf_p->cnt_p = ++odbf_p->c_p) = 0;
#if YASP_RVL_GE(0,1,26)
   *(++odbf_p->pos_p) = GA(RE_position); /* Note position of RE */
#endif
}

/*--------------------------------------------------------------------stw_data-+
|Put curr_char in the output buffer                                            |
+-----------------------------------------------------------------------------*/
void P_FCT stw_data(COM_P M_p)
{
   GA_P gbla_p = M_p->gbla_p;
   register ODBF STACK_BASED *odbf_p;

   odbf_p = GA(odbf_pC);
   if (odbf_p->c_p >= (uchar STACK_BASED *)odbf_p->buf + ODBF_SIZE - 1) {
      flush_buffer(M_p, GA(odbf_pC) = odbf_p = odbf_p->next_p);
   }
   *(++(odbf_p->c_p)) = (uchar)(GA(curr_char));
#if YASP_RVL_GE(0,1,26)
   if (++*odbf_p->cnt_p == 1) {           /* starting new substring of data */
      ++odbf_p->pos_p;
   }
   em_pos(gbla_p, odbf_p->pos_p, 0);      /* register its position */
#else
   ++*odbf_p->cnt_p;
#endif
}
/******************************************************************************/
