/*******************************************************************************
* 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   = modele.c                                                  *
* DESCRIPTIVE NAME = Check validity of elements against the document model     *
* DATE_WR          = 02/23/1988                                                *
* AUTHOR           = Pierre G. Richard - PETRUS at YKTVMX                      *
*                                                                              *
* FUNCTIONAL DESCRIPTION =                                                     *
*                                                                              *
* Check that, at a given point, a proposal to start or end an element,         *
* or to enter #PCDATA matches the "model tree" -- a binary tree that is built  *
* when decoding the DTD's ELEMENT declaration.                                 *
* To achieve this goal, the history of already entered elements is kept in     *
* a Tag Control Block stack, that states the point we reached in the document  *
* structure.                                                                   *
*                                                                              *
* Given a TCB stack, this code is able to tell if a proposed change in the     *
* document structure is valid or not, and if not, why: for instance, if a      *
* known required element is missing, if some required element is missing       *
* but unable to find out what element, or if an element is out of context.     *
*                                                                              *
* The "model tree" is built in the following manner.  To each "token" is       *
* associated a node.  If the token is a group, then the node is a father       *
* and the child_link points to sub_tokens.  If the token is a GI, the          *
* child_link is used to point the ELT structure.                               *
*                                                                              *
* For a more detailled description, see comments in the code.                  *
*                                                                              *
* IMPLEMENTATION NOTES =                                                       *
*                                                                              *
* This code is a tricky one: don't fiddle with it !                            *
*                                                                              *
* -- HITS bit Computation --                                                   *
* Bits are numbered from 0 to N-1 (C convention)                               *
* Depending on the GRPCNT_MAX quantity, two definitions can be used.           *
* One is for GRPCNT_MAX not exceeding 32, the other for any greater value.     *
* The former is no more used (GRPCNT_MAX being now 100).  It has been          *
* kept here for historical reasons.                                            *
*                                                                              *
* DEPENDENCIES =  (see Include list)                                           *
*                                                                              *
* IMPORT=                                                                      *
*   (none)                                                                     *
*                                                                              *
* EXPORT=                                                                      *
*   "mdl_goto" requires to reach a given elt_node in the model tree            *
*   "mdl_fini" requires to find a way out from the tree.                       *
*   "mdl_edtg" close a datatag when the template matches the data.             *
*                                                                              *
* CHANGE HISTORY =                                                             *
* Number Date     Inits    Change & Reason (brief)                             *
*      1 08/03/88 PETRUS   Try mdl_goto when INVALID_MS: less error messages   *
*      2 10/17/88 PETRUS   Requested elt/ex_depth at tcb_p + 1                 *
*      3 11/06/88 PETRUS   DATATAG feature added                               *
*      4 02/02/89 PETRUS   Merged HIT                                          *
*      5 03/25/89 PETRUS   Reworked check_exceptions (C2 compiler bug if -Ol)  *
*      6 11/25/89 PETRUS   New strategy for AND connection.                    *
*      7 11/22/90 PETRUS   Inclusions get over even finished tcb (mdl_goto)    *
*      8 06/01/91 PETRUS   GRPCNT_MAX > 32 tested and optimized                *
*      9 11/02/91 PETRUS   next_unhit typo caused troubles in "(... & TAG?)"   *
*******************************************************************************/

/*-------------+
|Include Files |
+-------------*/
#include "machine.h"
#include "portable.h"
#include "yaspapi.h"
#include "global.h"
#include "usrlib.h"
#include "sgml.h"
#include "modele.h"

/*-------------------+
|Local data and types|
+-------------------*/
typedef enum {
   NONE_NTK,
   AFTER_HIT_NTK,
   FLUSH_NTK
}NTK;

/*---------------------------+
|Local Functions and In-Line |
+---------------------------*/
#define IS_CONN(val_conn) (grp_p->mdl_p->connector == val_conn)
#define IS_OMITTABLE      ((grp_p+1)->mdl_p->occurence & OMITTABLE)
#define IS_REPEATABLE     ((grp_p+1)->mdl_p->occurence & REPEATABLE)
#define IS_LAST_TOK       (grp_p->ix == (uint)(grp_p->mdl_p->l.mdl.num  - 1))
#define IS_ANY_HIT        IS_HITS(grp_p->hits)
#define IS_TOK_HIT        IS_HITN(grp_p->hits,grp_p->ix)
#define HIT_TOK           SET_HITN(grp_p->hits,grp_p->ix)

