/*******************************************************************************
* 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 / SGML Library                                       *
* STATUS           = Version 0, Release 1, Level 34 and up                     *
* COMPONENT NAME   = em.c                                                      *
* DESCRIPTIVE NAME = Entity Manager                                            *
* DATE_WR          = 04/06/1987                                                *
* AUTHOR           = Pierre G. Richard - PETRUS at YKTVMX                      *
*                                                                              *
* FUNCTIONAL DESCRIPTION =                                                     *
* (will be filled later: see description in each module)                       *
*                                                                              *
* IMPLEMENTATION NOTE =                                                        *
* A ring-buffer is used to allow quick unread; when a new entity is opened     *
* in the nest, it's impossible to unread the previous one before this point.   *
* This might not affect the usability of the code, due to the Ee rules..       *
* A RS is forced at the beginning of each new entity, for trace purposes.      *
*                                                                              *
* DEPENDENCIES =                                                               *
*                                                                              *
* IMPORT=                                                                      *
*                                                                              *
* EXPORT=                                                                      *
*                                                                              *
* CHANGE HISTORY =                                                             *
* Number Date     Inits    Change & Reason (brief)                             *
*      1 06/29/89 PGR      IO Interface (IO serv) integrated                   *
*      2 11/05/89 PGR      Don't find NOTATION if it was undefined (bug)       *
*      3 11/05/89 PGR      Applied last paragraph of 9.1 (Amendement 1)        *
*      4 01/20/91 PGR      Tell application when entity starts/ends            *
*      5 03/01/91 DUROT    em_pos(): for YASP 1.26                             *
*      6 12/17/91 PGR      Opened Entity Block                                 *
*      7 12/24/91 PGR      Offset-row table to avoid negative values in em_pos *
*******************************************************************************/

/*-------------+
|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 "hash.h"
#include "teller.h"

/*---------------+
|Local Functions |
+---------------*/
static uchar * _loadds LP_FCT adr_dflt_appl_info(void);

/*STATIC----------------------------------------------------adr_dflt_appl_info-+
| Return the full address of a default application info                        |
+-----------------------------------------------------------------------------*/
static uchar * _loadds LP_FCT adr_dflt_appl_info(void)
{
   static uchar dflt_appl_info[] = "** No application info **";
   return(dflt_appl_info);
}

/*--------------------------------------------------------------------em_fgety-+
|Find the ETY structure given the name of a general entity                     |
+-----------------------------------------------------------------------------*/
ETY * P_FCT em_fgety(GA_P gbla_p, uchar *name_p)
{
   ETY *ety_p, *ety_pT;
   EXID *exid_pT;
   uchar def_name[1];            /* local default name */

   if ((ety_p = GA(gety_htab) ?
      (ETY *)shsh_fnd(GA(gety_htab),name_p) :    /* use compacted form */
      (ETY *)ehsh_fnd(GA(gety_scope),name_p))    /* or primary hash-table */
      != (ETY *)0) {
      return(ety_p);
   }
   /*-------------------------+
   |Not found: get the default|
   +-------------------------*/
   def_name[0] = '\0';        /* default entity name is "" */
   if (!(ety_p = GA(gety_htab) ?
      (ETY *)shsh_fnd(GA(gety_htab),def_name) :
      (ETY *)ehsh_fnd(GA(gety_scope),def_name))) {
      errorh(gbla_p,NOGERIGN,ERH_W_SEV+ERH_LOCATE,name_p); /* gn enty ignord */
      return((ETY *)0);
   }
   /*-----------------------------------------------------+
   |Make a copy of the Default Entity.                    |
   |If the DEFAULT entity is external, also copy the EXID:|
   |The system's strategy is allowed to use this name     |
   |(see ISO8879 + Amendment 1, 10.1.6)                   |
   +-----------------------------------------------------*/
   errorh(gbla_p,NOGERDEF,ERH_I_SEV+ERH_LOCATE,name_p); /* gen enty deflt */
   if (!GA(gety_htab)) {
      EHSH *ehsh_p;
      ++GA(nbr_of_g_entities);
      ehsh_p = ehsh_open(GA(gety_scope),name_p);
      ehsh_p->var_p = (void *)(ety_pT = (ETY *)malloc(sizeof(ETY)));
      ety_pT->name._.p = ehsh_p->name_p;
   }else {
      ++GA(capcnt)[ENTCAP_ix];
      ety_pT = shsh_add(gbla_p,GA(gety_htab),name_p,sizeof(ETY));
   }
   ety_pT->name.len = strlen(name_p);
   ety_pT->val = ety_p->val;
   ety_pT->is = ety_p->is;
   SET_BITON(ety_pT->is,DEFLT_EF);
   ety_pT->kind = ety_p->kind;
   if IS_BITON(ety_p->is,EXTERNAL_EF) {
      ++GA(nbr_of_exid);             /* used only when !GA(ety_htab) */
      exid_pT = ety_pT->val.ext._.exid.p = (EXID *)malloc(sizeof(EXID));
      *(exid_pT) = *(ety_p->val.ext._.exid.p);  /* copy the exid */
      exid_pT->name._.p = ety_pT->name._.p;
      exid_pT->name.len = ety_pT->name.len;
   }
   return(ety_pT);
}

