/******************************************************************************/
/* SGMLIO: Ideas for non-DOS environments.                                    */
/* Includes both stream I/O and low-level I/O (stream tested for MSC only).   */
/******************************************************************************/
#include "vmincl.h"           /* Include files for VM. */
#include "vmxtrn.h"           /* Declarations for VM public variables. */
/******************************************************************************/
#ifdef IBMC                   /* Stream I/O for IBMC (mainframe) only. */
#define STREAMIO
#else
#define LOWLEVIO
#endif /* def IBMC */
#ifdef STREAMIO
#define OKIO(rc) ((UNIV)(rc)>NULL)  /* OPEN return: 1=good; 0=error. */
#define OPENREAD "rb"         /* Stream I/O: binary read (for MSC stream).  */
#define close fclose          /* Stream I/O: equivalent function. */
#define lseek fseek           /* Stream I/O: equivalent function. */
#define open  fopen           /* Stream I/O: equivalent function. */
#define tell  ftell           /* Stream I/O: equivalent function. */
#define eof   feof            /* Stream I/O: equivalent function. */
#else                         /* If low-level I/O: */
#define OKIO(rc) ((rc)>0)     /* OPEN return: 1=good; 0=error. */
#define OPENREAD (int)(O_RDONLY|O_BINARY) /* Low-level I/O: binary read. */
#endif /* def STREAMIO */
/******************************************************************************/
/* Functions used in this module only.
*/
struct iofcb *fcbgen(char *);
void fcbnext(struct iofcb *);
void readeof(struct iofcb *, char *, int *);
int readcat(struct iofcb *);
int readfrst(struct iofcb *, char *);
/******************************************************************************/
/* SGMLIO: Text processor I/O services for SGML.
   SGML must see a file in which RE and RS (CR/LF) are present between records,
   and EOFCHAR (Ctl-Z) is present at the end.  SGMLIO must supply these
   characters if they are not naturally present in the file.
   SGML will open two files at a time: when an entity is nested, the
   new file is opened before closing the old in order to make sure the
   open is successful. If it is, the original open file is closed temporarily
   (FILEPEND); when the stack is popped, the new file is closed and the original
   file is re-opened (FILECONT). SGML will check error returns
   for the initial open of a file and all reads, and for re-openings when the
   stack is popped, but not for closes.  Setting io.ipbrc<0 indicates
   an error; 0 or more is a successful operation,
   except for READ where io.ipbrc is the number of characters read, and must
   exceed 0 to be successful.  The first READ must always be successful, and
   normally consists of just priming the buffer with EOBCHAR (or RS EOBCHAR).
   SGMLIO must assure that there is an EOBCHAR at the end of each block read,
   except for the last block of the entity, which must have an EOFCHAR.

   SGML views an entity as a contiguous whole, without regard to its
   actual form of storage.  SGMLIO supports entities that are equivalent
   to a single file of one or more records, or to a concatenation of
   files (e.g., "profile.gml;main.gml").  It also recognizes
   an initial code that indicates whether prefixing of an RS and/or trimming
   of a trailing RE/RS are wanted (e.g., "3=profile.gml;main.gml").
   If the code is 1 or 3 (the default), RS is prefixed to the first
   record read for a file.  If 2 or 3 (the default), an RE/RS sequence
   occurring at the end of a file (before the EOF) will be trimmed.  Many
   editors insert such a sequence to allow file concatenation, but it can
   cause an extraneous space to occur when formatting.
   A default setting is contained in boundsw, which can be gotten from the
   command line or a processing instruction.  A code of 4 in a system identifier
   means to use the default value for that file, just as if no code were
   specified.  A code of 0 means that no special treatment is wanted.
*/
VOID sgmlio(io)
struct ipbfile *io;           /* IPB: file I/O services. */
{
     struct iofcb *f;         /* Active file control block. */
     int fnum;                /* READ: Return code from DOS. */
     char *flast;             /* READ: Last char read into buffer. */