#if (defined(C2_00))      /* IBM C2: optimizer bug */
#define SKIP_TO_TOK(i) \
   grp_p->ix = (i);\
   (grp_p+1)->mdl_p = grp_p->mdl_p + grp_p->mdl_p->l.mdl.rx + grp_p->ix
#else
#define SKIP_TO_TOK(i) \
   (grp_p+1)->mdl_p = grp_p->mdl_p + grp_p->mdl_p->l.mdl.rx + (grp_p->ix = (i))
#endif

static boolean LP_FCT is_opt_tok       (GRP_P, GRP_P, boolean);
static boolean LP_FCT is_req_tok       (GRP_P, GRP_P, boolean);
static boolean LP_FCT check_hit_bits   (GRP_P, boolean);
static void    LP_FCT next_token       (TCB_P, GRP_P, GRP_P, NTK const);
static int     LP_FCT check_exceptions (ELT *, TCB_P);
static int     LP_FCT next_unhit       (GRP_P, uint);

#if (GRPCNT_MAX <= 32)
/*================================================ GRPCNT_MAX <= 32 ==========*/
#define IS_HITN(hits,n)  ((boolean)((HITS)(hits) & (0x1L<<(n)) ? TRUE : FALSE))
#define SET_HITN(hits,n) ((hits) |= (0x1L<<(n)))
#define RESET_HITS(hits) (hits) = (HITS)0
#define IS_HITS(hits)    ((boolean)((hits) != (HITS)0))

/*STATIC------------------------------------------------------------next_unhit-+
| Returns the index of the first unset bit in the group pointed by "grp_p",    |
| after and including the caller's "first" bit.                                |
| If all bits are set, it returns -1;                                          |
+-----------------------------------------------------------------------------*/
static int LP_FCT next_unhit(
   GRP_P grp_p,                          /* group */
   register uint first)                  /* Index of first bit to be tested */
{
   register HITS hits = grp_p->hits;     /* Hits to be tested */
   uint last = grp_p->mdl_p->l.mdl.num;  /* Index of last bit to be tested */

   hits >>= first;
   while (first < last) {
      if (!(hits & 0x01)) return(first);
      ++first;
      hits >>= 1;
   }
   return(-1);
}

#else
/*================================================ GRPCNT_MAX > 32 ===========*/
#define IS_HITN(hits,n)  ((*((uchar *)(hits)+((n)>>3))) & ((1<<((n)&0x07))))
#define SET_HITN(hits,n) ((*((uchar *)(hits)+((n)>>3))) |=((1<<((n)&0x07))))
#define RESET_HITS(hits) memset(hits,0x00,sizeof(HITS));
#define IS_HITS(hits)    (hits_any(hits))

static boolean LP_FCT hits_any  (HITS);
/*STATIC--------------------------------------------------------------hits_any-+
| Check any hit bit is on                                                      |
+-----------------------------------------------------------------------------*/
static boolean LP_FCT hits_any(HITS hits)
{
   uint max = 1+sizeof(HITS);

   while (--max) if (*(hits++)) return(TRUE);
   return(FALSE);
}

/*STATIC------------------------------------------------------------next_unhit-+
| Returns the index of the first unset bit in the group pointed by "grp_p",    |
| after and including the caller's "first" bit.                                |
| If all bits are set, it returns -1;                                          |
+-----------------------------------------------------------------------------*/
static int LP_FCT next_unhit(
   GRP_P grp_p,                          /* group */
   register uint first)                  /* Index of first bit to be tested */
{
   uint last = grp_p->mdl_p->l.mdl.num;  /* Index of last bit to be tested */

   while (first < last) {
      if (!IS_HITN(grp_p->hits,first)) return(first);
      ++first;
   }
   return(-1);
}
#endif
/*============================================================================*/