/*--------------------------------------------------------------------em_opger-+
| Try to access a general entity reference.                                    |
| Return a pointer to the entity, (NULL pointer if unable to access).          |
| The entity can be opened, if the recognition mode allows it (see em_open).   |
|                                                                              |
| At compile time (GA(gety_tab) == NULL), we can only be called                |
| by tg_gtavs, parsing a default AVS.                                          |
+-----------------------------------------------------------------------------*/
ETY * P_FCT em_opger(GA_P gbla_p)
{
   uchar name[NAMELEN_MAX+1];    /* see 9.3.1 */
   ETY *ety_p;

   get_enmd(gbla_p,name);
   if (((ety_p=em_fgety(gbla_p,name))==(ETY *)0)||(!em_open(gbla_p,ety_p))) {
      return((ETY *)0);
   }else {
      return(ety_p);
   }
}

/*--------------------------------------------------------------------em_opper-+
|Access to a parameter entity reference                                        |
+-----------------------------------------------------------------------------*/
void P_FCT em_opper(GA_P gbla_p)
{
   uchar name[NAMELEN_MAX+1];  /* see 9.3.1 */
   ETY *ety_p;

   get_enmd(gbla_p,name);
   if (!(ety_p = GA(pety_htab) ?
      (ETY *)shsh_fnd(GA(pety_htab),name) :      /* use compacted form */
      (ETY *)ehsh_fnd(GA(pety_scope),name))) {   /* or primary hash-table */
      errorh(gbla_p,NOPER,ERH_W_SEV+ERH_LOCATE); /* entname not found */
   }else {
      /*---------------------------------------------------------+
      |Only PI (no C/S/N Data nor SUBDOC) can be found by em_open|
      |(and not opened)                                          |
      +---------------------------------------------------------*/
      if ((em_open(gbla_p,ety_p)) && (ety_p->kind == PI_EFK)) {
         tll_ePI(gbla_p,ety_p);
      }
   }
}

/*---------------------------------------------------------------------em_find-+
| External Entities/Notations: obtain the FileId AHL in EXID.                  |
| return FALSE if not found, or TRUE if OK                                     |
+-----------------------------------------------------------------------------*/
boolean P_FCT em_find(GA_P gbla_p, EXID *exid_p)
{
   PAC *pac_p;

   if (!IS_BITON(exid_p->is,SOLVED_XF)) {
      pac_p = gbla_p->pac_p;
      pac_p->dad._.p = (uchar *)exid_p;
      pac_p->func = FIND_PFC;
      switch (pac_p->ase(pac_p)) {
      case 0:
         exid_p->idhl = pac_p->ahl;
         break;
      case END_PARSE_RC:
         exit(END_PARSE_RC);
      case END_RESUME_RC:
         exit(END_RESUME_RC);
      default:
         SET_BITON(exid_p->is,INVALID_XF);
         break;
      }
      SET_BITON(exid_p->is,SOLVED_XF);
   }
   if (IS_BITON(exid_p->is,INVALID_XF)) {
      if (exid_p->asgn_cls==PRIMARY_XFC) {
         errorh(gbla_p,BADOPPRIM,ERH_T_SEV);
      }else {
         errorh(gbla_p,INVEXID,ERH_I_SEV+ERH_LOCATE,exid_p->name._.p);
      }
      return(FALSE);          /* check_code = 0 */
   }else {
      return(TRUE);
   }
}