     switch (io->ipbtype) {
     case FILENM:             /* Generate fileid from name & external ID. */
          io->ipbn = (UNIV)savestr(xidgen((struct fpi *)io->ipbn));
          return;
     case FILEOPEN:           /* Open new file for binary read-only. */
          f = fcbgen((UNCH *)io->ipbn);             /* Generate FCB. */
          io->ipbn = (UNIV)f;                       /* Return its pointer. */
          fcbnext(f);                               /* Make it next file. */
          f->fcbfd =  open(f->fcbfile+1, OPENREAD); /* Open file. */
          io->ipbrc = OKIO(f->fcbfd) ? 1 : -1;      /* Normalize return code. */
          f->fcbfirst = 1;                          /* Next read is first. */
          return;
     case FILEREAD:           /* Read file at current location. */
          f = (struct iofcb *)io->ipbn;      /* Get FCB pointer. */
          if (f->fcbfirst) {                 /* Fake first READ of an entity. */
               fnum = readfrst(f, io->ipbbuf);
               goto readrtrn;
          }
          if (f->fcbcatsw && readcat(f)) {   /* Open catenated file, if due. */
               fnum = -1; goto readrtrn;     /* Return if can't open it. */
          }
          f->fcboff = tell(f->fcbfd);        /* Location of START of block.*/
#ifdef LOWLEVIO
          if (eof(f->fcbfd)) {               /* File ended on previous READ. */
               *io->ipbbuf = EOFCHAR;        /* Fake read of Ctl-Z. */
               fnum = 1;
          }
          else fnum =  read(f->fcbfd, io->ipbbuf, readcnt); /* Read the file. */
          if (fnum<0) goto readrtrn;         /* Return if read error. */
          flast = io->ipbbuf + fnum-1;       /* Last character read. */
          if (*flast==EOFCHAR || fnum<readcnt) readeof(f, flast, &fnum);
          else *(io->ipbbuf + fnum++) = EOBCHAR;  /* Add EOB char. */
#else /* def STREAMIO */
         if (eof(f->fcbfd)) {                /* File ended on previous READ. */
               *io->ipbbuf = EOFCHAR;        /* Fake read of Ctl-Z. */
               *(io->ipbbuf+1) = EOS;        /* End the string. */
          }
         else
          fgets(io->ipbbuf, (int)readcnt, f->fcbfd); /* Read the next block. */
          if (ferror(f->fcbfd)) {
               fnum = -1; goto readrtrn;     /* Return if read error. */
          }
          fnum = (int)strlen(io->ipbbuf);    /* Length of record read. */
               /* TO DO: Insert RE,RS in front of record if needed. */
               /* If appended, fix READEOF to avoid it on EOF. */
          flast = io->ipbbuf + fnum;         /* Point after last char read. */
          if (*(--flast)==EOFCHAR) {         /* File ended with this READ. */
               readeof(f, flast, &fnum);
          }
          else {
               ++fnum; ++flast;              /* Include EOB char in count. */
               *flast = EOBCHAR;             /* Add EOB char. */
               /* TO DO: Insert RE,RS in front of record if needed. */
               /* If appended, fix READEOF avoid it on EOF. */
          }
#endif /* def STREAMIO */
          readrtrn:
          io->ipbrc = fnum;                                /* Do return code.*/
          return;
     case FILEPEND:           /* Close file temporarily. */
          (f = IPBFCB)->fcbcatsw = 0;                   /*Resume in same file.*/
          lseek(f->fcbfd, f->fcboff, SEEK_SET);         /* Start of block. */
          lseek(f->fcbfd, (long)io->ipboff, SEEK_CUR);  /* Current char. */
          f->fcboff =  tell(f->fcbfd);                  /* Save location. */
           close(f->fcbfd);
          return;
     case FILECONT:           /* Reopen file; position to saved location. */
          f->fcbfd =  open((f = IPBFCB)->fcbfile+1, OPENREAD);
          io->ipbrc = OKIO(f->fcbfd) ? 1 : -1;
          if (io->ipbrc>0)
              io->ipbrc = (int)lseek(f->fcbfd, f->fcboff, SEEK_SET);
          return;
     case FILECLOS:           /* Close file permanently. */
           close(IPBFCB->fcbfd);
          free((UNIV)IPBFCB);
          return;
     }
}
/******************************************************************************/
/* READEOF: Process end of file.  There are three possibilities:
            1. Data and Ctl-Z read.
            2. Data only read.
            3. Ctl-Z only read.
*/
void readeof(f, flast, pfnum)
struct iofcb *f;              /* Pointer to file control block. */
char *flast;                  /* Last char read into buffer. */
int *pfnum;                   /* Number of chars read. */
{
     int eofsw = 0;           /* 1=EOFCHAR present; 0=supply one. */

     if (*flast==EOFCHAR) eofsw = 1;    /* Set switch if EOF present. */
     else {++flast; ++*pfnum;}          /* Prepare for EOF or EOB. */
     if (f->fcbnext) {                  /* Another file in this entity? */
          f->fcbcatsw = 1;              /* For next READ. */
          *flast = EOBCHAR;             /* Not entity end. */
     }
     else {                             /* Entity ended: */
          if (!eofsw)                   /* If no EOF char? */
               *flast = EOFCHAR;        /* Add EOF char. */
          else if ( f->fcbRE            /* EOF was there: is RE trim wanted? */
            && *pfnum>=3                /* At least 2 chars before EOF? */
            && *(--flast)==0x0A         /* Is RS there? */
            && *(--flast)==0x0D )       /* Is RE there? */
               {*flast = EOFCHAR; *pfnum -= 2;}    /* Cut them off. */
     }
}
/******************************************************************************/
/* READCAT: Open concatenated file in current entity.
            Returns 1 if open was successful, 0 if not.
*/
int readcat(f)
struct iofcb *f;              /* Pointer to file control block. */
{
     f->fcbcatsw = 0;         /* Next read will not be new concatenated file. */
      close(f->fcbfd);        /* Close old file.*/
     fcbnext(f);              /* Find new file. */
     f->fcbfd =  open(f->fcbfile+1, OPENREAD);
     return (!OKIO(f->fcbfd));
}
/******************************************************************************/
/* READFRST: Simulate first READ of entity in order to handle RS insertion
             and guarantee an error-free return.
*/
int readfrst(f, ipbbuf)
struct iofcb *f;              /* Pointer to file control block. */
char *ipbbuf;                 /* IPBFILE: Ptr to SGML read buffer. */
{
     f->fcbfirst = 0;                   /* Next read will no longer be first. */
     if (f->fcbRS) {                    /* RS prefix wanted? */
        *((STRING)ipbbuf) = RSCHAR;     /* Put RS in buffer. */
        *((STRING)ipbbuf+1) = EOBCHAR;  /* And end block. */
        return(2);                      /* Successful "read" */
     }
     /* else */
        *((STRING)ipbbuf) = EOBCHAR;    /* Put EOB in buffer. */
        return(1);                      /* Successful "read" */
}
/******************************************************************************/
/* FCBGEN: Generates a file control block from an external identifier.
*/
struct iofcb *fcbgen(x)
UNCH *x;                      /* Ptr to external ID (len+EOS). */
{
     struct iofcb *f;         /* Ptr to new fcb. */

     f = (struct iofcb *)vmalloc((UNS)sizeof(struct iofcb));
     f->fcbxid = x;
     f->fcbRS = (int)*(x+1)-'0' & 1;
     f->fcbRE = (int)*(x+1)-'0' & 2;
     f->fcbnext = x+3;
     return(f);
}
/******************************************************************************/
/* FCBNEXT: Generates next full system fileid for a file control block.
*/
VOID fcbnext(f)
struct iofcb *f;              /* Pointer to file control block. */
{
     UNS i;                   /* Work variable. */
     UNCH *p, *n;             /* Work variable. */

