/*******************************************************************************
* 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 / DTD compiler                                       *
* STATUS           = Version 0, Release 1, Level 36 and up                     *
* COMPONENT NAME   = odtelem.c                                                 *
* DESCRIPTIVE NAME = ELEMENT declaration / build the ELT and MDL arrays        *
* DATE_WR          = 08/07/1987                                                *
* AUTHOR           = Pierre G. Richard - PETRUS at YKTVMX                      *
*                                                                              *
* FUNCTIONAL DESCRIPTION =                                                     *
*                                                                              *
* IMPLEMENTATION NOTE =                                                        *
*                                                                              *
* DEPENDENCIES =  (see Include list)                                           *
*                                                                              *
* IMPORT=                                                                      *
*                                                                              *
* EXPORT=                                                                      *
*                                                                              *
* CHANGE HISTORY =                                                             *
* Number Date     Inits    Change & Reason (brief)                             *
*      1 03/21/88 PGR      Added inherently optional recognition               *
*      2 04/09/88 PGR      Removed test for non ref elmt, base elemt           *
*                          Allow duplicate element definition (last ignored)   *
*                          Assume "O O ANY" for non defined element            *
*      3 11/06/88 PGR      Added the DATATAG feature                           *
*      4 09/11/89 PGR      if DATATAG in model, element is.pcdata (11.2.4.4)   *
*      5 10/13/89 PGR      Implemented ambiguities                             *
*      6 12/29/89 PGR      Check exception duplicates                          *
*      7 02/21/90 PGR      Rank Feature                                        *
*      8 12/16/90 PGR      Check for duplicate rank-stem and element           *
*      9 04/01/97 ESPERT   Added ELEMENT declaration reporting                 *
*******************************************************************************/

/*-------------+
|Include Files |
+-------------*/
#include "machine.h"
#include "portable.h"
#include "yaspapi.h"
#include "global.h"
#include "usrlib.h"
#include "sgml.h"
#include "hash.h"
#include "odt.h"
#include "gtype.h"
#include "teller.h"

/*--------------+
|Local Functions|
+--------------*/
static CHOICES const * _loadds LP_FCT adr_eldc_choices(void);
static uint    LP_FCT get_occurence_indic (GA_P);
static MNODE * LP_FCT alloc_mdl_node      (COMMON_P);
static void    LP_FCT new_mdl_link        (COMMON_P);
static void    LP_FCT set_mnode           (COMMON_P);
static void    LP_FCT decode_model_group  (COMMON_P);
static int     P_FCT  tpl_comp            (int, int, void *);
static void    P_FCT  tpl_exchg           (int, int, void *);
static void    LP_FCT decode_datatag_group(COMMON_P);
static void    LP_FCT undo_mdl            (COMMON_P, MNODE *);
static void    P_FCT  install_elt         (void STACK_BASED *);
static void    P_FCT  install_excpt       (void STACK_BASED *);

/*----------------+
|Local definitions|
+----------------*/
#define NULL_MNODE    0
#define TARGET_MNODE  1
#define DATATAG_MNODE 2
#define PCDATA_MNODE  3
#define REGULAR_MNODE 4