/*---------------------------------------------------------------------em_open-+
| Check if a reference to an entity is allowed in a given context.             |
| If so, open it if reparseable.                                               |
| Return TRUE if the entity reference was allowed (even if not opened).        |
|                                                                              |
| "check_code" was an internal code that has been supressed to make the code   |
| goes faster.  However, it has been left in the comments, taking the          |
| following values:                                                            |
|                                                                              |
|  0  if the entity is not valid (don't open it), that is:                     |
|      - an external entity, and the system was unable to locate it            |
|      - a SUBDOC (external) entity, not in CON mode                           |
|      - a NDATA (external) entity, not in CON mode                            |
|      - a PI (internal) entity, not in CON nor DS mode                        |
|     A suitable error message is issued.                                      |
|     FALSE is returned.                                                       |
|                                                                              |
|  1  if the entity is valid, and can be opened, that is:                      |
|      - a reparseable entity, succesfully located if external (not SUBDOC)    |
|      - a SUBDOC entity in CON mode                                           |
|      - a C/S Data (external or internal) in mode other than CON, RCD, or     |
|        RCDMS: as LITC mode (see 7.9.3)                                       |
|     In such a case, the entity is opened.                                    |
|     If successful, TRUE is returned.  Otherwise FALSE.                       |
|                                                                              |
|  2  if the entity is valid, but must not be opened because it is             |
|     not reparseable: that is                                                 |
|      - a C/S DATA (external or internal) entity in CON, RCD, RCDMS mode      |
|      - an NDATA (external) in CON mode                          [ (see 9.1)  |
|      - a PI (internal) entity in CON or DS mode                              |
|     In such a case, the entity is not opened.                                |
|     The caller will certainly issue tll_ETY or tll_ePI                       |
|     (after analysis of the entity kind)                                      |
|     TRUE is returned.                                                        |
|                                                                              |
| To N/S/C DATA external entities, a NOTATION is always associated.            |
|                                                                              |
| - In LITC_MODE the notation is not handled because the entity will be opened |
|   and expanded by the parser. Parser is not able to handle the notation,     |
|   and (when the AVS is declared in ODT) the notation is not even solved.     |
|   These entitites will be read like data-char (see 9.3.1 in ISO8879).        |
|                                                                              |
| - In CON_MODE the entity is passed asis to the application via entity        |
|   control blocks, pointing a notation control block.                         |
+-----------------------------------------------------------------------------*/
boolean P_FCT em_open(GA_P gbla_p, ETY *ety_p)
{
   AHL achl;              /* access handle */
   int i;
   PAC *pac_p = gbla_p->pac_p;
   YB *yb_p;

   if IS_BITON(ety_p->is,EXTERNAL_EF) {
      if (!em_find(gbla_p,ety_p->val.ext._.exid.p)) return(FALSE); /* chk 0 */
      if (!IS_BITON(ety_p->is,RCDATA_EF)) {
         /*---------------------------------+
         |Non RCDATA (N/S/C Data or SUBDOC) |
         +---------------------------------*/
         if (((ety_p->kind==NDATA_EFK)||(ety_p->kind==SUBDOC_EFK)) &&
            (GA(curr_mode) != CON_MODE)) {                 /* see 9.1 */
            errorh(gbla_p,INVETYPE,ERH_W_SEV+ERH_LOCATE);
            return(FALSE);       /* check_code = 0 */
         }
         if ((ety_p->kind != SUBDOC_EFK) &&
            ((GA(curr_mode)==CON_MODE) || (GA(curr_mode)==RCD_MODE) ||
            (GA(curr_mode)==RCDMS_MODE))) {
            /*----------------------------------------------------------+
            |Ext C/S DATA in CON, RCD, RCDMS mode, or NDATA in CON Mode:|
            |Non REPARSEABLE entity, and allowed, will return TRUE      |
            |(but not opened *really*).                                 |
            |Note:                                                      |
            |For notations, em_find() must be called only when parsing  |
            |the document instance -- not in prolog, at compile time,   |
            |since they are not solved.  This cannot be the case:       |
            |none of the above modes can be activated while in prolog.  |
            +----------------------------------------------------------*/
            if (ety_p->val.ext.nad.p) {                        /* valid */
               em_find(gbla_p,&(ety_p->val.ext.nad.p->ncb.p->exid));
            }
            return(TRUE);        /* check_code = 2 */
         }
      }
      pac_p->ahl = ety_p->val.ext._.exid.p->idhl;  /* pass ident handle.. */
      pac_p->func = XENOP_PFC;
      /*----------------------------------+
      | pacfun return code for XENOP:     |
      |   RC  6:  File is already open    |
      |   RC  8:  Bad handle              |
      |   RC > 100: CMS return code + 100 |
      +----------------------------------*/
      switch (pac_p->ase(pac_p)) {
      case 0:
         break;
      case END_PARSE_RC:
         exit(END_PARSE_RC);
      case END_RESUME_RC:
         exit(END_RESUME_RC);
      default:
         if (GA(enty_level)) {              /* NOT for PRIMARY entity */
            errorh(gbla_p,BADOPEN,ERH_W_SEV+ERH_LOCATE,ety_p->name._.p);
         }else {
            errorh(gbla_p,BADOPPRIM,ERH_T_SEV);
         }
         return(FALSE);
      }
      achl = pac_p->ahl;                    /*.. to get access handle */
   }else {
      if (!IS_BITON(ety_p->is,RCDATA_EF)) {
         /*---------------------------------+
         |Non reparseable internal entities |
         +---------------------------------*/
         if (ety_p->kind == PI_EFK) {          /* Internal PI */
            if ((GA(curr_mode) == CON_MODE) || (GA(curr_mode) == DS_MODE)) {
               return(TRUE);        /* check_code = 2 */
            }
            errorh(gbla_p,INVETYPE,ERH_W_SEV+ERH_LOCATE);
            return(FALSE);          /* check_code = 0 */
         }
         /* Internal C-SDATA (cannot be in prolog) */
         if ((GA(curr_mode)==CON_MODE) || (GA(curr_mode)==RCD_MODE) ||
            (GA(curr_mode)==RCDMS_MODE)) {
            return(TRUE);           /* check_code = 2 */
         }
      }
   }
   /* check_code = 1 */
   /*------------------------------------------------+
   |Here the entity needs to be expanded: it can be  |
   |a non reparseable (general) entity for AVS.      |
   |If it is an external entity, it has been opened. |
   |Prepare the entity stack for the new entity.     |
   +------------------------------------------------*/
   if (GA(enty_level)) {                       /* NOT for PRIMARY entity */
      if ((uint)(GA(enty_level)) > ENTLVL) {
         errorh(gbla_p,BIGENTLVL,ERH_W_SEV+ERH_LOCATE);
         return(FALSE);
      }
      /*-------------------------------------+
      |If some unread characters are waiting,|
      |save the (circular) unread buffer in  |
      |a temporary mallocated area           |
      +-------------------------------------*/
      yb_p = GA(yb_pC);                        /* Get current YB */
      if (!(i = GA(max) - GA(head))) {         /* no need */
         yb_p->fore_bufsize = 1;
         yb_p->v.curr_char = GA(curr_char);
      }else if (i > 0) {
         yb_p->v.fore_buf = malloc(yb_p->fore_bufsize = i+1);
         memcpy(yb_p->v.fore_buf, GA(ebuf)+GA(head), yb_p->fore_bufsize);
      }else {
         yb_p->v.fore_buf = malloc(yb_p->fore_bufsize = i+1+BUFSIZE);
         memcpy(yb_p->v.fore_buf, GA(ebuf)+GA(head), BUFSIZE-GA(head));
         memcpy(yb_p->v.fore_buf+BUFSIZE-GA(head), GA(ebuf), GA(max) + 1);
      }
#if YASP_RVL_GE(0,1,26)
      em_pos(gbla_p, &(pac_p->pos), 1); /* Position within the parent entity */
   }else {                              /* or no parents for primary entity */
      pac_p->pos.row = 0;
      pac_p->pos.col = 0;
      pac_p->pos.ofs = 0;
      pac_p->pos.ety_p = (ETY *)NULL;
#endif
   }
   yb_p = zalloc(sizeof(YB));           /* Create a new YB */
   yb_p->prev_p = GA(yb_pC);            /* link it to the previous one */
   GA(yb_pC) = yb_p;                    /* new is current */
   pac_p->oeb_p = &(yb_p->oeb);         /* Tell application */
   yb_p->oeb.ety_p = ety_p;
   /* Force a Record Start character to avoid too much unread (errorh) */
   (GA(ebuf))[GA(head) = 0] = GA(RS_char);
   ++GA(enty_level);
   if IS_BITON(ety_p->is,EXTERNAL_EF) {
      GA(max) =  0;                     /* Forces io */
      yb_p->oeb.achl = achl;
      yb_p->oeb.offset = -1;
      yb_p->oeb.appl_info = adr_dflt_appl_info();
   }else {                              /* It's a memory entity: not opened, */
      GA(max) =  1;                     /* the entity will be unread */
      if IS_BITON(ety_p->is,BRACKETED_EF) {    /* Bracketed */
         uchar *c1_p, *c2_p;    /* for bracketed text inclusion */
         uint i1, i2;

         switch(ety_p->kind) {
      /* case PI_EFK:  has been handled above */
         case STARTTAG_EFK:
            c1_p = GA(dstab)[STAGO_ix].p;
            c2_p = GA(dstab)[TAGC_ix].p;
            break;
         case ENDTAG_EFK:
            c1_p = GA(dstab)[ETAGO_ix].p;
            c2_p = GA(dstab)[TAGC_ix].p;
            break;
         case MS_EFK:
            c1_p = GA(dstab)[MDO_DSO_ix].p;
            c2_p = GA(dstab)[MSC_MDC_ix].p;
            break;
         default:  /* case MD_EFK: */
            c1_p = GA(dstab)[MDO_ix].p;
            c2_p = GA(dstab)[MDC_ix].p;
            break;
         }
         memcpy(GA(ebuf)+GA(max),c1_p,i1=strlen(c1_p));  /* first part */
         GA(max) += i1;
         memcpy(GA(ebuf)+GA(max),ety_p->val.lit._.p,ety_p->val.lit.len);
         GA(max) += ety_p->val.lit.len;
         memcpy(GA(ebuf)+GA(max),c2_p,i2=strlen(c2_p));  /* second part */
         GA(max) += i2;
         yb_p->oeb.offset =            /* for offset, do not count 1st part */
         yb_p->oeb.col = GA(max) - i1;
         yb_p->vdiff = 0 - i1;
      }else {
         memcpy(GA(ebuf)+GA(max),ety_p->val.lit._.p,ety_p->val.lit.len);
         GA(max) += ety_p->val.lit.len;
         yb_p->oeb.offset =
         yb_p->oeb.col = GA(max);
      }
      (GA(ebuf))[GA(max)] = Ee;
   }
   if (IS_BITON(ety_p->is,EXTERNAL_EF)  /* External entity, or */
      || (ety_p->name._.p)) {           /* internal, but not a YASP literal: */
      pac_p->dad._.p = ety_p;           /* passing ETY block, */
      pac_p->func = ETYST_PFC;          /* tell application entity starts */
      if ((i=pac_p->ase(pac_p))!=0) exit(i);
   }
   GA(curr_char) = em_read(gbla_p);     /* was: (GA(ebuf))[GA(head)]; */
   return(TRUE);
}