     if ((p = f->fcbnext)==0) return;   /* Return if no more files for entity.*/
     f->fcbnext = 0;                    /* Assume this file is the last. */
     if ((n = strchr(p, ';'))!=0) {     /* If ; found, there is still another.*/
          *n = EOS;                     /* Temporary EOS for strlen to use. */
          f->fcbnext = n+1;             /* Location of next file after this. */
     }
     i = strlen(p);                     /* Get length of this file name. */
     memcpy(pd+1, p, ++i);              /* Move to buffer to work on it. */
     *pd = (char)++i;                   /* Prefix length to it. */
     p = xidpath(pd);                   /* Complete the fileid (if necessary).*/
     memcpy(f->fcbfile, p, *p);         /* Save full fileid in fcb. */
     if (f->fcbnext) *n = ';';          /* Put the ; back in the xid. */
}
/******************************************************************************/
/* XIDGEN: Generates a system identifier (fileid) from a name,
           and possibly a public or system identifier as well.
           It returns a ptr to the fileid in the fpi or in the pd buffer.
           Note 1: This routine assumes that an installation assigns
           reserved entity names that correspond to public identifiers, so it
           ignores the actual public identifier; in a more general approach,
           a table would be checked for the corresponding system ID.
           For version-dependent public entities, a real application
           would use different tables, depending on the display device
           (or different suffixes, in a scheme like that used here).
           If this routine is modified so that, under some conditions,
           it cannot generate a fileid, it should return 0 in those cases.
           FILEOPEN should check for the 0 and return an error code to SGML
           at that time.
           Note 2: This routine allows boundary treatment codes for data
           content notation files (f->fpistore==6) even though such files
           are not parsed.  VM strips the code before displaying the file
           identifier (which is analagous to a text processor stripping it
           before passing it to an application).  Alternatively, this
           routine could be modified so that the code is never inserted
           in the first place.
           Note 3: This is the place to check that system identifiers are
           valid for your environment, and that the boundary code (if
           specified) is valid.  SGML does not check such things because
           it knows nothing about the environment.
*/
UNCH *xidgen(f)
struct fpi *f;                /* Pointer to fpi control block. */
{
     UNS i, j;                /* Work variables. */
     UNCH *p;                 /* Work pointer. */