/*STATIC---------------------------------------------------get_occurence_indic-+
|[132] occurence indicator = opt | plus | rep                                  |
+-----------------------------------------------------------------------------*/
static uint LP_FCT get_occurence_indic(GA_P gbla_p)
{
#if (defined(IBMC370))                          /* IBM C370 Bug */
   dl_find(gbla_p);  switch (GA(curr_token)) {
#else
   switch (dl_find(gbla_p)) {
#endif
   case OPT_TK:
      return(OPT_OCCUR);  /* 0 or 1 */
   case REP_TK:
      return(REP_OCCUR);  /* 0 or more */
   case PLUS_TK:
      return(PLUS_OCCUR); /* 1 or more */
   default:
      em_unrd(gbla_p,GA(curr_count));
      return(NULL_OCCUR); /* 1 */
   }
}

/*STATIC--------------------------------------------------------alloc_mdl_node-+
| Add a new node, either son or sibling depending on what link is pointing at. |
+-----------------------------------------------------------------------------*/
static MNODE * LP_FCT alloc_mdl_node(COMMON_P M_p)
{
   GA_P gbla_p = M_p->gbla_p;
   MNODE *mnode_p;

   ++M_p->nbr_of_model;
   if (++M_p->grpgtc > GRPGTCNT + 1) {  /* +1: model group itself not counted */
      errorh(gbla_p,BIGGRPGTC,ERH_T_SEV+ERH_LOCATE);
   }
   mnode_p = (MNODE *)zalloc(sizeof(MNODE));
   mnode_p->rlink = *M_p->link;      /* propagate thread */
   *M_p->link = mnode_p;             /* establish link */
   return(mnode_p);
}

/*STATIC----------------------------------------------------------new_mdl_link-+
| Add a new node and prepare it to be father.                                  |
+-----------------------------------------------------------------------------*/
static void LP_FCT new_mdl_link(COMMON_P M_p)
{
   MNODE *mnode_p;

   mnode_p = alloc_mdl_node(M_p);
   mnode_p->is.occurence = NULL_OCCUR;
   M_p->link = &(mnode_p->u.llink);
   *M_p->link = mnode_p;                          /* new thread set */
}

/*STATIC-------------------------------------------------------------set_mnode-+
| Enter here when an element token has been decoded:                           |
| Either a "group token" as in "()" or "()|" or "()?,"                         |
| Or a "GI token" as in "backm" or "backm+" or "backm+&"                       |
|                                                                              |
| M_p->occurence has the occurence indicator,                                  |
| M_p->connector has the connector,                                            |
|                                                                              |
| If it's a group node,                                                        |
|   it's a father already created (a father must exist before his children)    |
|   get its address from the child-father thread                               |
|   if he has no children at all: error                                        |
|   else if he has only one child, he's a useless father: remove it:           |
|      - the resulting occurence is the logical OR of the child's and          |
|        father's occurences Ex: (a?)+ == a*                                   |
|      - the resulting connector (ltag) is the child's connector (if none:     |
|        the child is a real node, so is the father - and if one, the child    |
|        is a dummy father node, and the father inherits the connector that    |
|        links the children of the child)                                      |
|     -  the resulting llink is the child's llink (same reasons as above)      |
|     -  rtag and rlink are unchanged, rtag is overwritten later               |
|     => so:  AND M_p->occurence with the child occurence,                     |
|             then copy the rlink to the child,                                |
|             (after doing the classic update of the rthread)                  |
|             set the child as the current node, delete old node               |
|                                                                              |
|  Note: the "ltag" corresponds to the connector (AND, OR, SEQ) binding        |
|        the children: it's known only after the first child has been          |
|        processed.  So, initially, "no ltag" does *not* mean that the         |
|        node is not a father: it can have a unique son and the bind between   |
|        children is not known.  The only way to check such a case is to       |
|        ask the left-linked element who's his father.                         |
|        To avoid confusion:                                                   |
|        -"M_ltag" is used when it really has the meaning  of "filiation",     |
|        -otherwise "connector" is used                                        |
|                                                                              |
| Else, it's a regular node (a brother):                                       |
|   create it on the fly                                                       |
|   enter his name in the "bind list"                                          |
|   turn off ltag, because real node can't have chidren                        |
|                                                                              |
| Set the occurence indic for this new node                                    |
| Link it to its predecessor                                                   |
| Find out if a connector has been declared (ask the father for elder brother) |
| If none: declare the current connector (tell the eldest is there)            |
| Else: if this is not the last child, check brother connection is consistent. |
|                                                                              |
+-----------------------------------------------------------------------------*/
static void LP_FCT set_mnode(COMMON_P M_p)
{
   GA_P gbla_p = M_p->gbla_p;
   MNODE *mnode_p, *mnode_pX, *mnode_pY;

   if (!M_p->kind_of_mnode) {                   /* dummy node */
      mnode_p = *M_p->link;                     /* restore thread */
      /*--------------------------------------------------------+
      |Apply inherency rules: (4.159, p 13)                     |
      |An optional token in an OR group makes the group optional|
      |If all tokens in any group are optional, so is the group |
      +--------------------------------------------------------*/
      if (((mnode_p->is.connector == OR_CONN)&&(mnode_p->is.one_omit))
         ||(!mnode_p->is.one_reqd)) {
         M_p->occurence |= OMITTABLE;
      }
      if (mnode_p->u.llink == mnode_p) {        /* if no child at all */
         errorh(gbla_p,NOCNTN,ERH_T_SEV+ERH_LOCATE);  /* it's an error */
      }else if (!mnode_p->is.connector) {       /* no child sets connector */
         mnode_pY = mnode_p->u.llink;           /* only one child: get it */
         mnode_pX = mnode_p->rlink;             /* find the predecessor */
         if (mnode_pX->u.llink == mnode_p) {    /* ... in preorder: */
            mnode_pX->u.llink = mnode_pY;       /* a father: set new son */
         }else {
            mnode_pX = mnode_pX->u.llink;
            while (mnode_pX->rlink != mnode_p) {
               mnode_pX = mnode_pX->rlink;
            }
            mnode_pX->rlink = mnode_pY;         /* an elder: new brother */
         }
         mnode_pY->rlink = mnode_p->rlink;      /* copy rlink into child */
         free(mnode_p);                         /* remove current node */
         ++M_p->nbr_of_useless_model;
         --M_p->nbr_of_model;
         mnode_p = mnode_pY;                    /* replaced by the child */
         M_p->occurence |= mnode_p->is.occurence;  /* change occurence indic */
      }
   }else {
      mnode_p = alloc_mdl_node(M_p);
      switch(M_p->kind_of_mnode) {
      case TARGET_MNODE:
         mnode_p->is.target = TRUE;
         /* fall thru */
      case REGULAR_MNODE:
         if (M_p->enode_p->ref_cnt) {
            /*-------------------------------------------------+
            |The GI name stored by ehsh_open must be available |
            |to issue ambiguities messages: ehsh_lnk cannot be |
            |used directly...                                  |
            +-------------------------------------------------*/
            /* mnode_p->u.gi_p =  */
            /*   (GI *)ehsh_lnk(M_p->elt_scope,M_p->name,sizeof(GI)); */
            EHSH *ehsh_p;
            if (!(ehsh_p = ehsh_open(M_p->elt_scope,M_p->name))->var_p) {
               mnode_p->u.gi_p = (GI *)(ehsh_p->var_p = zalloc(sizeof(GI)));
            }else {
               mnode_p->u.gi_p = (GI *)ehsh_p->var_p;
               if (mnode_p->u.gi_p->mdl_id == M_p->mdl_id) {
                  mnode_p->u.gi_p->is_duplicate = TRUE;
                  M_p->must_check_ambiguities = TRUE;
               }else {
                  mnode_p->u.gi_p->is_duplicate = FALSE;
               }
            }
            mnode_p->u.gi_p->name_p = ehsh_p->name_p;
            mnode_p->u.gi_p->mdl_id = M_p->mdl_id;
         }
         break;
      case PCDATA_MNODE:
         if (M_p->enode_p->is.pcdata) {          /* already found */
            if (M_p->enode_p->ref_cnt) {
               M_p->is_PCDATA_duplicate = TRUE;  /* can be ambiguous... */
               M_p->must_check_ambiguities = TRUE;
            }
         }else {
            M_p->enode_p->is.pcdata = TRUE;      /* see TE, 11.2.3 + 7.6 */
         }
         mnode_p->is.pcdata = TRUE;
         mnode_p->u.gi_p = (GI *)0;              /* ...means #PCDATA   */
         break;
      case DATATAG_MNODE:
         if (M_p->enode_p->is.pcdata) {          /* already found */
            if (M_p->enode_p->ref_cnt) {         /* can be ambiguous... */
               M_p->is_PCDATA_duplicate = TRUE;
               M_p->must_check_ambiguities = TRUE;
            }
         }else {
            M_p->enode_p->is.pcdata = TRUE;      /* see TE, 11.2.4.4 */
         }
         mnode_p->is.datatag = TRUE;
         mnode_p->u.ofs_p = M_p->ofs_p;
         break;
      }
   }
   mnode_p->is.occurence = M_p->occurence;
   if (mnode_p->is.occurence & OMITTABLE) {
      mnode_p->rlink->is.one_omit = TRUE;
   }else {
      mnode_p->rlink->is.one_reqd = TRUE;
   }
   M_p->link = &mnode_p->rlink;                 /* prepare for siblings */
   if (!M_ltag(*M_p->link)) (*M_p->link)->is.connector = M_p->connector;
   if (!M_p->connector) {                       /* if last child, */
      mnode_p->is.rtag = FALSE;                 /* rlink is a thread */
   }else {
      mnode_p->is.rtag = TRUE;                  /* else, check connection */
      if ((*M_p->link)->is.connector != M_p->connector) {
         errorh(gbla_p,DIFCONN,ERH_T_SEV+ERH_LOCATE);
      }
   }
}

/*STATIC--------------------------------------------------tpl_comp / tpl_exchg-+
| Compare and exchange function to sort datatag template                       |
| The longest template must be the first one in the list.                      |
+-----------------------------------------------------------------------------*/
static int P_FCT tpl_comp(int i1, int i2, void *a_p)
#define M_p ((COMMON_ODT_AREA *)a_p) /* to avoid casts all over the place... */
{
   return(- strcmp(
      sk_point(M_p->gbla_p, *(M_p->ofs_p+i1)),
      sk_point(M_p->gbla_p, *(M_p->ofs_p+i2))
   ));
}
#undef M_p
/*----------------------------------------------------------------------------*/
static void P_FCT tpl_exchg(int i1, int i2, void *a_p)
#define M_p ((COMMON_ODT_AREA *)a_p) /* to avoid casts all over the place... */
{
   OFS ofs;
   ofs = *(M_p->ofs_p + i1);
   *(M_p->ofs_p + i1) = *(M_p->ofs_p + i2);
   *(M_p->ofs_p + i2) = ofs;
}
#undef M_p

/*STATIC--------------------------------------------------decode_datatag_group-+
|                                                                              |
+-----------------------------------------------------------------------------*/
static void LP_FCT decode_datatag_group(register COMMON_P M_p)
{
   GA_P gbla_p = M_p->gbla_p;
   boolean is_data_tpl_grp;
   boolean at_padding;
   OFS    *ofs_p0, *ofs_p;
   uint len;
   uint grpcnt = 0;

   if (M_p->grplvl >= GRPLVL) errorh(gbla_p,BIGGRPLVL,ERH_T_SEV+ERH_LOCATE);
   ++M_p->nbr_of_datatag;
   /*-----------------------+
   |Decode GI of the target |
   +-----------------------*/
   skip_ts(gbla_p);
   get_gi(gbla_p,M_p->name);           /* Will be used later */
   skip_ts(gbla_p);
   if (GA(curr_token) != SEQ_TK) errorh(gbla_p,INVCONN,ERH_T_SEV+ERH_LOCATE);

   /*----------------------------------------+
   |Decode the pattern(s) and template if any|
   +----------------------------------------*/
   skip_ts(gbla_p);
   if (GA(curr_token) == GRPO_TK) {
      is_data_tpl_grp = TRUE;
      skip_ts(gbla_p);
   }else {
      is_data_tpl_grp = FALSE;
   }
   at_padding = FALSE;
   ofs_p = ofs_p0 = (OFS *)malloc((GRPCNT+2)*sizeof(OFS));
   for (;;) {
      if (!(len = get_plit(gbla_p, GA(lit_p), TRUE))) {
         errorh(gbla_p,INVEMPTY,ERH_W_SEV+ERH_LOCATE);
      }else if ((++grpcnt > GRPCNT) && (!at_padding)) {
         errorh(gbla_p,BIGGRPCNT,ERH_T_SEV+ERH_LOCATE);
         /* --grpcnt; */
      }else {
         *(GA(lit_p) + len++) = '\0';   /* see last paragraph of 11.2.4.4 */
         *++ofs_p = sk_park(gbla_p, GA(lit_p), len);
         M_p->nbr_of_datatag_text += len + NORMSEP;  /* see odtattl */
      }
      skip_ts(gbla_p);
      if (!at_padding) {
         if (is_data_tpl_grp) {
            switch (GA(curr_token)) {
            case OR_TK:
               skip_ts(gbla_p);
               continue;                 /* loop for next template */
            case GRPC_TK:
               skip_ts(gbla_p);
               break;
            default:
               errorh(gbla_p,INVCONN,ERH_T_SEV+ERH_LOCATE);
               break;
            }
         }
         if (GA(curr_token) == SEQ_TK) {   /* data tag padding template */
            at_padding = TRUE;
            skip_ts(gbla_p);
            continue;                      /* loop for padding */
         }
         *++ofs_p = (OFS)0;                /* No data tag padding */
         ++grpcnt;
      }
      if (GA(curr_token) != DTGC_TK) errorh(gbla_p,NODTGC,ERH_T_SEV+ERH_LOCATE);
      break;
   }
   /*-----------------------------------+
   | Build the model                    |
   | M_p->name has the GI of the target |
   +-----------------------------------*/
   M_p->occurence = NULL_OCCUR;
   M_p->connector = SEQ_CONN;
   if ((grpcnt <= 1) ||                    /* nothing usefull was set up */
      (!IS_BITON(GA(opt).ft,DATATAG_FT))) { /* or DATATAG feature not enabled */
      M_p->kind_of_mnode = REGULAR_MNODE;  /* Regular tag */
      set_mnode(M_p);                      /* set its node */
      M_p->kind_of_mnode = PCDATA_MNODE;   /* default to PCDATA */
      free(ofs_p0);                        /* release area */
   }else {
      M_p->kind_of_mnode = TARGET_MNODE;   /* TARGET tag */
      set_mnode(M_p);                      /* set target node */
      M_p->nbr_of_dtp += grpcnt+1;         /* create DTP */
      M_p->kind_of_mnode = DATATAG_MNODE;
      ofs_p0 = realloc(ofs_p0,(grpcnt+1)*sizeof(OFS)); /* shrink */
      *ofs_p0 = *ofs_p;                    /* padding in first entry */
      *ofs_p = (OFS)0;                     /* zero last entry */
      M_p->ofs_p = ofs_p0 + 1;             /* to sort only the template */
      wqsort(grpcnt-1,tpl_comp,tpl_exchg,M_p);
      M_p->ofs_p = ofs_p0;                 /* point the padding */
   }
   M_p->occurence = REP_OCCUR;             /* see 11.2.4.2 p 41 */
   M_p->connector = NULL_CONN;
   set_mnode(M_p);                         /* set a new node */

   M_p->occurence = get_occurence_indic(gbla_p);
   M_p->kind_of_mnode = NULL_MNODE;        /* to end current structure */
}

/*STATIC----------------------------------------------------decode_model_group-+
| Decode a model group (ISO 8879 - p40)                                        |
|                                                                              |
| [127] model_group = GRPO, ts*,content_token,ts*,   , GRPC,occurence_indic?   |
|                           (connector, ts*,content_token,ts* )*               |
|                                                                              |
| At ENTRY: curr_char == GRPO;  (no check, start AFTER: at ts*)                |
|                                                                              |
| 1) Decode "ts*,content_token,ts*"                                            |
|    [128] ts*,token_content,ts* =                                             |
|                                                                              |
|      ts*, + prim_content_token + RNI,"PCDATA"                                |
|           |                    - elmt_token = generic_id,occurence_indic?    |
|           |                    + data tag group = DTGO,...                   |
|           + model group = GRPO,...                                           |
|                                                                              |
|    [30] generic_identifier = name                                            |
|                                                                              |
| 2) check GRPC or connector: if none -> error  (but at the zero level)        |
|                                                                              |
| 3) set the "model node"                                                      |
|                                                                              |
| 4) if GRPC, get occurence indic and return, else loop.                       |
|                                                                              |
+-----------------------------------------------------------------------------*/
static void LP_FCT decode_model_group(register COMMON_P M_p)
{
   GA_P gbla_p = M_p->gbla_p;
   uint grpcnt = 0;
   int curr_floor_level = GA(floor_level); /* save current floor_level */
   int init_enty_level  = GA(enty_level);  /* remember where we started */
   int curr_enty_level;

   if (++M_p->grplvl > GRPLVL) errorh(gbla_p,BIGGRPLVL,ERH_T_SEV+ERH_LOCATE);
   for (;;) {
      GA(floor_level) = GA(enty_level);          /* No Ee allowed */
      skip_ts(gbla_p);                           /* ts after GRPO, connector */
      GA(floor_level) = init_enty_level;         /* allow EE to occur */
      switch (GA(curr_token)) {
      case GRPO_TK:
         new_mdl_link(M_p);                      /* get a new branch */
         decode_model_group(M_p);
         break;
      case RNI_TK:
         chk_gnam(gbla_p,GA(gnwdtab).PCDATA.s.p);
         M_p->kind_of_mnode = PCDATA_MNODE;      /* a PCDATA mnode */
         M_p->occurence = REP_OCCUR;             /* see 11.2.4.2 p 41 */
         break;
      case NOT_FOUND_TK:
         get_gi(gbla_p,M_p->name);
         M_p->occurence = get_occurence_indic(gbla_p);
         M_p->kind_of_mnode = REGULAR_MNODE;     /* a REGULAR mnode */
         break;
      case DTGO_TK:
         new_mdl_link(M_p);                      /* get a new branch */
         decode_datatag_group(M_p);
         break;
      default:
         errorh(gbla_p,INVDLM,ERH_T_SEV+ERH_LOCATE);
         break;
      }
      if (++grpcnt > GRPCNT) errorh(gbla_p,BIGGRPCNT,ERH_T_SEV+ERH_LOCATE);
      curr_enty_level = GA(enty_level);
      skip_ts(gbla_p);                           /* opt ts after content */
      switch(GA(curr_token)) {
      case GRPC_TK:
         M_p->connector = NULL_CONN;
         break;
      case AND_TK:
         M_p->connector = AND_CONN;
         break;
      case SEQ_TK:
         M_p->connector = SEQ_CONN;
         break;
      case OR_TK:
         M_p->connector = OR_CONN;
         break;
      default:
         errorh(gbla_p,INVCONN,ERH_T_SEV+ERH_LOCATE);
         break;
      }
      set_mnode(M_p);                            /* set a new node */
      if (!M_p->connector) {                     /* GRPC (no more connector) */
         M_p->occurence = get_occurence_indic(gbla_p);
         M_p->kind_of_mnode = NULL_MNODE;        /* to end current structure */
         --M_p->grplvl;                          /* bump count level */
         if (GA(enty_level) != init_enty_level) {
            errorh(gbla_p,INVHNEST,ERH_W_SEV+ERH_LOCATE);
         }
         GA(floor_level) = curr_floor_level;   /* restore current floor_level */
         return;                               /* and return */
      }else {
         if (GA(enty_level) > curr_enty_level) {
            errorh(gbla_p, INVDLM, ERH_W_SEV+ERH_LOCATE);
         }
      }
   }
}

/*STATIC---------------------------------------------------------install_excpt-+
|Install a new exception (element name) in the hash tables                     |
+-----------------------------------------------------------------------------*/
#define M_p ((COMMON_P)XM_p)
static void P_FCT install_excpt(void STACK_BASED * XM_p)
{
   GA_P gbla_p = M_p->gbla_p;
   uchar name[NAMELEN_MAX+1];
   EXCPT *excpt_p, **excpt_pp;
   GI *gi_p;

   get_gi(gbla_p,name);
   gi_p = (GI *)ehsh_lnk(M_p->elt_scope,name,sizeof(GI));
   gi_p->mdl_id = M_p->mdl_id;  /* to tell "check_elt" this GI was referenced */
   /*-------------------+
   |Check for duplicates|
   +-------------------*/
   excpt_pp = M_p->excpt_pp;
   while (*excpt_pp) {
      if ((*excpt_pp)->gi_p == gi_p) {
         errorh(gbla_p,DUPNAM,ERH_W_SEV+ERH_LOCATE); /* if duplicate */
         return;
      }else {
         excpt_pp = &((*excpt_pp)->next);
      }
   }
   if (M_p->enode_p->ref_cnt) {
      ++M_p->nbr_of_excptnames;
      excpt_p = *excpt_pp = (EXCPT *)malloc(sizeof(EXCPT));
      excpt_p->gi_p = gi_p;
      excpt_p->next = (EXCPT *)0;
   }
}
#undef M_p

/*STATIC-----------------------------------------------------------install_elt-+
|This procedure is called for each element name or rank stem                   |
|Build a list until we know if it is a stem or a element                       |
+-----------------------------------------------------------------------------*/
#define M_p ((COMMON_P)XM_p)
static void P_FCT install_elt(void STACK_BASED * XM_p)
{
   GA_P gbla_p = M_p->gbla_p;

   get_gi(gbla_p, M_p->c_p);
   M_p->c_p += 1 + NAMELEN;
}
#undef M_p

/*STATIC--------------------------------------------------------------undo_mdl-+
|                                                                              |
+-----------------------------------------------------------------------------*/
static void LP_FCT undo_mdl(COMMON_P M_p, MNODE *mnode_p)
{
   GA_P gbla_p = M_p->gbla_p;
   --M_p->nbr_of_model;
   if (mnode_p->is.rtag) undo_mdl(M_p,mnode_p->rlink);
   if (M_ltag(mnode_p)) undo_mdl(M_p,mnode_p->u.llink);
   free(mnode_p);
}

/*STATIC------------------------------------------------------adr_eldc_choices-+
| Allow temporary access to local constant area (choices)                      |
+-----------------------------------------------------------------------------*/
static CHOICES const * _loadds LP_FCT adr_eldc_choices(void)
{
   static CHOICES const eldc_choices[] = {
      { offsetof(GNWDTAB,CDATA),  (int)CDATA_TK     },
      { offsetof(GNWDTAB,RCDATA), (int)RCDATA_TK    },
      { offsetof(GNWDTAB,EMPTY),  (int)EMPTY_TK     },
      { offsetof(GNWDTAB,ANY),    (int)ANY_TK       },
      { -1,                       (int)NOT_FOUND_TK }
   };
   return(eldc_choices);
}

/*--------------------------------------------------------------------elm_deco-+
|Install a new element                                                         |
|                                                                              |
|[116] element declaration = mdo,"ELEMENT",                                    |
|      ps+,element_type,(ps+, omitted tag minimization)?,                      |
|      ps+,(declared_content | content_model), ps*                             |
|      mdc                                                                     |
|                                                                              |
|Enter here when current_char after "ELEMENT",                                 |
|When return, current_char at mdc                                              |
+-----------------------------------------------------------------------------*/
void P_FCT elm_deco(COMMON_P M_p)
{
   GA_P gbla_p = M_p->gbla_p;
   boolean check_exceptions = FALSE;
   int om_count, nbr_of_useless_model;
   uint suffix_len;
   MNODE mdl_head;
   ELT *elt_pG, *elt_p;
   uint elt_ix;

   nbr_of_useless_model = M_p->nbr_of_useless_model;
   M_p->grpgtc = 0;
   M_p->must_check_ambiguities = FALSE;
   M_p->enode_p = (ENODE *)zalloc(sizeof(ENODE));
   ++M_p->mdl_id;

   /* Tell the start of an ELEMENT declaration */
   tll_ELDCST(gbla_p);
   /*-----------------------------------------------------+
   |Get the list of names in M_p->names_p                 |
   |                                                      |
   |[117] element_type = generic_identifier |             |
   |         | name_group | ranked_element | ranked_group |
   +-----------------------------------------------------*/
   M_p->c_p = M_p->names_p;  /* (GRPCNT+1)*(NAMELEN+1) */
   skip_ps(gbla_p);
   switch (GA(curr_token)) {
   case GRPO_TK:
      get_nmgp(gbla_p,install_elt,M_p); /* Parse name group */
      break;
   case NOT_FOUND_TK:
      install_elt(M_p);                 /* install a new elemt type */
      break;
   default:
      errorh(gbla_p,INVNAMGRP,ERH_T_SEV+ERH_LOCATE);
      break;
   }

   /*--------------------------------------------+
   |Find out if these are stems, rather than GIs |
   +--------------------------------------------*/
   skip_ps(gbla_p);
   suffix_len = 0;
   if ((GA(curr_token)==NOT_FOUND_TK)&&(is_digit(GA(curr_char)))) {
      uchar *c_p = M_p->c_p-1;   /* use the extra entry (GRPCNT + 1) */
      do {
         if (++suffix_len <= NAMELEN) *(++c_p) = (uchar)GA(curr_char);
      }while (is_digit(em_read(gbla_p)));
      *++c_p = '\0';
      skip_ps(gbla_p);
   }
   {
      uchar *c_p, *c_pF;
      uint enode_ref_cnt = 0;
      uint sts_cnt = 0;
      uint i;
      GI *gi_p;
      RKLN *rkln_pS, *rkln_pG, *rkln_p;  /* Stem, Group */

      elt_p = elt_pG = (ELT *)malloc(sizeof(ELT));
      for (c_p=M_p->names_p, c_pF=M_p->c_p; c_p != c_pF; c_p += 1+NAMELEN) {
         if (suffix_len) {
            if (ehsh_fnd(M_p->elt_scope,c_p)) {
               errorh(gbla_p,DUPSTMELT,ERH_W_SEV+ERH_LOCATE,c_p);
               continue;
            }
            rkln_pS = (RKLN *)ehsh_lnk(M_p->stem_scope,c_p,sizeof(RKLN));
            if (!(rkln_pS->s.stem_p)) {
               ++sts_cnt;
               rkln_pS->s.stem_p = rkln_pS;
            }
            if ((suffix_len + (i=strlen(c_p))) > NAMELEN) {
               errorh(gbla_p,BIGSTMSUF,ERH_W_SEV+ERH_LOCATE,c_p,c_pF);
               continue;
            }
            memcpy(c_p+i,c_pF,suffix_len+1);
         }else {
            if (ehsh_fnd(M_p->stem_scope,c_p)) {
               errorh(gbla_p,DUPELTSTM,ERH_W_SEV+ERH_LOCATE,c_p);
               continue;
            }
         }
         gi_p = (GI *)ehsh_lnk(M_p->elt_scope,c_p,sizeof(GI));
         if (gi_p->enode_p) {
            errorh(gbla_p,DUPELT,ERH_W_SEV+ERH_LOCATE,c_p);
            continue;
         }
         elt_pG = (ELT *)realloc(elt_pG, (enode_ref_cnt+1)*sizeof(ELT));
         elt_p = elt_pG + enode_ref_cnt;
         elt_p->name.p = c_p;
         gi_p->enode_p = M_p->enode_p;
         if (suffix_len) {
            rkln_p = (RKLN *)malloc(sizeof(RKLN));
            rkln_p->s.stem_p = rkln_pS->s.stem_p;
            rkln_pS->s.stem_p = rkln_p;
            ++rkln_pS->g.stem_cnt;
            if (!enode_ref_cnt) {
               rkln_pG = rkln_p->g.group_p = rkln_p;
            }else {
               rkln_p->g.group_p = rkln_pG->g.group_p;
               rkln_pG->g.group_p = rkln_p;
            }
            gi_p->rkln_p = rkln_p;
         }
         ++enode_ref_cnt;                         /* One more referenced */
      }
      M_p->enode_p->ref_cnt = enode_ref_cnt;
      if (suffix_len) {
         M_p->nbr_of_rkl += enode_ref_cnt;
         M_p->nbr_of_sts += sts_cnt;
      }
   }


   /*-----------------------+
   |Omitted tag minimization|
   +-----------------------*/
   for (om_count=0; (om_count<2)&&(GA(curr_token)==NOT_FOUND_TK); ++om_count) {
      if (get_gnam(gbla_p,GA(gnwdtab).O.s.p)) {        /* "O" found */
         if (!om_count) {                              /* first time: */
            skip_Wps(gbla_p);                          /* skip'n check PS */
         }else {
            skip_ps(gbla_p);                           /* skip PS if any */
         }
      }else {                                          /* not "O" found */
         if (!get_gstr(gbla_p,GA(dstab)[MINUS_ix].p)) { /* neither MINUS */
            break;                                     /* in error */
         }else {                                       /* MINUS found */
            if (!om_count) {                           /* first time: */
               M_p->enode_p->is.reqd_open_tag = TRUE;  /* for open */
            }else {                                    /* 2nd time: */
               M_p->enode_p->is.reqd_end_tag = TRUE;   /* for close */
            }
         }
         skip_ps(gbla_p);                              /* skip PS if any */
      }
   }
   if ((!om_count) && (!IS_BITON(GA(opt).ft,OMITTAG_FT))) {
      M_p->enode_p->is.reqd_open_tag = TRUE;
      M_p->enode_p->is.reqd_end_tag = TRUE;
   }else if (om_count != 2) {
      errorh(gbla_p,NOOTAGMIN,ERH_T_SEV+ERH_LOCATE);
   }

   /*---------------------------------------------------------------+
   | decode element content                                         |
   |                                                                |
   | element_content = (declared_content | content_model)           |
   |                                                                |
   | [125] declared_content = "CDATA" | "RCDATA" | "EMPTY"          |
   | [126] content_model = (model_group | "ANY"), (ps+, exceptions)?|
   +---------------------------------------------------------------*/
   switch(GA(curr_token)) {
   case GRPO_TK:
      psh_mode(gbla_p,GRP_MODE);
      mdl_head.u.llink = &(mdl_head);
      mdl_head.is.connector = SEQ_CONN;       /* single content => seq */
      M_p->link = &(mdl_head.u.llink);
      M_p->occurence = NULL_OCCUR;
      check_exceptions = TRUE;
      new_mdl_link(M_p);
      decode_model_group(M_p);
      set_mnode(M_p);
      M_p->enode_p->u.mnode_p = mdl_head.u.llink; /* cont-mdl */
      if (M_p->must_check_ambiguities) abg_chk(M_p,mdl_head.u.llink);
      pop_mode(gbla_p);
      break;
   case NOT_FOUND_TK:
      /* get dcl content in eldc list of choices */
      switch (choos_in(gbla_p,adr_eldc_choices(),(GNWD *)&GA(gnwdtab))) {
      case ANY_TK:
         M_p->enode_p->is.any = TRUE;
         M_p->enode_p->is.pcdata = TRUE;  /* mixed cnt, and char data allowed */
         check_exceptions = TRUE;
         break;
      case CDATA_TK:
         M_p->enode_p->is.dcdata = NONR_DCDATA;
         if (!M_p->enode_p->is.reqd_open_tag) {    /* O start tag for DCDATA */
            M_p->enode_p->is.reqd_open_tag = TRUE; /* is invalid */
            errorh(gbla_p,INVSTGMIN,ERH_I_SEV+ERH_LOCATE);
         }
         break;
      case RCDATA_TK:
         M_p->enode_p->is.dcdata = RPLC_DCDATA;
         if (!M_p->enode_p->is.reqd_open_tag) {    /* O start tag for DCDATA */
            M_p->enode_p->is.reqd_open_tag = TRUE; /* is invalid */
            errorh(gbla_p,INVSTGMIN,ERH_I_SEV+ERH_LOCATE);
         }
         break;
      case EMPTY_TK:
         M_p->enode_p->is.empty = TRUE;
         if (!M_p->enode_p->is.reqd_open_tag) {    /* O start tag for EMPTY */
            M_p->enode_p->is.reqd_open_tag = TRUE; /* is invalid */
            errorh(gbla_p,INVSTGMIN,ERH_I_SEV+ERH_LOCATE);
         }
         if (M_p->enode_p->is.reqd_end_tag) {      /* - end tag for EMPTY */
            M_p->enode_p->is.reqd_end_tag = FALSE; /* is invalid */
            if (om_count) errorh(gbla_p,INVETGMIN,ERH_I_SEV+ERH_LOCATE);
         }
         break;
      default:                                    /* NOT_FOUND */
         errorh(gbla_p,INVKWD,ERH_T_SEV+ERH_LOCATE);
         break;
      }
      break;
   default:
      errorh(gbla_p,INVDLM,ERH_T_SEV+ERH_LOCATE);
      break;
   }
   /*----------------------------------------------------------+
   |Decode exceptions                                          |
   |[138] exceptions = (exclusions,(ps+,inclusions)?|inclusions|
   |[140] exclusions = MINUS, name group                       |
   |[139] inclusions = PLUS, name group                        |
   +----------------------------------------------------------*/
   M_p->enode_p->mexcpt.p = (EXCPT *)0;           /* Minus exceptions */
   M_p->enode_p->pexcpt.p = (EXCPT *)0;           /* Plus exceptions */
   skip_ps(gbla_p);
   if (check_exceptions) {
      switch (GA(curr_token)) {
      case MINUS_GRPO_TK:                  /* handle exclusions first */
         /*----------------+
         |handle exclusions|
         +----------------*/
         M_p->excpt_pp = &M_p->enode_p->mexcpt.p;
         if (M_p->enode_p->ref_cnt) ++M_p->nbr_of_exceptions;
         get_nmgp(gbla_p,install_excpt,M_p);
         skip_ps(gbla_p);
         if (GA(curr_token)!=PLUS_GRPO_TK) break;
         /* else, check inclusions */
      case PLUS_GRPO_TK:
         /*----------------+
         |handle inclusions|
         +----------------*/
         M_p->excpt_pp = &M_p->enode_p->pexcpt.p;
         if (M_p->enode_p->ref_cnt) ++M_p->nbr_of_exceptions;
         get_nmgp(gbla_p,install_excpt,M_p);
         skip_ps(gbla_p);
      default:
         break;                           /* not an exception */
      }
   }
   if (!M_p->enode_p->ref_cnt) {          /* undo everything...*/
      if ((!M_p->enode_p->is.any)&&
         (!M_p->enode_p->is.empty)&&(!M_p->enode_p->is.dcdata)) {
         undo_mdl(M_p,M_p->enode_p->u.mnode_p);
         M_p->nbr_of_useless_model = nbr_of_useless_model;
      }
      free(M_p->enode_p);
   }
   for (elt_p=elt_pG, elt_ix=0;
        elt_ix < M_p->enode_p->ref_cnt;
        elt_p++, elt_ix++) {
      elt_p->is = M_p->enode_p->is;
   }
   tll_ELDCND(gbla_p, elt_pG, M_p->enode_p->ref_cnt);
   free(elt_pG);
}

/*--------------------------------------------------------------------elm_mdbl-+
|Build "stringized" model structure                                            |
+-----------------------------------------------------------------------------*/
void P_FCT elm_mdbl(COMMON_P M_p, MNODE *mnode_p, ushort *rx_p)
{
   GA_P gbla_p = M_p->gbla_p;
   MDL  *mdl_p;

   mdl_p = M_p->mdl_p++;
   mdl_p->connector  = mnode_p->is.connector;
   mdl_p->occurence  = mnode_p->is.occurence;
   mdl_p->is_pcdata  = mnode_p->is.pcdata;
   mdl_p->is_datatag = mnode_p->is.datatag;
   mdl_p->is_target  = mnode_p->is.target;
   mdl_p->unused = 0;
   if (mnode_p->is.rtag) {
      elm_mdbl(M_p,mnode_p->rlink,rx_p);
   }else {
      *rx_p = 0;
   }
   if (M_ltag(mnode_p)) {
      mdl_p->l.mdl.rx  = (ushort)(M_p->mdl_p - mdl_p);
      elm_mdbl(M_p,mnode_p->u.llink,&(mdl_p->l.mdl.num));
   }else {
      if (mnode_p->is.pcdata) {            /* #PCDATA */
         mdl_p->l.elt.ix = 0;              /* to keep ODT consistent */
      }else if (mnode_p->is.datatag) {     /* DATATAG */
         OFS *ofs_p = mnode_p->u.ofs_p;
         mdl_p->l.dtp.ix = diffptr(M_p->dtp_p, M_p->dtp_orig);
         do (M_p->dtp_p++)->ofs = *ofs_p; while (*++ofs_p);
         (M_p->dtp_p++)->ofs = 0;
         free(mnode_p->u.ofs_p);
      }else {                              /* TARGET or REGULAR */
         mdl_p->l.elt.ix = mnode_p->u.gi_p->hash_ix;
      }
   }
   ++(*rx_p);
   free(mnode_p);
}

/*--------------------------------------------------------------------elm_exbl-+
|Build EX array for an ELT.  Last entry is -1.                                 |
|The EXCPT structure is freed.                                                 |
+-----------------------------------------------------------------------------*/
void P_FCT elm_exbl(COMMON_P M_p, EXCPT *excpt_p)
{
   GA_P gbla_p = M_p->gbla_p;
   EXCPT *excpt_pS;        /* avoid to reaccess a freed area */

   do {
      (*(M_p->ex_p)++).ix = excpt_p->gi_p->hash_ix;
      excpt_p = (excpt_pS=excpt_p)->next;
      free(excpt_pS);
   }while (excpt_p);
   (*(M_p->ex_p)++).ix = -1;
}
/******************************************************************************/
