Path: utzoo!utgpu!news-server.csri.toronto.edu!bonnie.concordia.ca!uunet!sparky!kent From: tom@hcx2.ssd.csd.harris.com (Tom Horsley) Newsgroups: comp.sources.misc Subject: v20i029: ivinfo - InterViews emacs info file browser in C++, Part04/04 Message-ID: <1991May30.023550.944@sparky.IMD.Sterling.COM> Date: 30 May 91 02:35:50 GMT References: Sender: kent@sparky.IMD.Sterling.COM (Kent Landfield) Organization: Sterling Software, IMD Lines: 1264 Approved: kent@sparky.imd.sterling.com X-Md4-Signature: aa7a043f31d53fbe6ae88b73f075904e Submitted-by: Tom Horsley 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 'info.c' <<'END_OF_FILE' X#include "info.h" X#include X#ifndef O_BINARY X#define O_BINARY 0 X#endif X#include X#include X#include X#include X#include X#include X#ifndef _tolower X#define _tolower tolower X#endif X#include X#include X#include 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.