v20i029: ivinfo - InterViews emacs info file browser in C++, Part04/04
Tom Horsley
tom at hcx2.ssd.csd.harris.com
Thu May 30 12:35:50 AEST 1991
Submitted-by: Tom Horsley <tom at 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 at 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 at 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 at 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 at uunet.uu.net.
More information about the Comp.sources.misc
mailing list