/*STATIC------------------------------------------------------------is_opt_tok-+
| Return TRUE if current token is contextually optional, FALSE otherwise.      |
|                                                                              |
| see ISO8879-1986(E), page 8:                                                 |
| 4.59 "contextually optional element"                                         |
| 4.60: "contextually optional token"                                          |
|                                                                              |
| Contextually optional element:                                               |
|   1) a plus inclusion; or                                                    |
|   2) currently applicable model token is a "contextually optional token":    |
|      i.e. a content token that                                               |
|      a) has an inherently optional token; or                                 |
|      b) has a PLUS occurence indicator and has been satisfied; or            |
|      c) is in a model group that is itself a contextually optional token,    |
|         no tokens of which has been statisfied.                              |
|                                                                              |
| Note: the "inherently optional" rule has been already applied at DTD compile |
|       time.                                                                  |
+-----------------------------------------------------------------------------*/
static boolean LP_FCT is_opt_tok(
   register GRP_P grp_p,
   GRP_P grp_p0,
   boolean mhit)               /* Hit bit of the element model */
{
   for(;;) {                   /* Faster, but cannot use IS_OMITTABLE !!! */
      if (grp_p->mdl_p->occurence & OMITTABLE) return(TRUE);   /* Rule a) */
      if (--grp_p < grp_p0)                    return(mhit);   /* Rule b) */
      if IS_TOK_HIT                            return(TRUE);   /* Rule b) */
      if IS_ANY_HIT                            return(FALSE);  /* Rule c) */
   }
}

/*STATIC------------------------------------------------------------is_req_tok-+
| Return TRUE if current elmnt is contextually required, FALSE otherwise.      |
|                                                                              |
| see ISO8879-1986(E), page 8:                                                 |
| 4.61 "contextually required element"                                         |
| 4.62: "contextually required token"                                          |
|                                                                              |
| Not a contextually optional element; and                                     |
|   1) gi of the Document Type Name; or                                        |
|   2) currently applicable model token is a "contextually required token":    |
|      i.e. a content token that                                               |
|      a) is the only one in its model group; or                               |
|      b) is a member of a "seq" group                                         |
|         i) that                                                              |
|            - is itself a contextually required token; or                     |
|            - contains a token which has been satisfied;                      |
|            and                                                               |
|         ii) all preceding tokens of which                                    |
|            - have been satisfied; or                                         |
|            - are contextually optional.                                      |
|                                                                              |
| Note: "b) ii" will be achieved by next_token().                              |
+-----------------------------------------------------------------------------*/
static boolean LP_FCT is_req_tok(
   register GRP_P grp_p,
   GRP_P grp_p0,
   boolean mhit)               /* Hit bit of the element model */
{
   if (is_opt_tok(grp_p,grp_p0,mhit)) return(FALSE);
   for(;;) {
      if (--grp_p < grp_p0)    return(TRUE);
      if (!IS_CONN(SEQ_CONN))  return(FALSE);
      if IS_ANY_HIT            return(TRUE);
   }
}

/*STATIC--------------------------------------------------------check_hit_bits-+
| Returns TRUE if all hit bits for the specified group are turned on,          |
| (other than those that correspond to optional tokens if "opt" is FALSE).     |
| Otherwise, returns FALSE.                                                    |
| grp_p points the group header to be checked against.                         |
+-----------------------------------------------------------------------------*/
static boolean LP_FCT check_hit_bits(
   GRP_P grp_p,               /* mother group pointer */
   boolean opt)               /* TRUE: optional tokens must be hit */
{
   MDL *mdl_p = grp_p->mdl_p; /* point model node */
   int last;
   register int i;            /* "register" avoids an optimizer bug in IBM/C2 */

   last = mdl_p->l.mdl.num;   /* Ending index (number of bits to test). */

   for (mdl_p += mdl_p->l.mdl.rx, i=-1; ++i < last; ++mdl_p) {
      if (!IS_HITN(grp_p->hits,i) && (opt || !(mdl_p->occurence & OMITTABLE))) {
         return(FALSE);
      }
   }
   return(TRUE);
}

