[comp.sources.misc] v20i029: ivinfo - InterViews emacs info file browser in C++, Part04/04

tom@hcx2.ssd.csd.harris.com (Tom Horsley) (05/30/91)

Submitted-by: Tom Horsley <tom@hcx2.ssd.csd.harris.com>
Posting-number: Volume 20, Issue 29
Archive-name: ivinfo/part04

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 4 (of 4)."
# Contents:  info.c
# Wrapped by tom@hcx2 on Wed May 29 10:48:52 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'info.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'info.c'\"
else
echo shar: Extracting \"'info.c'\" \(37794 characters\)
sed "s/^X//" >'info.c' <<'END_OF_FILE'
X#include "info.h"
X#include <fcntl.h>
X#ifndef O_BINARY
X#define O_BINARY 0
X#endif
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <errno.h>
X#include <string.h>
X#include <stdio.h>
X#include <ctype.h>
X#ifndef _tolower
X#define _tolower tolower
X#endif
X#include <limits.h>
X#include <stdlib.h>
X#include <unistd.h>
X
X// TODO - have InfoRoot check last access time of file periodically before
X//        doing a GetNode and flush the file and start over if the file has
X//        be written since the buffer was read.
X
Xextern "C" {
X   extern char * regcmp(const char *, ...);
X   extern char * regex(const char *, const char *, ...);
X}
X
X// *********************************************************** Static Functions
X
X// Compare 2 characters in a case-insensitive and space-insensitive fashion.
Xinline int
Xchricmp(char c1, char c2)
X{
X   if (isascii(c1)) {
X      if (isupper(c1)) {
X         c1 = _tolower(c1);
X      } else if (isspace(c1)) {
X         c1 = ' ';
X      }
X   }
X   if (isascii(c2)) {
X      if (isupper(c2)) {
X         c2 = _tolower(c2);
X      } else if (isspace(c2)) {
X         c2 = ' ';
X      }
X   }
X   return c1 - c2;
X}
X
X// Compare strings case-insensitive and space-insensitive (any white space
X// sequence in s1 matches any white space sequence in s2).
Xstatic int
Xstricmp(const char * s1, const char * s2)
X{
X   int stat;
X   char c1='x';
X   do {
X      if (isascii(c1) && isspace(c1)) {
X         while (isascii(*s1) && isspace(*s1)) ++s1;
X         while (isascii(*s2) && isspace(*s2)) ++s2;
X      }
X      stat = chricmp(c1 = *s1++, *s2++);
X   } while ((stat == 0) && (c1 != '\0'));
X   return stat;
X}
X
X// Compare strings case-insensitive and space insensitive limited by length
X// (length is assumed to be length of s2 when unequal amounts of white space
X// show up in strings).
Xstatic int
Xstrincmp(const char * s1, const char * s2, int len2)
X{
X   if (len2 <= 0) return 0;
X   int stat;
X   char c1='x';
X   do {
X      if (isascii(c1) && isspace(c1)) {
X         while (isascii(*s1) && isspace(*s1)) ++s1;
X         while ((--len2 > 0) && (isascii(*s2) && isspace(*s2))) ++s2;
X      }
X      stat = chricmp(c1 = *s1++, *s2++);
X   } while ((stat == 0) && (c1 != '\0') && (--len2 > 0));
X   return stat;
X}
X
X// ************************************************************** InfoFileBuffer
X
X// An info node starts with an ^_ char at the beginning of a line which is
X// followed by a newline or a ^L then a newline (*note format: (info)Add.)
Xint
XInfoFileBuffer::LookingAtNode(int offset)
X{
X   if ((offset < 0) || (offset >= PointMax())) return 0;
X   return ((offset == PointMin() || CharAfter(offset-1) == '\n') &&
X           (CharAfter(offset) == '\037') &&
X           ((CharAfter(offset+1) == '\n') ||
X            ((CharAfter(offset+1) == '\f') && (CharAfter(offset+2) == '\n'))));
X}
X
X// An info node is ended by a ^_ or ^L at the beginning of a line, or by end
X// of file.  (*note format: (info)Add.)
Xint
XInfoFileBuffer::LookingAtNodeEnd(int offset)
X{
X   if ((offset < 0) || (offset > PointMax())) return 0;
X   return ((offset == PointMax()) ||
X           ((CharAfter(offset-1) == '\n') &&
X            ((CharAfter(offset) == '\037') || (CharAfter(offset) == '\f'))));
X}
X
X// Scan out the fields of a node header. The fields can be in any order and
X// consist of Node:, Previous:, Up:, and Next:. The referenced node name
X// follows the keyword header and is terminated by comma, tab, or newline
X// (space is allowed). (*note format: (info)Add.)
X//
X// The terminator argument to this routine is used to allow a different set
X// of terminators to be used for things like scanning tag tables where DEL
X// terminates the node name.
X//
X// The routine returns the offset of any trailing data following the last
X// name found.
X
X#define EOL_STRING "\n\177"
X
Xint
XInfoFileBuffer::ScanHeader(
X   int offset,
X   int& name_off, int& name_len,
X   int& prev_off, int& prev_len,
X   int& up_off,   int& up_len,
X   int& next_off, int& next_len,
X   const char * terminator)
X{
X   name_len = 0;
X   prev_len = 0;
X   up_len = 0;
X   next_len = 0;
X   int next_avail_data = offset;
X   int keyword_offset, keyword_length;
X   int node_offset, node_length;
X   char c;
X   int state = 1;
X   while (state != 99) {
X      c = CharAfter(offset);
X      switch(state) {
X      case 1: // skipping leading white space prior to a keyword name
X         if (c == '\n' || c == '\0') {
X            state = 99;
X         } else if ((isascii(c) && isspace(c)) || (c == ':')) {
X            state = 1;
X         } else {
X            state = 2;
X            keyword_offset = offset;
X            keyword_length = 1;
X         }
X         break;
X      case 2: // looking for the ':' terminating a keyword name
X         if (c == '\n' || c == '\0') {
X            state = 99;
X         } else if (c == ':') {
X            if (strincmp("node",Text(keyword_offset),keyword_length) == 0 ||
X                strincmp("previous",Text(keyword_offset),keyword_length) == 0 ||
X                strincmp("next",Text(keyword_offset),keyword_length) == 0 ||
X                strincmp("up",Text(keyword_offset),keyword_length) == 0) {
X               state = 3;
X            } else {
X               state = 1;
X            }
X         } else if (isascii(c) && isspace(c)) {
X            state = 1;
X         } else {
X            keyword_length++;
X         }
X         break;
X      case 3: // skipping while space following keyword:
X         if (c == '\n' || c == '\0') {
X            state = 99;
X         } else if (isascii(c) && isspace(c)) {
X            state = 3;
X         } else {
X            node_offset = offset;
X            node_length = 1;
X            state = 4;
X         }
X         break;
X      case 4: // accumulating node name up to terminator char
X         if (strchr(terminator, c) != NULL) {
X            if (strchr(EOL_STRING, c) != NULL) {
X               state = 99;
X            } else {
X               state = 1;
X            }
X            if (strincmp("node", Text(keyword_offset),
X                         keyword_length) == 0) {
X               name_off = node_offset;
X               name_len = node_length;
X            } else if (strincmp("previous", Text(keyword_offset),
X                                keyword_length) == 0) {
X               prev_off = node_offset;
X               prev_len = node_length;
X            } else if (strincmp("up", Text(keyword_offset),
X                                keyword_length) == 0) {
X               up_off = node_offset;
X               up_len = node_length;
X            } else if (strincmp("next", Text(keyword_offset),
X                                keyword_length) == 0) {
X               next_off = node_offset;
X               next_len = node_length;
X            }
X            next_avail_data = offset;
X         } else {
X            node_length++;
X         }
X         break;
X      }
X      offset = ForwardChar(offset);
X   }
X   return next_avail_data;
X}
X
X// get_text is a private routine called allocate buffer space for the
X// file and read it. If the file cannot be read or is not a regular file
X// status is set to non-zero and a dummy buffer is filled in.
Xvoid
XInfoFileBuffer::get_text()
X{
X   struct stat statbuf;
X   int         fd;
X
X   if (text != NULL) return;
X   fd = open((const char *)file_name, O_RDONLY|O_BINARY);
X   if ((fd >= 0) &&
X       (fstat(fd, &statbuf) == 0) &&
X       ((statbuf.st_mode & S_IFMT) == S_IFREG)) {
X      text = new char [statbuf.st_size + 2];
X      int count = 0;
X      while (count < statbuf.st_size) {
X         int rstat = read(fd, text + count,
X                          (unsigned)(statbuf.st_size - count));
X         if (rstat == 0) break;
X         if ((rstat < 0) && (errno = EINTR)) rstat = 0;
X         if (rstat < 0) break;
X         count += rstat;
X      }
X      point_max = count;
X      text[count] = '\n';
X      text[count+1] = '\0';
X      if (count != statbuf.st_size) status = -2;
X   } else {
X      text = new char [2];
X      text[0] = '\n';
X      text[1] = '\0';
X      status = -1;
X   }
X   if (fd >= 0) close(fd);
X}
X
X// ForwardLine advances to the beginning of a line. The number of lines to
X// skip is given as the 'count' argument (default 1). If count is less than
X// zero, then BackwardLine is called. If count is equal to zero then the
X// offset is set to the beginning of the current line.
Xint
XInfoFileBuffer::ForwardLine(int offset, int count)
X{
X   if (count < 0) return BackwardLine(offset, - count);
X   if (count == 0) return BeginningOfLine(offset);
X   if (offset < 0) return 0;
X   while (count-- > 0) {
X      if (offset >= PointMax()) return PointMax();
X      offset = EndOfLine(offset);
X      offset = ForwardChar(offset);
X   }
X   return offset;
X}
X
X// BackwardLine backs up to the beginning of a line. The number of lines to
X// skip is given as the 'count' argument (default 1). If count is less than
X// zero, then ForwardLine is called. If count is equal to zero then the
X// offset is set to the beginning of the current line.
Xint
XInfoFileBuffer::BackwardLine(int offset, int count)
X{
X   if (count < 0) return ForwardLine(offset, - count);
X   if (count == 0) return BeginningOfLine(offset);
X   if (offset > PointMax()) return PointMax();
X   offset = BeginningOfLine(offset);
X   while (count-- > 0) {
X      if (offset <= 0) return 0;
X      offset = BackwardChar(offset);
X      offset = BeginningOfLine(offset);
X   }
X   return offset;
X}
X
X// Advance pointer to the end of a line (if already at end, stay there).
Xint
XInfoFileBuffer::EndOfLine(int offset)
X{
X   if (offset < 0) {
X      offset = 0;
X   } else if (offset >= PointMax()) {
X      return PointMax();
X   }
X   char * p = Text(offset);
X   while ((offset < PointMax()) && (*p != '\n')) {
X      ++offset;
X      ++p;
X   }
X   return offset;
X}
X
X// Backup pointer to beginning of a line (if already at beginning, stay there).
Xint
XInfoFileBuffer::BeginningOfLine(int offset)
X{
X   if (offset <= 0) return 0;
X   if (offset > PointMax()) offset = PointMax();
X   char * p = Text(offset);
X   while ((offset > 0) && (*(p-1) != '\n')) {
X      --offset;
X      --p;
X   }
X   return offset;
X}
X
X// ForwardNode advances to the beginning of a node. The number of nodes to
X// skip is given as the 'count' argument (default 1). If count is less than
X// zero, then BackwardNode is called. If count is equal to zero then the
X// offset is set to the beginning of the current node.
Xint
XInfoFileBuffer::ForwardNode(int offset, int count)
X{
X   if (count < 0) return BackwardNode(offset, - count);
X   if (count == 0) return BeginningOfNode(offset);
X   if (offset < 0) offset = 0;
X   while (count-- > 0) {
X      if (offset >= PointMax()) return PointMax();
X      offset = EndOfNode(offset);
X      while ((offset < PointMax()) && (! LookingAtNode(offset))) {
X         offset = ForwardLine(offset);
X      }
X   }
X   return offset;
X}
X
X// BackwardNode backs up to the beginning of a node. The number of nodes to
X// skip is given as the 'count' argument (default 1). If count is less than
X// zero, then ForwardNode is called. If count is equal to zero then the
X// offset is set to the beginning of the current node.
Xint
XInfoFileBuffer::BackwardNode(int offset, int count)
X{
X   if (count < 0) return ForwardNode(offset, - count);
X   offset = BeginningOfNode(offset);
X   while (count-- > 0) {
X      if (offset <= 0) return 0;
X      offset = BackwardLine(offset);
X      offset = BeginningOfNode(offset);
X   }
X   return offset;
X}
X
X// Advance to the end of a node. If at the beginning of a node (which
X// may also be the end of a previous node) advance forward anyway.
X// If at something like ^L which can end a node, but not begin one,
X// then stay there.
Xint
XInfoFileBuffer::EndOfNode(int offset)
X{
X   if (offset < 0) offset = 0;
X   if (offset > PointMax()) offset = PointMax();
X   if (LookingAtNode(offset)) offset = ForwardLine(offset);
X   while (! LookingAtNodeEnd(offset)) {
X      offset = ForwardLine(offset);
X   }
X   return offset;
X}
X
X// Back up to the beginning of a node. If already at the beginning, then
X// stay there.
Xint
XInfoFileBuffer::BeginningOfNode(int offset)
X{
X   if (offset < 0) offset = 0;
X   if (offset >= PointMax()) return PointMax();
X   while ((offset > 0) && (! LookingAtNode(offset))) {
X      offset = BackwardLine(offset);
X   }
X   return offset;
X}
X
XInfoFileBuffer::~InfoFileBuffer()
X{
X   if (text != NULL) delete text;
X}
X
X// ******************************************************************** InfoFile
X
X// This routine initializes the root node of a (possibly) indirect set
X// of info files.
Xvoid
XInfoFile::do_init()
X{
X   int offset = PointMax();
X   int lines = 8;
X
X   // If the string "End tag table" comes right at the end of a node, within
X   // the last eight lines of a file, then the file contains a node tag
X   // table which we read to build up a list of node which are assigned
X   // tentative addresses (according to the offset recorded in the tag
X   // table).
X   while (lines-- > 0) {
X      offset = BackwardLine(offset);
X      if (strincmp(Text(offset), "\037\nEnd tag table\n", 16) == 0) {
X         int last_tag_offset = BackwardLine(offset);
X         int tag_table_offset = BeginningOfNode(last_tag_offset);
X         int first_tag_offset = ForwardLine(tag_table_offset);
X         if (strincmp(Text(first_tag_offset), "Tag table:\n", 11) == 0) {
X            first_tag_offset += 11;
X
X            // If this is an indirect tag table, we also need to build a
X            // list of sub-files...
X            if (strincmp(Text(first_tag_offset), "(Indirect)\n", 11) == 0) {
X               first_tag_offset += 11;
X               int ind_offset = ForwardLine(BackwardNode(tag_table_offset));
X               if (strincmp(Text(ind_offset), "Indirect:\n", 10) == 0) {
X                  ind_offset += 10;
X                  // First count the number of files (include this file as
X                  // the first in the list at offset zero).
X                  int nfiles = 1;
X                  for (offset = ind_offset;
X                       ! LookingAtNodeEnd(offset);
X                       offset = ForwardLine(offset)) {
X                     nfiles += 1;
X                  }
X                  sub_file = new InfoSubFile * [nfiles];
X                  FileName dir(file_name.GetDirectory());
X                  sub_file[sub_file_count++] = this;
X                  for (offset = ind_offset;
X                       sub_file_count < nfiles;
X                       offset = ForwardLine(offset)) {
X                     char * fn = Text(offset);
X                     char * endfn = fn;
X                     while (*endfn != ':' && *endfn != '\n' && *endfn != '\0') {
X                        ++endfn;
X                     }
X                     char saveit = *endfn;
X                     *endfn = '\0';
X                     FileName subname(fn, (const char *)dir);
X                     *endfn = saveit;
X                     while (*endfn != '\n' && *endfn != '\0' &&
X                            (*endfn < '0' || *endfn > '9')) {
X                        ++endfn;
X                     }
X                     sub_file[sub_file_count++] =
X                        new InfoSubFile(subname, atoi(endfn) - 1);
X                  }
X               }
X            }
X
X            // Now go through the list of tags recording the node names
X            // and tentative offsets.
X            for (offset = last_tag_offset;
X                 offset >= first_tag_offset;
X                 offset = BackwardLine(offset)) {
X               int node_off, node_len, prev_off, prev_len, up_off, up_len,
X                   next_off, next_len;
X               int endoff = ScanHeader(offset, node_off, node_len,
X                  prev_off, prev_len, up_off, up_len, next_off, next_len,
X                  "\t,\n\177");
X               if ((node_len > 0) && (CharAfter(endoff) == '\177')) {
X                  char * namep = Text(node_off);
X                  char saveit = namep[node_len];
X                  namep[node_len] = '\0';
X                  node_list = new InfoNode(this, namep, atoi(Text(endoff+1))-1,
X                                           node_list);
X                  namep[node_len] = saveit;
X               }
X            }
X
X            // Normally last_scan_offset is used to record the place we left
X            // off scanning the file for nodes, but when we have a tag table
X            // we don't need to sequentially scan the file, so just set this
X            // to max int to make it appear as though the scan has been
X            // completed.
X            last_scan_offset = INT_MAX;
X         }
X      }
X   }
X}
X
XInfoNode *
XInfoFile::GetNode(const char * node_name)
X{
X   InfoNode * n;
X
X   // Scan list of nodes already seen and return the node of interest if
X   // found.
X   for (n = node_list; n != NULL; n = n->GetNext()) {
X      if (stricmp(n->GetName(), node_name) == 0) return n;
X   }
X
X   // Special case node name "*" to select entire file.
X   if (strcmp(node_name, "*") == 0) {
X      node_list = new InfoNode(this, node_list);
X      return node_list;
X   }
X
X   // Pick up sequential scanning through the file from the last place we
X   // looked and stop as soon as we find the right node.
X   while (last_scan_offset < PointMax()) {
X      last_scan_offset = ForwardNode(last_scan_offset);
X      if (LookingAtNode(last_scan_offset)) {
X         last_scan_offset = ForwardLine(last_scan_offset);
X         int node_off, node_len, prev_off, prev_len, up_off, up_len,
X             next_off, next_len;
X         ScanHeader(last_scan_offset, node_off, node_len,
X            prev_off, prev_len, up_off, up_len, next_off, next_len);
X         if (node_len > 0) {
X            node_list = new InfoNode(this, (InfoSubFile*)this,
X                                     last_scan_offset, node_off, node_len,
X                                     node_list);
X            if (stricmp(node_list->GetName(), node_name) == 0)
X               return node_list;
X         }
X      }
X   }
X
X   // I guess this node doesn't exist.
X   return NULL;
X}
X
XInfoFile::~InfoFile()
X{
X   if (sub_file_count > 0) {
X      for (int i = 1; i < sub_file_count; ++i) {
X         delete sub_file[i];
X      }
X      delete sub_file;
X   }
X   InfoNode * next_node;
X   InfoNode * this_node;
X   for (this_node = node_list; this_node != NULL; this_node = next_node) {
X      next_node = this_node->GetNext();
X      delete this_node;
X   }
X}
X
Xint
XInfoFile::ReSearch(
X   const char * re,
X   InfoNode * & found_node,
X   int & found_offset)
X{
X   last_re_offset = 0;
X   if (last_re != NULL) free(last_re);
X   last_re = regcmp(re, NULL);
X   return ReSearchAgain(found_node, found_offset);
X}
X
Xint
XInfoFile::ReSearchAgain(
X   InfoNode * & found_node,
X   int & found_offset)
X{
X   if (last_re == NULL) {
X      found_node = NULL;
X      found_offset = 0;
X      return 0;
X   }
X   // OK, this is it. Here I have to search through (possibly multiple)
X   // subfiles for the regular expression, determine what node the RE is in
X   // (skipping the match if it is not in any node), and return the node and
X   // offset within node of the end of the matching string.
X
X   // Determine which sub-file contains the offset to start at by picking
X   // through the indirect table (if any).
X   int sub_offset;
X   InfoSubFile * sf;
X   int i;
X   char * match_point;
X   if (sub_file_count > 0) {
X      for (i = 0; i < sub_file_count; ++i) {
X         if (last_re_offset >= sub_file[i]->InfoOffset() &&
X             ((i == sub_file_count - 1) ||
X              (last_re_offset < sub_file[i+1]->InfoOffset()))) {
X            sf = sub_file[i];
X            break;
X         }
X      }
X   } else {
X      sf = (InfoSubFile *)this;
X      i = sub_file_count + 1;
X   }
X   sub_offset = last_re_offset - sf->InfoOffset() +
X      sf->FirstNodeOffset();
X   if (sub_offset < sf->FirstNodeOffset()) sub_offset = sf->FirstNodeOffset();
X   for ( ; ; ) {
X      char *t0, *t1, *t2, *t3, *t4, *t5, *t6, *t7, *t8, *t9;
X      match_point = regex(last_re, sf->Text(sub_offset),
X         &t0, &t1, &t2, &t3, &t4, &t5, &t6, &t7, &t8, &t9, NULL);
X      if (match_point != NULL) {
X         sub_offset = match_point - sf->Text();
X         int node_offset = sub_offset;
X         if (! sf->LookingAtNode(sub_offset))
X            node_offset = sf->BackwardNode(node_offset, 0);
X
X         // If this isn't really a node, then try again
X         if (! sf->LookingAtNode(node_offset))
X            continue;
X
X         node_offset = sf->ForwardLine(node_offset);
X         int node_end_offset = sf->EndOfNode(node_offset);
X
X         // If the match was in a "dead zone" between two nodes, then
X         // loop back and try again.
X         if (node_end_offset <= sub_offset)
X            continue;
X
X         last_re_offset = sf->InfoOffset() +
X            (sub_offset - sf->FirstNodeOffset());
X         found_offset = sub_offset - node_offset;
X         int node_off, node_len, prev_off, prev_len, up_off, up_len,
X             next_off, next_len;
X         sf->ScanHeader(node_offset, node_off, node_len,
X            prev_off, prev_len, up_off, up_len, next_off, next_len);
X
X         // If no "node" header was found, then try again (probably was
X         // something like a tag table that looks like a node but isn't).
X         if (node_len == 0)
X            continue;
X
X         char * tempname = new char [node_len + 1];
X         strncpy(tempname, sf->Text(node_off), node_len);
X         tempname[node_len] = '\0';
X         found_node = GetNode(tempname);
X         delete tempname;
X         return (found_node != NULL);
X      }
X      if (++i < sub_file_count) {
X         sf = sub_file[i];
X         sub_offset = sf->FirstNodeOffset();
X      } else {
X         found_node = NULL;
X         found_offset = 0;
X         if (last_re != NULL) {
X            free(last_re);
X            last_re = NULL;
X         }
X         return 0;
X      }
X   }
X}
X
X// ******************************************************************** InfoNode
X
XInfoNode::InfoNode(
X   InfoFile * root,
X   const char * name,
X   int tentative_offset,
X   InfoNode * n)
X{
X   root_file = root;
X   sub_file = NULL;
X   node_name = new char [strlen(name)+1];
X   strcpy(node_name, name);
X   offset = tentative_offset;
X   next = n;
X   link_list = NULL;
X   length = -1;
X}
X
XInfoNode::InfoNode(
X   InfoFile * root,
X   InfoSubFile * fil,
X   int node_offset,
X   int nameoff,
X   int namelen,
X   InfoNode * n)
X{
X   root_file = root;
X   sub_file = fil;
X   offset = node_offset;
X   next = n;
X   node_name = new char [namelen+1];
X   strncpy(node_name, sub_file->Text(nameoff), namelen);
X   node_name[namelen] = '\0';
X   link_list = NULL;
X   missing = 0;
X   length = -1;
X}
X
XInfoNode::InfoNode(
X   InfoFile * root,
X   InfoNode * n)
X{
X   root_file = root;
X   next = n;
X   sub_file = (InfoSubFile *)root;
X   offset = 0;
X   node_name = new char [2];
X   strcpy(node_name, "*");
X   link_list = NULL;
X   length = sub_file->PointMax();
X   missing = 0;
X}
X
X// where_am_i looks up the actual position of a node given the initial
X// tentative position. It scans through the file in both directions starting
X// at the tentative position and looking 1K in either direction.
Xvoid
XInfoNode::where_am_i()
X{
X   // If we already know where this node is, return.
X   if (sub_file != NULL) return;
X
X   // Determine which sub-file contains the node of interest by picking
X   // through the indirect table (if any).
X   if (root_file->sub_file_count > 0) {
X      for (int i = 0; i < root_file->sub_file_count; ++i) {
X         if (offset >= root_file->sub_file[i]->InfoOffset() &&
X             ((i == root_file->sub_file_count - 1) ||
X              (offset < root_file->sub_file[i+1]->InfoOffset()))) {
X            sub_file = root_file->sub_file[i];
X            break;
X         }
X      }
X   } else {
X      sub_file = (InfoSubFile *)root_file;
X   }
X
X   // OK - it is not explicitly documented, but as near as I can tell by
X   // examining several info files, the offset recorded in the indirect
X   // table is NOT the offset of the beginning of the first char in the
X   // sub-file, but rather the offset of the 1st actual *node* in the
X   // sub-file (leading trash that is not part of a node - like copyright
X   // gibberish - is ignored).
X   int init_offset = sub_file->FirstNodeOffset() +
X                     (offset - sub_file->InfoOffset());
X   int back_off = sub_file->BackwardNode(init_offset);
X   int fore_off = sub_file->ForwardNode(back_off);
X   int node_off, node_len, prev_off, prev_len, up_off, up_len,
X       next_off, next_len;
X   missing = 1;
X   while (((fore_off - init_offset) < 1024) ||
X          ((init_offset - back_off) < 1024)) {
X      if (((fore_off - init_offset) < 1024) &&
X          (sub_file->LookingAtNode(fore_off))) {
X         fore_off = sub_file->ForwardLine(fore_off);
X         sub_file->ScanHeader(fore_off, node_off, node_len,
X            prev_off, prev_len, up_off, up_len, next_off, next_len);
X         if ((node_len == strlen(node_name)) &&
X             (strincmp(node_name, sub_file->Text(node_off), node_len) == 0)) {
X            missing = 0;
X            offset = fore_off;
X            break;
X         } else {
X            fore_off = sub_file->ForwardNode(fore_off);
X         }
X      } else {
X         fore_off = init_offset + 1025;
X      }
X      if (((init_offset - back_off) < 1024) &&
X          (sub_file->LookingAtNode(back_off))) {
X         back_off = sub_file->ForwardLine(back_off);
X         sub_file->ScanHeader(back_off, node_off, node_len,
X            prev_off, prev_len, up_off, up_len, next_off, next_len);
X         if ((node_len == strlen(node_name)) &&
X             (strincmp(node_name, sub_file->Text(node_off), node_len) == 0)) {
X            missing = 0;
X            offset = back_off;
X            break;
X         } else {
X            back_off = sub_file->BackwardNode(back_off);
X         }
X      } else {
X         back_off = init_offset - 1025;
X      }
X   }
X}
X
Xchar *
XInfoNode::Text()
X{
X   if (sub_file == NULL) where_am_i();
X   if (missing) return NULL;
X   return sub_file->Text(offset);
X}
X
Xint
XInfoNode::Length()
X{
X   if (sub_file == NULL) where_am_i();
X   if (missing) return 0;
X   if (length == -1) length = (sub_file->EndOfNode(offset) - offset);
X   return length;
X}
X
X// ScanLinks scans the body of the node looking for the next, prev, and up
X// links in the header, a menu in the body, or any notes in the body.  (for
X// detailed description of menu format see *note Menus: (info)Menus, for
X// detailed description of note format see *note Menus: (info)Cross-refs.)
Xvoid
XInfoNode::ScanLinks()
X{
X   // Make sure we have found the node.
X   if (sub_file == NULL) where_am_i();
X   if (missing) return;
X
X   // Scan the header to record Up, Previous, and Next.
X   int node_off, node_len, prev_off, prev_len, up_off, up_len,
X       next_off, next_len;
X   sub_file->ScanHeader(offset, node_off, node_len,
X      prev_off, prev_len, up_off, up_len, next_off, next_len);
X   if (up_len > 0)
X      link_list =
X         new InfoLink("Up", up_off-offset, up_len, link_list, InfoLinkUp);
X   if (prev_len > 0)
X      link_list =
X         new InfoLink("Previous", prev_off-offset, prev_len, link_list,
X                      InfoLinkPrev);
X   if (next_len > 0)
X      link_list =
X         new InfoLink("Next", next_off-offset, next_len, link_list,
X                      InfoLinkNext);
X
X   // Scan the body for menus and notes
X   char * p = sub_file->Text(offset);
X   int curoff = offset;
X   char * endp = p + Length();
X   int in_menu = 0;
X   char prevc = '\n';
X   while (p < endp) {
X      char c = *p++;
X      ++curoff;
X      if (c == '*') {
X         int isxref=0;
X         int foundoff = -1;
X         InfoLinkKind lk;
X         if (! in_menu && prevc == '\n' && strincmp(p, " Menu:", 6) == 0) {
X            // This is a menu header, remember the fact that we are in a menu
X            in_menu = 1;
X         } else if (prevc == '\n' &&
X                    in_menu &&
X                    *p == ' ' &&
X                    ! isspace(p[1])) {
X
X            // This is a menu item, scan out the item name and node reference
X            // starting after the space.
X            foundoff = curoff+1;
X            lk = InfoLinkMenu;
X         } else if ((strincmp(p, "note", 4) == 0) &&
X                     isspace(p[4])) {
X
X            // This is a cross reference, scan out the name and node starting
X            // after the word "note" and the space following it.
X            foundoff = curoff+5;
X            lk = InfoLinkNote;
X            isxref = 1;
X         }
X         if (foundoff >= 0) {
X            int nameoff, namelen, nodeoff, nodelen;
X            if (ScanRef(foundoff, nameoff, namelen, nodeoff, nodelen, isxref)) {
X               link_list = new InfoLink(sub_file->Text(nameoff), namelen,
X                                        nodeoff-offset, nodelen, link_list, lk);
X            }
X         }
X      }
X      prevc = c;
X   }
X}
X
Xint
XInfoNode::ScanRef(
X   int foundoff,
X   int& nameoff,
X   int& namelen,
X   int& nodeoff,
X   int& nodelen,
X   int  isxref)
X{
X   char * p = sub_file->Text(foundoff);
X   char * s = p;
X
X   // Skip any leading white space.
X   while (isspace(*s)) ++s;
X
X   // Now scan out the link name.
X   nameoff = foundoff + (s - p);
X   namelen = 0;
X   while (strchr((isxref ? "\t:" : "\t\n:"), *s) == NULL) {
X      ++namelen;
X      ++s;
X   }
X
X   // If we hit terminator prior to ':' at end of name, then bad format, so
X   // return false.
X   if (*s != ':') return 0;
X
X   // OK skip past ':'
X   ++s;
X
X   // If we have two ':' in a row, then the link name and the node reference
X   // are the same string and we are done.
X   if (*s == ':') {
X      nodeoff = nameoff;
X      nodelen = namelen;
X      return 1;
X   }
X
X   // Otherwise we have to skip white space to find the start of the node
X   // reference.
X   while (isspace(*s)) ++s;
X
X   // Now scan out the node ref.
X   nodeoff = foundoff + (s - p);
X   nodelen = 0;
X
X   // If we have a filename spec, scan for closing paren, don't terminate
X   // on . or , in filename part of node.
X   if (*s == '(') {
X      while (strchr("\t\n)", *s) == NULL) {
X         ++nodelen;
X         ++s;
X      }
X   }
X
X   // The node name part does terminate on . or ,
X   while (strchr((isxref ? "\t,." : "\t,\n."), *s) == NULL) {
X      ++nodelen;
X      ++s;
X   }
X   return 1;
X}
X
XInfoNode::~InfoNode()
X{
X   delete node_name;
X   InfoLink * this_link;
X   InfoLink * next_link;
X   for (this_link = link_list; this_link != NULL; this_link = next_link) {
X      next_link = this_link->GetNext();
X      delete this_link;
X   }
X}
X
X// ******************************************************************** InfoLink
X
XInfoLink::InfoLink(const char * name, int nl, int node, int nol, InfoLink * nxt,
X   InfoLinkKind lk)
X{
X   ref_name = new char [nl+1];
X   strncpy(ref_name, name, nl);
X   ref_name[nl] = '\0';
X   ref_node = node;
X   ref_node_len = nol;
X   next = nxt;
X   link_kind = lk;
X}
X
XInfoLink::InfoLink(const char * name, int node, int nol, InfoLink * nxt,
X   InfoLinkKind lk)
X{
X   ref_name = new char [strlen(name)+1];
X   strcpy(ref_name, name);
X   ref_node = node;
X   ref_node_len = nol;
X   next = nxt;
X   link_kind = lk;
X}
X
XInfoLink::~InfoLink()
X{
X   delete ref_name;
X}
X
X// ***************************************************************** InfoHistory
X
X// Constructor manufactures a new item for the top of the linked list of
X// history objects.
XInfoHistory::InfoHistory(InfoRoot * root, InfoNode * n)
X{
X   const char * filepart = n->GetFileName();
X   const char * nodepart = n->GetName();
X   node_name = new char [strlen(filepart) + strlen(nodepart) + 2 + 1];
X   strcpy(node_name, "(");
X   strcat(node_name, filepart);
X   strcat(node_name, ")");
X   strcat(node_name, nodepart);
X   history_root = root;
X   prev = root->prev_node;
X   root->prev_node = this;
X}
X
X// Descructor deletes the history entry from the list in the associated info
X// root node.
XInfoHistory::~InfoHistory()
X{
X   InfoHistory ** cfp = &history_root->prev_node;
X   while (*cfp != NULL) {
X      if (*cfp == this) {
X         *cfp = this->prev;
X         break;
X      }
X      cfp = &((*cfp)->prev);
X   }
X   delete node_name;
X}
X
X// InfoHistory::GetNode gets the info node associated with the history object.
XInfoNode * InfoHistory::GetNode()
X{
X   return history_root->GetNode(node_name);
X}
X
X// ******************************************************************** InfoRoot
X
XInfoRoot::InfoRoot(const char * ipath)
X{
X   last_file = NULL;
X   prev_node = NULL;
X   last_node = NULL;
X   if (root_count == 0) {
X      if (ipath == NULL) ipath = getenv(INFO_PATH);
X      if (ipath == NULL) ipath = DEFAULT_INFO_PATH;
X      char * endpath = (char *)ipath;
X      while (*endpath != '\0') {
X         ipath = endpath;
X         while (*ipath == ':') ++ipath;
X         endpath = strchr(ipath, ':');
X         if (endpath == NULL) {
X            endpath = (char *)ipath + strlen(ipath);
X         }
X         int dirlen = endpath - (char *)ipath;
X         if (dirlen > 0) {
X            char * onedir = new char [dirlen+1];
X            strncpy(onedir, ipath, dirlen);
X            onedir[dirlen] = '\0';
X            FileName d(onedir);
X            AddInfoDirectory(d);
X            delete onedir;
X         }
X      }
X   }
X   ++root_count;
X}
X
XInfoRoot::~InfoRoot()
X{
X   --root_count;
X   if (root_count == 0) {
X      InfoDirList * this_dir;
X      InfoDirList * next_dir;
X      for (this_dir = info_dir_path; this_dir != NULL; this_dir = next_dir) {
X         next_dir = this_dir->next;
X         delete this_dir;
X      }
X
X      InfoFileList * this_f;
X      InfoFileList * next_f;
X      for (this_f = info_file_list; this_f != NULL; this_f = next_f) {
X         next_f = this_f->next;
X         delete this_f->file;
X         delete this_f;
X      }
X   }
X}
X
X// The InfoRoot class allows you to maintain a search list of info
X// directories.  If you reference a file never yet seen, it will look in
X// each of the directories until it finds it. This function adds directories
X// to the end of the list (if they are not already in the list).
Xvoid
XInfoRoot::AddInfoDirectory(FileName& d)
X{
X   for (InfoDirList ** dpp = &info_dir_path;*dpp != NULL;dpp = &(*dpp)->next) {
X      if (strcmp((const char *)(*dpp)->dir, (const char *)d) == 0) return;
X   }
X   *dpp = new InfoDirList(d, NULL);
X}
X
X// This is usually the main entry point into the info browser. It knows how
X// to parse complete (filename)nodename references, look up the file, then
X// look up the node. It keeps track of the last filename seen so references
X// to nodenames with no file refer to nodes in the most recently referenced
X// file.
XInfoNode *
XInfoRoot::GetNode(const char * node_name)
X{
X   char * my_node = (char *)node_name;
X
X   // If there is a leading (file) spec, strip it off and establish that
X   // file as the latest file.
X   if (*my_node == '(') {
X      char * fn = my_node+1;
X      char * fnend = strchr(fn, ')');
X      if (fnend != NULL) {
X         // Leave just the node part.
X         last_file = NULL;
X         my_node = fnend+1;
X         int filelen = fnend - fn;
X         if (filelen == 0) return NULL;
X         char * onefile = new char [filelen+1];
X         strncpy(onefile, fn, filelen);
X         onefile[filelen] = '\0';
X
X         // The name "DIR" is special, always indicating the root node, but
X         // the root node is actually named "dir", so make this one filename
X         // case-insensitive...
X         if (stricmp(onefile, "dir") == 0) strcpy(onefile, "dir");
X         int fnd = 0;
X         for (InfoDirList * d = info_dir_path; !fnd && d != NULL; d = d->next) {
X            FileName tryit(onefile, d->dir);
X            if (access((const char *)tryit, R_OK) == 0) {
X               for (InfoFileList * f = info_file_list; f != NULL; f = f->next) {
X                  if (strcmp(f->file->GetFileName(), (const char *)tryit) == 0){
X                     last_file = f->file;
X                     fnd=1;
X                     break;
X                  }
X               }
X               if (!fnd) {
X                  last_file = new InfoFile(tryit);
X                  info_file_list = new InfoFileList(last_file, info_file_list);
X                  fnd = 1;
X               }
X            }
X         }
X      }
X   }
X
X   // If no context for node name, return NULL
X   if (last_file == NULL) return NULL;
X
X   // The default node name is "Top".
X   if (*my_node == '\0') {
X      my_node = "Top";
X   }
X
X   // Now just lookup the node in the most recent file.
X   InfoNode * rval = last_file->GetNode(my_node);
X
X   if (rval != NULL) {
X      // If there is a current active node, make a history entry for it
X      // prior to establishing a new current node.
X      if (last_node != NULL) new InfoHistory(this, last_node);
X      last_node = rval;
X   }
X   return rval;
X}
X
XInfoNode *
XInfoRoot::PopHistory()
X{
X   if (prev_node == NULL) return NULL;
X
X   // OK. I am popping the current node off the list to get back to the
X   // previous node, so I don't want a history entry for current node.
X   last_node = NULL;
X
X   InfoNode * rval = prev_node->GetNode();
X   delete prev_node;
X   return rval;
X}
X
Xint
XInfoRoot::ReSearch(
X   const char * re,
X   InfoNode * & found_node,
X   int & found_offset)
X{
X   if (last_file != NULL) {
X      last_file->ReSearch(re, found_node, found_offset);
X      if (found_node != NULL) {
X         // If there is a current active node, make a history entry for it
X         // prior to establishing a new current node.
X         if ((last_node != NULL) && (last_node != found_node))
X            new InfoHistory(this, last_node);
X         last_node = found_node;
X      }
X   } else {
X      found_node = NULL;
X      found_offset = 0;
X   }
X   return (found_node != NULL);
X}
X
Xint
XInfoRoot::ReSearchAgain(
X   InfoNode * & found_node,
X   int & found_offset)
X{
X   if (last_file != NULL) {
X      last_file->ReSearchAgain(found_node, found_offset);
X      if (found_node != NULL) {
X         // If there is a current active node, make a history entry for it
X         // prior to establishing a new current node.
X         if ((last_node != NULL) && (last_node != found_node))
X            new InfoHistory(this, last_node);
X         last_node = found_node;
X      }
X   } else {
X      found_node = NULL;
X      found_offset = 0;
X   }
X   return (found_node != NULL);
X}
END_OF_FILE
if test 37794 -ne `wc -c <'info.c'`; then
    echo shar: \"'info.c'\" unpacked with wrong size!
fi
# end of 'info.c'
fi
echo shar: End of archive 4 \(of 4\).
cp /dev/null ark4isdone
MISSING=""
for I in 1 2 3 4 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 4 archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
======================================================================
domain: tahorsley@csd.harris.com       USMail: Tom Horsley
  uucp: ...!uunet!hcx1!tahorsley               511 Kingbird Circle
                                               Delray Beach, FL  33444
+==== Censorship is the only form of Obscenity ======================+
|     (Wait, I forgot government tobacco subsidies...)               |
+====================================================================+

exit 0 # Just in case...
-- 
Kent Landfield                   INTERNET: kent@sparky.IMD.Sterling.COM
Sterling Software, IMD           UUCP:     uunet!sparky!kent
Phone:    (402) 291-8300         FAX:      (402) 291-4362
Please send comp.sources.misc-related mail to kent@uunet.uu.net.