/*--------------------------------------------------------------------em_close-+
| Close an entity - return FALSE if troubles..                                 |
| Note: This function is also called in case the Parser must EXIT              |
|       code must not assume ERH_T_SEV or EXIT does not return!                |
|       Do the minimum during exits -- when GA(recovering) is TRUE,            |
|       do not issue free's: no idea of what subpool we are in, and may RC 10. |
|       pl_clean() will free all subpoos, anyway.                              |
+-----------------------------------------------------------------------------*/
boolean P_FCT em_close(GA_P gbla_p)
{
   PAC *pac_p = GA(pac_p);
   boolean rc = TRUE;
   ETY *ety_p;
   AHL achl;
   int p_rc;
   YB *yb_pP;                                     /* P for Previous */

   if (!GA(enty_level)) {                         /* no more to close ! */
      errorh(gbla_p,INVEOF,ERH_T_SEV+ERH_LOCATE); /* can return from this */
   }else {
      ety_p = GA(yb_pC)->oeb.ety_p;               /* save current entity */
      achl = GA(yb_pC)->oeb.achl;                 /* and current ahl */
      yb_pP = GA(yb_pC)->prev_p;                  /* Save pointer to prev YB */
      pac_p->oeb_p = &(yb_pP->oeb);               /* New OEB now */
#if YASP_RVL_GE(0,1,26)
      em_pos(gbla_p, &(pac_p->pos), 0);           /* Position in current ety */
#endif
      if (IS_BITON(ety_p->is,EXTERNAL_EF)/* External entity, or */
         || (ety_p->name._.p)) {         /* internal, but not a YASP literal: */
         pac_p->dad._.p = ety_p;         /* passing ETY block, */
         pac_p->func = ETYND_PFC;        /* tell application entity ends */
         if ((p_rc=pac_p->ase(pac_p))!=0) exit(p_rc);
      }
      --GA(enty_level);
      if (!GA(recovering)) {
         if (GA(enty_level)) {                    /* Reset prev entity state */
            GA(head) = 1;
            if ((GA(max) = yb_pP->fore_bufsize) > 1) {
               GA(curr_char) = *(yb_pP->v.fore_buf);
               memcpy(GA(ebuf)+1, yb_pP->v.fore_buf, yb_pP->fore_bufsize);
               free(yb_pP->v.fore_buf);
            }else {
               (GA(ebuf))[1] = (uchar)(GA(curr_char) = yb_pP->v.curr_char);
            }
            /* Force a Record Start char to avoid too much unread (errorh) */
            (GA(ebuf))[0] = GA(RS_char);
         }
         free(GA(yb_pC));
      }
      GA(yb_pC) = yb_pP;                          /* Pretend it's closed */
      if IS_BITON(ety_p->is,EXTERNAL_EF) {
         pac_p->ahl = achl;
         pac_p->func = XENCL_PFC;
         /*-----------------------------+
         |pacfun return codes for XENCL:|
         |  6:  File is not open        |
         |  8:  Bad handle              |
         +-----------------------------*/
         switch (pac_p->ase(pac_p)) {
         case 0:
            break;
         case END_RESUME_RC:
            exit(END_RESUME_RC);         /* can return */
            return(FALSE);
         case END_PARSE_RC:
            exit(END_PARSE_RC);          /* can return (and then fall thru) */
         default:
            if (GA(enty_level) > 1) {   /* no name for the primary entity */
               errorh(gbla_p,BADCLPRIM,ERH_T_SEV);
            }else {
               errorh(gbla_p,BADCLOSE,ERH_T_SEV,ety_p->name._.p);
            }
            rc = FALSE;
            break;
         }
      }
#if YASP_RVL_GE(0,1,26)
      if (GA(enty_level)) {
         em_pos(gbla_p, &(pac_p->pos), 0); /* Position within resumed entity */
      }else {                              /* no parents for primary entity */
         pac_p->pos.row = 0;
         pac_p->pos.col = 0;
         pac_p->pos.ofs = 0;
         pac_p->pos.ety_p = (ETY *)NULL;
      }
#endif
   }
   return(rc);
}