/*STATIC------------------------------------------------------check_exceptions-+
| See if an element is in a plus or minus exception group on the stack.        |
| This function must be called for real tags (neither PCDATA, or datatag).     |
|                                                                              |
| Returns: > 0 if in a minus group                                             |
|              --this is also "ex_depth", (stack depth level of minus group)   |
|           -1 if in a plus group, otherwise                                   |
|            0 if in neither                                                   |
+-----------------------------------------------------------------------------*/
static int LP_FCT check_exceptions(
   ELT *elt_p,                /* element to look for */
   TCB_P tcb_p0)              /* TCB stack */
{
   register EX *ex_p;
   register TCB_P tcb_p;
   int i;                   /* +i= found at level i (0 < i <= tcb_stack_ix) */

   /*------------------------------------------+
   |First check in the minus groups of all TCBs|
   +------------------------------------------*/
   for (tcb_p=tcb_p0, i=1; !tcb_p->is.b.zero; --tcb_p, ++i) {
      if ((ex_p = tcb_p->elt_p->mex.p) != (EX *)0) {
         do {
            if (ex_p->p == elt_p) return(i);
         }while ((++ex_p)->p);
      }
   }
   /*------------------------------------------+
   |Then check in the plus groups of all TCBs  |
   +------------------------------------------*/
   for (tcb_p=tcb_p0; !tcb_p->is.b.zero; --tcb_p) {
      if ((ex_p = tcb_p->elt_p->pex.p) != (EX *)0) {
         do {
            if (ex_p->p == elt_p) return(-1);
         }while ((++ex_p)->p);
      }
   }
   /*-----------------------+
   |Nothing found: return 0 |
   +-----------------------*/
   return(0);
}