     *pd = 4;                 /* Starting length of constructed ID. */
     pd[1] = (char)boundsw;   /* Use default boundary treatment. */
     pd[2] = '=';             /* Boundary treatment delimiter. */
     p = pd+3;                /* Pt after boundary treatment delimiter. */
     /* If a complete system ID was specified, use it all.
        If only the boundary treatment was specified (n=), use it
        when constructing the system ID.
        If the boundary treatment was omitted, append the system ID to
        the default boundary treatment code.
     */
     if (f->fpisysl) {
          if (*(p = f->fpisysis+2)=='=') {
               if (*(--p)>'3' || *p<'0') *p = (char)boundsw;
               if (*(p+2)==EOS) {
                    memcpy(pd, f->fpisysis, 3);
                    p = pd+3;
               }
               else return(f->fpisysis);
          }
          else {
               memcpy(pd+3, f->fpisysis+1, f->fpisysl-1);
               *pd = f->fpisysl+2;
               return(pd);
          }
     }
     /* If not, add a suffix to the entity name to produce a system ID. */
     memcpy(p, f->fpinm+1, (i = f->fpinml-2));
     j = (f->fpipubl ? 1 : 0)*(f->fpiversw>0 ? 2 : 1)*6+f->fpistore;
     memcpy(p+i, genext[j], 5);    /* Add extension.*/
     *pd += (char)(i + 4);         /* Add entity name and extension lengths. */
     return(pd);
}
/******************************************************************************/
/******************************************************************************/
#ifdef IBMC
/******************************************************************************/
/* XIDPATH: The path buffer is used to store the completed fileid.
            TO DO: If mode is omitted, default it to the current drive,
                   or to * if cdirsw says path searching is wanted.
*/
UNCH *xidpath(pt)
UNCH *pt;                     /* Ptr to fileid in temporary storage (len+EOS).*/
{
      /* If path is to be searched, get the actual fileid. */
     if (filefind(pt+1, path+1)) {
          *path = (char)(strlen(path+1)+2);  /* Get len of actual fileid. */
          pt = path;                         /* Return ptr to actual fileid. */
     }
     return(pt);
}
/******************************************************************************/
/* FILEFIND: Returns 1 if a file exists, 0 if not.
             If it exists, the complete fileid is returned in caller's buffer.
             TO DO: Return the full fileid found by STATE.
*/
/******************************************************************************/
     char statebuf[27] = "STATE ";