/*---------------------------------------------------------------------em_read-+
| Read next chunk.                                                             |
|                                                                              |
| The last BUFSIZE chunks read are stored in a buffer exactly as they appear   |
| at the time they are fetched (either from the outside world, via a call      |
| to the client, or from the internal entity substitution algorithm).          |
| This allows a quick global unread.                                           |
+-----------------------------------------------------------------------------*/
uint P_FCT em_read(GA_P gbla_p)
{
   int i, j;
   uchar c;
   PAC *pac_p;
   YB  *yb_p = GA(yb_pC);

   if ((GA(head))++ == GA(max)) {           /* no more chunks to unread: */
      if ((i = yb_p->buf_max) == -1) {      /* read the IO buff */
         c = Ee;
         goto imm_return;                   /* immediate return for EOF */
      }
      ++yb_p->oeb.offset;
      ++yb_p->oeb.col;
      if ((j = yb_p->buf_pos++) < i)  {
         c = *(yb_p->buffer + j);           /* regular character */
      }else {
         /*-----------------------------+
         |Buffer empty: do the real IO  |
         |pacfun return codes for XENRD:|
         |  0:    Transparent IO        |
         |  1:    Record: insert RE/RS  |
         |  2:    only RS to insert     |
         |  3:    only RE to insert     |
         |  4:    End-of-File           |
         |  6:    File is not open      |
         |  8:    Bad handle            |
         |  >100: CMS code + 100        |
         +-----------------------------*/
         pac_p = (PAC *)GA(pac_p);
         pac_p->ahl = yb_p->oeb.achl;
         pac_p->func = XENRD_PFC;
         switch (pac_p->ase(pac_p)) {
         case 0:                            /* Transparent IO */
            yb_p->buffer = (uchar *)pac_p->dad._.p;
            yb_p->buf_max = pac_p->dad.len;
            yb_p->buf_pos = 1;
            c =  *(yb_p->buffer);
            break;
         case 1:                            /* Record, RS/RE to insert */
            *((yb_p->buffer = (uchar *)pac_p->dad._.p) +
            ((yb_p->buf_max = pac_p->dad.len + 1) - 1)) = GA(RE_char);
            yb_p->buf_pos = 0;              /* stay at first character */
            c = GA(RS_char);                /* return RS */
            break;
         case 2:                            /* RS to insert */
            yb_p->buffer = (uchar *)pac_p->dad._.p;
            yb_p->buf_max = pac_p->dad.len;
            yb_p->buf_pos = 0;              /* stay at first character */
            c = GA(RS_char);                /* return RS */
            break;
         case 3:                            /* RE to insert */
            *((yb_p->buffer = (uchar *)pac_p->dad._.p) +
            ((yb_p->buf_max = pac_p->dad.len + 1) - 1)) = GA(RE_char);
            yb_p->buf_pos = 1;
            c =  *(yb_p->buffer);
            break;
         default:                           /* IO error */
            if (GA(enty_level) > 1) {       /* no name for the primary entity */
               errorh(gbla_p, BADREAD, ERH_T_SEV, yb_p->oeb.ety_p->name._.p);
            }else {
               errorh(gbla_p, BADRDPRIM, ERH_T_SEV);
            }
            /* could fall thru (but will not: severity T ) */
         case END_PARSE_RC:
            exit(END_PARSE_RC);
         case END_RESUME_RC:
            exit(END_RESUME_RC);
         case 4:                            /* cannot read (EOF reached) */
            yb_p->buf_max = -1;             /* set EOF */
            c = Ee;
            goto imm_return;                /* immediate return for EOF */
         }
         /* Reset the ofrw if Application did change the column or offset */
         (yb_p->ofrw)[OFRW_MAX-1] = yb_p->oeb.offset - yb_p->oeb.col;
      }
      if (c == GA(RS_char)) {               /* store RS offsets in last ofrw */
         memmove(yb_p->ofrw, yb_p->ofrw + 1, (OFRW_MAX-1)*sizeof(ulong));
         (yb_p->ofrw)[OFRW_MAX-1] = yb_p->oeb.offset;
         yb_p->oeb.col = 0;                 /* Compute the new row, zero col */
         ++yb_p->oeb.row;
      }
      if (is_nonsgml(c)) {
         /*------------------------------------------+
         |Because errorh() reenters em_read(), enter |
         |the non-sgml character in the buffer,      |
         |making this code will run only once        |
         +------------------------------------------*/
         GA(curr_char)=(GA(ebuf))[GA(max)=(GA(head) &= BUFSIZE-1)] = c;
         if ((!GA(is_ocs_done)) && (c >= 32) && (c <= 128)) {
            /* Distinguish non-SGML from chars not allowed in a SGML MD */
            errorh(gbla_p,INVSGCHAR,ERH_W_SEV+ERH_LOCATE+ERH_NONSGML,c);
         }else {
            errorh(gbla_p,INVCHAR,ERH_W_SEV+ERH_LOCATE+ERH_NONSGML,c);
         }
         return(c);
      }
      imm_return:
      return(GA(curr_char)=(GA(ebuf))[GA(max)=(GA(head) &= BUFSIZE-1)] = c);
   }else {                                    /* unread next char */
      return(GA(curr_char)=(GA(ebuf))[(GA(head) &= BUFSIZE-1)]);
   }
}