/*STATIC------------------------------------------------------------next_token-+
| Find the next token to test, set new "grp_p" in the tcb.                     |
| "ntk" the new_token code is a one of:                                        |
|      - FLUSH: don't make stops at each possible token                        |
|      - AFTER_HIT: assume the current token was hit                           |
|      - NONE: None of these                                                   |
|                                                                              |
| Set "ms" in the TCB to:                                                      |
|      FINISH_MS  if element has ended (no more tokens to test);               |
|      INVALID_MS if a new token cannot be found because current token         |
|                 (which was not hit) was neither required nor optional;       |
|                 (see ISO8879-1986(E) 4.61, NOTE)                             |
|                                                                              |
| In addition if FLUSH is off:                                                 |
|      MATCH_MS  if a new token was found, or a previous token                 |
|                could be repeated;                                            |
|                                                                              |
| The current token will either be a group header,                             |
| or some token that could not end its group.                                  |
+-----------------------------------------------------------------------------*/
static void LP_FCT next_token(
   TCB_P tcb_p,
   register GRP_P grp_p,
   GRP_P grp_p0,
   NTK const ntk)         /* new_token_code: see comment in the header */
{
   int nextsib;           /* Position of the next sibling */
   boolean is_all_hit;    /* TRUE if all non-optionals were hit */

   /*---------------------------------------------------+
   |If the GI was a hit, turn on the hit bit.           |
   |If the current token is repeatable, exit so it will |
   |stand as the next token to test (note at 11.2.4).   |
   +---------------------------------------------------*/
   if (ntk == AFTER_HIT_NTK) {
      if (grp_p < grp_p0) {
         tcb_p->is.b.mhit = TRUE;
      }else {
         HIT_TOK;
      }
      if IS_REPEATABLE {
         tcb_p->is.b.ms = MATCH_MS;
         tcb_p->grp_p = grp_p;
         return;
      }
   }
   /*-----------------------------------------------------------------+
   |To find the next token, we will try first to end the current group|
   |and any we can that contain it.                                   |
   +-----------------------------------------------------------------*/
   while (grp_p >= grp_p0) {
      if ((!IS_ANY_HIT)
         && (IS_LAST_TOK || ((!IS_OMITTABLE) && IS_CONN(SEQ_CONN)))) {
         /*------------------------------------------------------+
         |It's worthless to further scan in the current group if |
         |nothing was hit and we reached the last token, or, for |
         |a SEQ group, we reached the first non-optional token.  |
         |Leave this group to try to pursue in the model.        |
         +------------------------------------------------------*/
         --grp_p;
         continue;
      }
      /*----------------------------------------------------------+
      |The current group can be safely closed                     |
      |(i.e. no more token in it could be a candidate) if:        |
      |- in an OR group, the current token was hit                |
      |- in a SEQ group, the current token is the last and was    |
      |  either hit, or is omittable                              |
      |- in an AND group, all tokens were hit (excluding optional |
      |  tokens if FLUSH)                                         |
      +----------------------------------------------------------*/
      switch (grp_p->mdl_p->connector) {
      case OR_CONN:
         is_all_hit = IS_TOK_HIT ? TRUE : FALSE;
         break;
      case SEQ_CONN:
         is_all_hit = (IS_LAST_TOK &&(IS_OMITTABLE||IS_TOK_HIT)) ? TRUE : FALSE;
         break;
      case AND_CONN:
         is_all_hit = check_hit_bits(grp_p,(boolean)(ntk != FLUSH_NTK));
         break;
/************* DISCARDED: Used during development only...
      default: exit(?);   -- internal error --
*************/
      }
      if (is_all_hit) {
         /*---------------------------------------------------------+
         |If all non-optional tokens in the group were hit, leave   |
         |the group (successfully).  Set the hit bit.               |
         |If we were called from "mdl_goto", and if the group is    |
         |repeateable, return: the same group is again a candidate. |
         |Otherwise, "mdl_fini" is trying to find a way out: loop.  |
         +---------------------------------------------------------*/
         if (--grp_p < grp_p0) {
            tcb_p->is.b.mhit = TRUE;
         }else {
            HIT_TOK;
         }
         if ((ntk != FLUSH_NTK) && IS_REPEATABLE) {
            tcb_p->is.b.ms = MATCH_MS;
            tcb_p->grp_p = grp_p;
            return;
         }
         continue;
      }

      /*----------------------------------------------------------------+
      |At this point, the "current token" is either the original one,   |
      |or the token of the highest level unhit group that it ended.     |
      |If we were called from "mdl_fini", and the element is optional   |
      |we can jump over.  Otherwise, it's INVALID.                      |
      |For an AND group, it's immediately invalid as we already skipped |
      |non-optional tokens.                                             |
      +----------------------------------------------------------------*/
      if (ntk == FLUSH_NTK) {
         if ((IS_CONN(AND_CONN)) ||
            (!is_opt_tok(grp_p+1, grp_p0, (boolean)tcb_p->is.b.mhit))) {
            tcb_p->is.b.ms = INVALID_MS; /* No more group to end, return */
            tcb_p->grp_p = grp_p;
            return;
         }
         SKIP_TO_TOK(grp_p->ix + 1);
         continue;                                /* and loop */
      }
      /*---------------------------------------------+
      |Called from "mdl_goto".  Get the next token.  |
      +---------------------------------------------*/
      if IS_CONN(AND_CONN) {
         if ((ntk == AFTER_HIT_NTK)||(IS_TOK_HIT)) {
            /*------------------------------------+
            |Back to an AND group after a hit:    |
            |either because if was just hit and   |
            |is not repeatable, or it was a       |
            |repeatable token hit before.         |
            |                                     |
            |restart from the first unhit token   |
            |Note: next_unhit will not returns -1,|
            |or group would have ended            |
            +------------------------------------*/
            SKIP_TO_TOK(next_unhit(grp_p,0));
            tcb_p->is.b.ms = MATCH_MS;
            tcb_p->grp_p = grp_p;
            return;
         }
         /*------------------------------------+
         |And group, but no hit:               |
         |the next HIT token is the next unhit.|
         +-------------------------------------*/
         if ((nextsib = next_unhit(grp_p,grp_p->ix+1))!= -1) {
            SKIP_TO_TOK(nextsib);
            tcb_p->is.b.ms = MATCH_MS;
            tcb_p->grp_p = grp_p;
            return;
         }
         /*-------------------------------------------+
         |If no next_unhit (AND group exhausted)      |
         |and if some non-optional token were not hit,|
         |it's definitively INVALID.                  |
         +-------------------------------------------*/
         if (!check_hit_bits(grp_p,FALSE)) {      /* some remains.. */
            tcb_p->is.b.ms = INVALID_MS;          /* definitively INVALID */
            tcb_p->grp_p = grp_p;
            return;
         }
         /*------------------------------------------------+
         |End the group if all non-optional token were hit.|
         |If the group is repeateable, return:             |                            |
         |the same group is again a candidate.             |
         |Otherwise, pursue the quest.                     |
         +------------------------------------------------*/
         if (--grp_p < grp_p0) {
            tcb_p->is.b.mhit = TRUE;
         }else {
            HIT_TOK;
         }
         if IS_REPEATABLE {
            tcb_p->is.b.ms = MATCH_MS;
            tcb_p->grp_p = grp_p;
            return;
         }
         continue;
      }
      /*------------------------------------------------------------------+
      |The next valid token is the next sequential one if current token:  |
      |- is omittable,                                                    |
      |- or was hit (i.e., because it is repeatable and was hit by a      |
      |  previous GI or because it is a hit group that just ended),       |
      |- or is in an OR group (it is not the last testable token,         |
      |  because group would have ended)                                  |
      +------------------------------------------------------------------*/
      if (IS_OMITTABLE || IS_CONN(OR_CONN) || IS_TOK_HIT ) {
         SKIP_TO_TOK(grp_p->ix+1);
         tcb_p->is.b.ms = MATCH_MS;
      }else {
         tcb_p->is.b.ms = INVALID_MS;
      }
      tcb_p->grp_p = grp_p;
      return;
   }
   /*----------------------------------------------------------+
   |The model is now exhausted: return FINISH_MS or INVALID_MS |
   +----------------------------------------------------------*/
   if ((tcb_p->is.b.mhit) || IS_OMITTABLE) {
      tcb_p->is.b.ms = FINISH_MS;   /* hit, or not hit but allowed to */
   }else {
      tcb_p->is.b.ms = INVALID_MS;
   }
   tcb_p->grp_p = grp_p;
   return;
}