/******************************************************************************/
int filefind(filename, buffer)
char *filename;               /* File to be searched for (with EOS). */
char *buffer;                 /* Buffer for found filename (with EOS). */
{
     if (strlen(filename)>20) return (0);    /* Return if invalid fileid.*/
     strcat(statebuf, filename);
     if (system(statebuf)) return (0);       /* Return if no file found. */
     strcpy(buffer, filename);
     return (1);
}
/******************************************************************************/
/******************************************************************************/
#else /* if ndef IBMC */
/******************************************************************************/
/******************************************************************************/
/* XIDPATH: Prefixes a path to a system identifier (DOS fileid) if
            path searching is wanted.
            The path buffer is used to build the prefixed fileid.
*/
UNCH *xidpath(pt)
UNCH *pt;                     /* Ptr to fileid in temporary storage (len+EOS).*/
{
     UNS i;                   /* Work variable. */

      /* If path is to be searched, get the full path name and fileid. */
     if (!cdirsw && ((i = filefind(pt+1, path+1))>1) ) {
          *path = *pt += (char)i;  /* Add len of path to fileid len. */
          pt = path;               /* Return offset of path+fileid. */
     }
     return(pt);
}
/******************************************************************************/
/* FILEFIND: This is the routine to locate a file, using SRCHPATH.
             If the file is found in the current directory, 1 is returned.
             If found in another directory on the environment path list,
             the length of the path name (no EOS) is returned; otherwise 0.
*/
/******************************************************************************/
#ifdef TURBOC
  #define srchpath(s) searchpath(s)
#else
  static char *srchpath(char *);
  static int testopen(char *);
#endif /* def TURBOC */
/******************************************************************************/
int filefind(filename, buffer)
char *filename;               /* File to be searched for (with EOS). */
char *buffer;                 /* Buffer for found filename (with EOS). */
{
     char *fptr;              /* Pointer to found filename. */
     unsigned fnlen;          /* Length of filename (+EOS). */

     _fmode = (int)O_BINARY;       /* Default file I/O is binary mode. */
     if ((fptr = srchpath(filename))==0) return (0);/*Return if no file found.*/
     memcpy( buffer , fptr, (fnlen = (unsigned)strlen(fptr)) );
     if (strlen(filename)==fnlen) return (1);     /* In current directory. */
     buffer[fnlen] = '\0';                        /* Somewhere on path. */
     return ((int)(fnlen+1));
}
#ifndef TURBOC
/******************************************************************************/
/* SRCHPATH: This file contains the routine to locate a file, utilizing the PATH
             environment variable for the directories to search.
             If the file is found in the current directory, a pointer to
             the filename argument is returned.  If the filename
             is found in another directory on the environment path list,
             the path name (with EOS) is stored in a static buffer, and a
             pointer to it is returned; if not found, NULL is returned.
             NOTE: DOS 2.0 or greater only; stack requirements: ~300 bytes.
*/
/******************************************************************************/
char tar_et[80];              /* Buffer for path (if any) + filename (+EOS). */
/******************************************************************************/
char *srchpath(filename)
char *filename;               /* File to be searched for (with EOS). */
{
     char paths[256];         /* Buffer for PATHS environment variable. */
     char *pptr;              /* Pointer into paths. */
     char *tptr;              /* Pointer into tar_et. */
     unsigned fnlen;          /* Length of filename (+EOS). */

     /* First check in the local directory */
     if (testopen(filename)>0) return(filename);
     /* No luck; search the path list from the environment. */
     if ((pptr = getenv("PATH"))==0) return (0);
     /* else */ strcpy(paths, pptr);
     fnlen = (unsigned)strlen(filename)+1;
     pptr = paths;
     while (*pptr != 0) {
          /* copy the directory name */
          tptr = tar_et;
          while (*pptr != ';' && *pptr != 0) {
               *tptr++ = *pptr++;
          }
          if (*pptr) pptr++;       /* beyond the ';' (ready for next try) */
          if (*(tptr-1) != '/' && *(tptr-1) != '\\')
               *tptr++ = '\\';
          /* Concatenate the filename to the path. */
          memcpy(tptr, filename, fnlen);
          if (testopen(tar_et)>0) return (tar_et);
     }
     return (0);                   /* can't find one */
}
/******************************************************************************/
/* TESTOPEN: Test whether a file can be opened.
*/
int testopen(pathname)
char *pathname;               /* File to be tested (with EOS). */
{
     int fid;                 /* File handle returned by open; work variable. */

     if ((fid = open(pathname, O_RDONLY))>0) {
          close(fid);
          return (1);
     }
     /* else */ return(0);
}
/******************************************************************************/
#endif /* ndef TURBOC */
/******************************************************************************/
#endif /* def IBMC */
/******************************************************************************/
/******************************************************************************/