/*---------------------------------------------------------------------em_unrd-+
| Unread i consecutive chunks in one shot.                                     |
|                                                                              |
| To improve the efficiency, no validity check is done: the caller is          |
| advised to keep a record of the number of chunks he is reading, and          |
| to unread only those, and not to unread more than BUFSIZE, and not to read   |
| past the Ee signal...                                                        |
+-----------------------------------------------------------------------------*/
uint P_FCT em_unrd(GA_P gbla_p, uint i)
{
#if (defined(WATC))                  /* Waterloo bug !!! (02/03-1989, 3.0) */
   GA(curr_char) = (GA(ebuf))[GA(head) = (GA(head)-i)&(BUFSIZE-1)];
   return(GA(curr_char));
#else
   return(GA(curr_char) = (GA(ebuf))[GA(head) = (GA(head)-i)&(BUFSIZE-1)]);
#endif
}

/*--------------------------------------------------------------------em_rplac-+
|  Replace (in the read BUFFER) a string of character that is "count" bytes    |
|  long by "new_char" (character reference handling).                          |
|                                                                              |
|  On ENTRY:                                                                   |
|    The character currently read (curr_char) is the one that                  |
|    follows immediately the string to be replaced.                            |
|  When RETURN:                                                                |
|    The string has been replaced, and curr_char is new_char (followed         |
|    by the character that was currently read).                                |
|                                                                              |
|  Ex:                                                                         |
|                                                                              |
|     curr_char = 'A'                               curr_char = '{'            |
|                            em_rplac('{',5)                                   |
|   |   X|&|1|2|3|;|A|                                |   X|}|A|               |
|   +-..-+-+-+-+-+-+-+         =====>                 +-..-+-+-+               |
|                                                                            |
|                   head                                    head               |
|                                                                              |
|   Count must normally be > 0;  as a feature, we implemented:                 |
|                                                                              |
|   Count == 0 is used to replace the current_char by new_char:                |
|                                                                              |
|     curr_char = 'A'                               curr_char = '{'            |
|                            em_rplac('{',0)                                   |
|   |   X|&|1|2|3|;|A|                              |   X|&|1|2|3|;|{|         |
|   +-..-+-+-+-+-+-+-+         =====>               +-..-+-+-+-+-+-+-+         |
|                                                                            |
|                   head                                            head       |
+-----------------------------------------------------------------------------*/
void P_FCT em_rplac(GA_P gbla_p, uchar new_char, uint count)
{
   int head_S, head_T;

   head_S = GA(head);                        /* remind where we are */
   GA(curr_char) = (GA(ebuf))[head_T = GA(head) = (GA(head)-count)&(BUFSIZE-1)]
   = new_char;
   if (count) {
      GA(yb_pC)->vdiff += (count-1);
      for (;;) {
         (GA(ebuf))[head_T=(head_T+1)&(BUFSIZE-1)] = (GA(ebuf))[head_S];
         if (head_S == GA(max)) break;
         head_S = (head_S+1)&(BUFSIZE-1);
      }
      GA(max) = head_T;
   }
}