/*--------------------------------------------------------------------mdl_goto-+
| Determine whether a GI is valid in the present element model.                |
|                                                                              |
| On entry,                                                                    |
|    tcb_p->grp_p   points the current model group                             |
|    tcb_p->grp_p+1 points the current token                                   |
|    elt_p points the ELT of the gi newly entered (to be check against)        |
|          or is a NULL pointer if DATA has been entered (is PCDATA allowed ?) |
|                                                                              |
| When return:                                                                 |
|    If the return code is MATCH_MS, INCLUS_MS or REQRD_MS:                    |
|    (tcb_p+1)->elt_p has the new elt pointer                                  |
|    If the elt_p is a "target elt" (see 4.315)                                |
|    (tcb_p+1)->dtp_p has the data_tag pattern.                                |
|                                                                              |
|    tcb_p->grp_p+1 is up to the next token                                    |
|    tcb_p->is.b.ms reflects next token status:                                |
|                   MATCH_MS, FINISH_MS, or INVALID_MS                         |
| Return codes:                                                                |
|    MATCH_MS    if valid,                                                     |
|    MATCHX_MS   if valid, but appears invalidly in the model exclusion list,  |
|    MATCHT_MS   if the current PCDATA is also a TOSL in the model,            |
|    INCLUS_MS   is valid as an inclusion (plus exception),                    |
|    FINISH_MS   if element model is finished,                                 |
|    REQRD_MS    if a different element is required,                           |
|                (tcb_p+1)->elt_p has the requested elt pointer,               |
|    REQRDX_MS   as above, but the required element appears invalidly          |
|                in the exclusion list,                                        |
|    INVALID_MS  if it is totally invalid,                                     |
|    EXCLUS_MS   if invalid as an exclusion (minus exception),                 |
|                (tcb_p+1)->all_bits has the MEX level.                        |
|                                                                              |
| NOTE 1:                                                                      |
|    "tcb_p->elt_p" can never be EMPTY, #PCDATA, DCDATA:                       |
|    - for EMPTY, caller opens and closes the element: not tcb-stacked         |
|    - #PCDATA is never tcb_stacked,                                           |
|    - in case of CDATA or RCDATA, this procedure is not called:               |
|       - it cannot be a start-tag, as it is not recognized,                   |
|       - DATA within DCDATA must be trapped by the caller (this is a          |
|         requirement).                                                        |
| NOTE 2:                                                                      |
|    The EXCLUDE validation service is provided thru the MATCHX and REQRDX     |
|    return codes (see ISO 8879-1986(E) p.42, 11.2.5.2, b)                     |
|    MATCHX_MS cannot be returned for #PCDATA  ((ELT *)0 )                     |
+-----------------------------------------------------------------------------*/
MS P_FCT mdl_goto(
   TCB_P tcb_p,            /* Current tag control block */
   ELT *elt_p)             /* ELT of new GI (if (ELT *)0: #PCDATA) */
{
   register GRP_P grp_p;
   GRP_P grp_p0 = tcb_p->grp_p0;
   MDL *mdl_p;
   int  ex_depth;                       /* >0 => MEX; -1 => PEX; 0 => none */

   (tcb_p+1)->elt_p = elt_p;            /* Assume it's OK */
   (tcb_p+1)->dtp_p = (DTP*)0;          /* Assume no target found */
   ex_depth = elt_p ? check_exceptions(elt_p,tcb_p) : 0;
   if (tcb_p->elt_p->is.any) {
      if (ex_depth > 0) {
         (tcb_p+1)->is.all_bits = ex_depth; /* let's caller know */
         return(EXCLUS_MS);
      }else {
         return(MATCH_MS);              /* ANY has no model -> not a target */
      }
   }else {
      if (tcb_p->is.b.ms == FINISH_MS) {
         return((ex_depth < 0) ? INCLUS_MS : FINISH_MS);
      }
   }                                        /**/
   do {
      grp_p = tcb_p->grp_p;                 /* get current group */
      /*-----------------------------------------------------+
      |Open enough groups until we meet a true "model elt"   |
      |Get it in "mdl_p".                                    |
      +-----------------------------------------------------*/
      while ((grp_p+1)->mdl_p->connector) { /* if current token is a group */
         ++grp_p;                           /* make it the current group */
         RESET_HITS(grp_p->hits);           /* open it */
         SKIP_TO_TOK(0);                    /* start at token 0 */
      }
      mdl_p = (grp_p+1)->mdl_p;
      /*-----------------------------------------------------+
      |Check if "given elt" matches "model elt":             |
      |If so, returns MATCH_MS                               |
      |However, if it is a minus exception, return EXCLUS_MS.|
      +-----------------------------------------------------*/
      if ((elt_p == mdl_p->l.elt.p) ||                /* Hit in model */
         ((!elt_p)&&(mdl_p->is_datatag))) {           /* or PCDATA && datatag */
         if ((ex_depth > 0) &&                        /* if in minus grp */
            ((mdl_p->occurence & OMITTABLE)|| IS_CONN(OR_CONN))) {
            (tcb_p+1)->is.all_bits = ex_depth;        /* let's caller know */
            next_token(tcb_p, grp_p, grp_p0, AFTER_HIT_NTK);
            return(EXCLUS_MS);                        /* Valid exclusion */
         }                                            /* else Invalid exclus */
         if (mdl_p->is_target) (tcb_p+1)->dtp_p = (mdl_p+1)->l.dtp.p;
         next_token(tcb_p, grp_p, grp_p0, AFTER_HIT_NTK);
         if (ex_depth > 0)        return(MATCHX_MS);  /* report invalid excl.*/
         else                     return(MATCH_MS);   /* vanilla match */
      }
      /*-----------------------------------------------------+
      |If "given elt" doesn't match "model elt",             |
      |see if "given elt" is in the PLUS list.               |
      |Returns INCLUS_MS if so.                              |
      |                                                      |
      |Otherwise, see if current "model elt" is required:    |
      |Returns REQRD_MS if so.                               |
      |                                                      |
      |Notes: - when #PCDATA is found and the "model elt"    |
      |         is a target elt, it is required (not in ISO) |
      |       - a #PCDATA "model elt" is never required      |
      |         (see ISO 8879-1986, 11.2.4.2)                |
      +-----------------------------------------------------*/
      if (ex_depth < 0) return(INCLUS_MS);               /* hit in plus grp */
      if ((is_req_tok(grp_p+1,grp_p0,(boolean)tcb_p->is.b.mhit))
         || ((!elt_p)&&(mdl_p->is_target))) {            /* If it's required */
         (tcb_p+1)->elt_p = elt_p = mdl_p->l.elt.p;      /* let's caller know */
         ex_depth = check_exceptions(elt_p,tcb_p);       /* check exceptions */
         if (mdl_p->is_target) (tcb_p+1)->dtp_p = (mdl_p+1)->l.dtp.p;
         next_token(tcb_p, grp_p, grp_p0, AFTER_HIT_NTK);
         return(ex_depth > 0 ? REQRDX_MS : REQRD_MS);
      }
      /*-----------------------------------------------------+
      |The "model elt", that doesn't match "given elt" is    |
      |not required: try jump over, and loop if succesfull.  |
      |But, first at all, check if "given elt" is on the red |
      |list.                                                 |
      +-----------------------------------------------------*/
      if (ex_depth > 0) {                             /* Hit in min grp */
         (tcb_p+1)->is.all_bits = ex_depth;           /* let's caller know */
         return(EXCLUS_MS);
      }
      next_token(tcb_p, grp_p, grp_p0, NONE_NTK);

   }while (tcb_p->is.b.ms == MATCH_MS);           /* MATCH: loop */
   return((MS)tcb_p->is.b.ms);                    /* INVALID or FINISH: leave */
}

/*--------------------------------------------------------------------mdl_fini-+
| Determine whether the current element can be ended, or whether               |
| non-optional tokens remain at the current level or higher.                   |
|                                                                              |
| Returns:                                                                     |
|    FINISH_MS  if element can be and has ended,                               |
|    REQRD_MS   if an element is required before to end this one,              |
|    INVALID_MS if element cannot be ended, due to a non-optional non-required |
+-----------------------------------------------------------------------------*/
MS P_FCT mdl_fini(register TCB_P tcb_p)         /* Current tag control block */
{
   /*----------------------------------------------+
   |If the element is one of RCDATA, CDATA or ANY, |
   |or if the tcb rc shows the element is ended,   |
   |returns FINISH_MS.                             |
   |Note: the tag cannot be #PCDATA, nor EMPTY:    |
   |      a tcb is not created for those           |
   +----------------------------------------------*/
   if (((tcb_p->elt_p->is.dcdata)||(tcb_p->elt_p->is.any)) ||
      (tcb_p->is.b.ms == FINISH_MS)) {
      return(FINISH_MS);
   }
   next_token(tcb_p,tcb_p->grp_p,tcb_p->grp_p0, FLUSH_NTK);
   if (tcb_p->is.b.ms == FINISH_MS) return(FINISH_MS);

   /*----------------------------------------------------------+
   |To check if a required element is missing, call mdl_goto,  |
   |pretending a dummy element started, and check if REQ is on.|
   |Note that our dummy element must successfully pass the     |
   |"check_exception" test, or no check for REQ would be done: |
   |that why we took the #PCDATA element.                      |
   +----------------------------------------------------------*/
   tcb_p->is.b.ms = MATCH_MS;
   switch (mdl_goto(tcb_p,(ELT *)0)) {   /* (ELT *)0 == PCDATA */
   case REQRD_MS:                /* required element found */
      return(REQRD_MS);
   case REQRDX_MS:
      return(REQRDX_MS);         /* required is also invalid exclusion */
   default:
      return(INVALID_MS);
   }
}

/*--------------------------------------------------------------------mdl_edtg-+
| End a datatag: asynchronous entry to next_token, after the content satisfied |
| a template.                                                                  |
| Trick: don't tell next_token "PCDATA" was hit: it was so, anyway,            |
| at the time the first character was entered (after the target) -             |
| this allows us to jump over the PCDATA (repeatable) token                    |
+-----------------------------------------------------------------------------*/
void P_FCT mdl_edtg(TCB_P tcb_p)
{
   next_token(tcb_p,tcb_p->grp_p,tcb_p->grp_p0, NONE_NTK);
}
/******************************************************************************/