/*----------------------------------------------------------------------em_ofs-+
|Return the offset within current entity of curr_char                          |
|This is the offset as "seen" by the user, without taking into account         |
|replacement (char references) or insertion (bracketed text).                  |
+-----------------------------------------------------------------------------*/
ulong P_FCT em_ofs(GA_P gbla_p)
{
   return(GA(yb_pC)->oeb.offset + GA(head) + (
   (GA(max) < GA(head)) ? BUFSIZE : 0) - GA(max) - 1);
}

/*---------------------------------------------------------------------em_vofs-+
|Return the virtual offset within current entity of curr_char                  |
|The virtual offset takes into account replacement and insertion               |
|done after the entity has been read.  It's main purpose is to unread          |
|properly in an edited buffer.                                                 |
+-----------------------------------------------------------------------------*/
ulong P_FCT em_vofs(GA_P gbla_p)
{
   return(GA(yb_pC)->oeb.offset - GA(yb_pC)->vdiff + GA(head) + (
   (GA(max) < GA(head)) ? BUFSIZE : 0) - GA(max) - 1);
}

/*--------------------------------------------------------------------em_locat-+
|Get the identification/location of the nth nested entity.                     |
|This routine is used to indicate errors and doesn't need to get the           |
|same complexity as em_pos (since it is just a rough indication for the user). |
|If too much negative values are returned, suppress "i".                       |
+-----------------------------------------------------------------------------*/
void P_FCT em_locat(
   GA_P gbla_p,
   int  requested_enty_level,
   uchar **name_pp,
   uchar **appl_info_pp,
   int *row_p,
   int *col_p)
{
   long offset, *ofrw_p;
   int ofrw_count;
   YB *yb_p = GA(yb_pC);

   if ((requested_enty_level -= GA(enty_level)) < 0) {
      do yb_p = yb_p->prev_p; while (++requested_enty_level < 0);
      offset = yb_p->oeb.offset - yb_p->fore_bufsize;
   }else {
      if (GA(max) < GA(head)) {
         offset = yb_p->oeb.offset - (BUFSIZE + GA(max) - GA(head));
      }else {
         offset = yb_p->oeb.offset - (GA(max) - GA(head));
      }
   }
   if IS_BITON(yb_p->oeb.ety_p->is,EXTERNAL_EF) { /* row, col */
      *row_p = yb_p->oeb.row;
      ofrw_p = yb_p->ofrw + ((ofrw_count = OFRW_MAX) - 1);
      if (offset < *ofrw_p) {                    /* For negative values */
         while (
            (--ofrw_count) && (--*row_p) &&      /* just in case */
            (offset < *(--ofrw_p))
         );
      }
      *col_p = (int)(offset - *(ofrw_p));
      *appl_info_pp = yb_p->oeb.appl_info;
   }else {
      *row_p = 0;
      *col_p = (int)(offset);
      *appl_info_pp = (uchar *)0;
   }
   *name_pp = yb_p->oeb.ety_p->name._.p; /* NULL pointer for internal literal */
}

#if YASP_RVL_GE(0,1,26)
/*----------------------------------------------------------------------em_pos-+
|Get (current position - i) and fill a POS structure.                          |
|A table of the offset of each row is kept to try to avoid em_pos returning    |
|negative values after character were unread.                                  |
+-----------------------------------------------------------------------------*/
POS * P_FCT em_pos(GA_P gbla_p, POS *pos_p, int i)
{
   long *ofrw_p;
   int ofrw_count;
   YB *yb_p = GA(yb_pC);

   if ((i += GA(max)-GA(head)) < 0) i += BUFSIZE;    /* number of unread char */
   pos_p->ofs   = yb_p->oeb.offset - i;
   pos_p->ety_p = yb_p->oeb.ety_p;

   if IS_BITON(yb_p->oeb.ety_p->is,EXTERNAL_EF) {    /* row, col */
      pos_p->row = yb_p->oeb.row;
      ofrw_p = yb_p->ofrw + ((ofrw_count = OFRW_MAX) - 1);
      if (pos_p->ofs < *ofrw_p) {                    /* For negative values */
         while (
            (--ofrw_count) && (--pos_p->row) &&      /* just in case */
            (pos_p->ofs < *(--ofrw_p))
         );
      }
      pos_p->col = (int)(pos_p->ofs - *(ofrw_p));
   }else {
      pos_p->row = 0;
      pos_p->col = (int)(pos_p->ofs);
   }
   return(pos_p);
}
#endif
/******************************************************************************/
