[net.sources] genealogy program source

jimmc@sci.UUCP (Jim McBeath) (01/21/85)

About a week ago I sent out a request for some software to draw
family trees.  I didn't find anyone who had anything, but there
did seem to be some interest in what I have.  This is a pretty young
program, and I submit it to the net in the hopes that there is
a large enough community of people interested in genealogy that we
can standardize on a data format and assist each other in creating
and improving the programs which work with that data.  Whether or
not this is the case, I plan on continuing to improve what I have.

Jim McBeath     ...{decwrl|qubix|turtlevax}!sci!jimmc
Silicon Compilers Incorporated, San Jose, CA  (408)371-2900


# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
#
# This archive contains:
# GENEAL.DOC Makefile PGMR.DOC README dataman.c errorman.c famgetdat.c family.1003 family.c famtree.c famtree.h gbrowse.c geneal.c geneal.h genealogy.dat index.c index.h indivs.c pagemap.c pagemap.h strsav.c tprintf.c vsprintf.c xalloc.c

echo x - GENEAL.DOC
cat > "GENEAL.DOC" << '//E*O*F GENEAL.DOC//'
Documentation for geneal
last edit 20-Jan-85 17:03:02 by jimmc (Jim McBeath)

Geneal is a program to read a database of family relations and
print the information out in various formats.  It deals with
families and individuals, each identified by a unique integer.
The database contains information such as birth, death and
marriage dates and places (if you know them!), relations between
people (i.e. what children belong to X, are you my mother),
and miscellaneous information of general interest.
The program can read the information for a specified individual
and output it in a specified format.  At the moment there are two
formats (only one of which is "real"): the tree, and the family
page.  The tree format is only a tiny bit implemented, so it's
not very useful yet.  The family page gives information about
an entire family (one generation), i.e. parents, children, and
childrens's spouses.  More formats can easily be added (and will
be in the future!).

Individuals or families are specified by index number.  Thus the
command to output the family data page for a particular family
would be
    geneal -f 1025
where 1025 is the index number for that family.

Geneal expects one argument on the command line, which is the index
number of the person or family to center the data search/output on.
The switches available are:
    -f   produce a family information page
    -h   give this help message (try this in case this doc. file is wrong!)
    -i   produce information about an individual
    -s   use a short form of output
    -t   produce a tree (not implemented very far!)
    -D   debug data routines
    -F file   use the specified file as datafile
    -I   debug index routines
    -N   output internal index numbers with all names
If no format switches (-f, -i, -t) are given, browse mode is entered.
The two debug switches (-D and -I) will probably not be useful to you
unless you are familiar with the dataman and index routines which they
apply to.

There is some flexibility in the data file as to what numbers you
use as index numbers.  I have adopted the convention that individuals
are numbered starting at 1 and families starting at 1000 (when I get
1000 individuals, I will start the next group at 10000).  You may
want to use different conventions, e.g. odd numbers for males, even
for females; families starting at a higher number; or whatever.

				-Jim McBeath
				 15-Jan-1985
//E*O*F GENEAL.DOC//

echo x - Makefile
cat > "Makefile" << '//E*O*F Makefile//'
CFLAGS	=	-pg

LINTFLAGS =	

DEST	      = .

EXTHDRS	      = /usr/include/ctype.h \
		/usr/include/stdio.h

HDRS	      = famtree.h \
		geneal.h \
		pagemap.h

LDFLAGS	      = -g

LINKER	      = cc

MAKEFILE      = Makefile

OBJS	      = dataman.o \
		errorman.o \
		famgetdat.o \
		family.o \
		famtree.o \
		gbrowse.o \
		geneal.o \
		index.o \
		indivs.o \
		pagemap.o \
		strsav.o \
		tprintf.o \
		vsprintf.o \
		xalloc.o

LIBS          =	

PRINT	      = pr

PROGRAM	      = geneal

SRCS	      = dataman.c \
		errorman.c \
		famgetdat.c \
		family.c \
		famtree.c \
		gbrowse.c \
		geneal.c \
		index.c \
		indivs.c \
		pagemap.c \
		strsav.c \
		tprintf.c \
		vsprintf.c \
		xalloc.c

all:		$(PROGRAM)

$(PROGRAM):     $(OBJS) $(LIBS)
		@echo -n "Loading $(PROGRAM) ... "
		@$(LINKER) $(LDFLAGS) $(OBJS) $(LIBS) -o $(PROGRAM)
		@echo "done"

clean:;		@rm -f $(OBJS)

depend:;	@echo Updating makefile
		@mkmf -f $(MAKEFILE) PROGRAM=$(PROGRAM) DEST=$(DEST)

index:;		@ctags -wx $(HDRS) $(SRCS)

lint:;		lint $(LINTFLAGS) $(SRCS) $(LINTLIBS)

install:	$(PROGRAM)
		@echo Installing $(PROGRAM) in $(DEST)
		@install -s $(PROGRAM) $(DEST)

print:;		@$(PRINT) $(SRCS) $(HDRS)

program:        $(PROGRAM)

tags:           $(HDRS) $(SRCS); @ctags $(HDRS) $(SRCS)

update:		$(DEST)/$(PROGRAM)

$(DEST)/$(PROGRAM): $(SRCS) $(LIBS) $(HDRS) $(EXTHDRS)
		@make -f $(MAKEFILE) DEST=$(DEST) install

###
dataman.o: /usr/include/stdio.h
errorman.o: /usr/include/stdio.h
famgetdat.o: /usr/include/stdio.h geneal.h famtree.h
family.o: /usr/include/stdio.h /usr/include/ctype.h geneal.h pagemap.h
famtree.o: /usr/include/stdio.h geneal.h famtree.h pagemap.h
gbrowse.o: geneal.h
geneal.o: /usr/include/stdio.h /usr/include/ctype.h
indivs.o: geneal.h
pagemap.o: pagemap.h /usr/include/stdio.h
vsprintf.o: /usr/include/stdio.h
xalloc.o: /usr/include/stdio.h
//E*O*F Makefile//

echo x - PGMR.DOC
cat > "PGMR.DOC" << '//E*O*F PGMR.DOC//'
Programmer Documentation for geneal
Written by Jim McBeath (jimmc) at SCI
last edit 20-Jan-85 17:00:33 by jimmc (Jim McBeath)

This document describes some of the files used in geneal.  It is
intended to assist someone who may want to fix a bug or improve the
program, or perhaps use one of the general modules for another program.
If you do any of the above, I would be interested in hearing about it.

The geneal program is divided up into a number of general purpose files
and some special files specifically for the geneal program.
The general files include:
    dataman.c		Simple reader for formatted data file
    errorman.c		Error message routines
    index.c		Routines to manipulate large virtual arrays
    pagemap.c		Build up an image of a page in memory and output it
    strsav.c		Save a malloc'ed copy of a string
    tprintf.c		sprintf into a malloc'ed string
    vsprintf.c		sprintf with a vector of arguments
    xalloc.c		allocate memory and die if no more
The geneal-specific files include:
    geneal.c		main program for geneal (switches etc)
    famgetdat.c		routines to generate one-line strings of data
    family.c		generate a page of info about a family
    famtree.c		generate a family tree (incomplete!)
    gbrowse.c		browse through the data
    indivs.c		generate info about an individual

Most of the general files are simple, and can be easily understood by
inspection.  The ones which deserve more extensive comment are dataman,
index, and pagemap.

DATAMAN is the interface to the datafile.  The datafile is in a particular
format (records, lines, keywords/values) which is decsribed at the beginning
of that file.  The routines in this module allow access to those data items,
typically given a record number and a keyword name. There were two primary
considerations behind selecting the format used in the data file and the
techniques used to read that file: 1) It
should be in a text format so that it can be edited by a text editor (I
didn't want to have to write a special datafile editor) and so that it is
human readable (so it could be used even before all the output routines
were written for any particular program); 2) The program should be able to
handle extremely large files.

The current implementation of DATAMAN works as follows: during initialization,
it scans through the data file looking for the start of each record.  It
then reads in the record number (an arbitrary but unique integer) and records
(using the INDEX module) the seek location in the file for that record.
When an access request is processed, it seeks to the location for that record
and then scans until it finds the requested keyword.  While this may not
be particularly fast, it does satisfy the above two requirements of simple
text file format and the ability to handle extremely large files.  It is
true that the initialization phase would take much longer for a very large
file, but the access time for any given item should be independent of
file size.

There are of course some optimizations and improvements which could be
made here.  The first two on my list are: 1) After scanning through the
data file creating the list of indexes and seek positions, write that
information out to a file - then the next time the program is run with
that same file, the dates can be checked, and the index file can be
read in if it is still newer (i.e. the data file has not changed). This
would greatly improve the initialization time for larger data files.
2) Cache the most recent records in memory (and perhaps sort the fields
in them).  In those cases where a program goes back and forth between a
few records (as often happens in the genealogy program when following
child pointers, parent pointers, etc.), this would help to speed up
the accessing.  However, remember that unix alreadys caches some disk
io, so this may not be as big a win as it seems at first.  (Try running
the geneal program - actually it's not all that slow, considering the
way it reads the data file!)

INDEX implements a large dynamic virtual array.  Each location in the
array is allowed to contain an integer (or a pointer, if you have more
data to store).  The routines allow you to set or get that value
(integer or pointer) for a specified index (conceptually the index
into a large table).  Internally, the data are stored in a number of
smaller tables, so that unused locations in the array need not take
up memory space.  For example, if you needed an array with indexes of
1 and 1000000, the amount of storage needed would be something like
1K (due to chunk size).  The approach used works well for arrays with
clusters of dense use, e.g. the number 1 to 1000, 1M to 1M+1000, 10M to
10M+1000, etc.  It does not work well for sparse but regular distributions,
e.g. 1K, 2K, 3K, etc.

The index table is implemented by a multi-level table.  The bottom level
table contains N data items; the next level up contains N pointers to
bottom level tables; the next level up contains N pointers to first
level pointer tables, etc. as far as needed.  The number N can be selected
in the initialization call for an array.  When the set routine is called,
enough tables are built to access the requested index.  When the get
routine is called, those tables are then followed to get to the data.
Thus the size of the table can grow as needed.

PAGEMAP is an embryonic module used to generate a page of character
data when it is desired to place things at particular locations on
the page.  The caller first initializes a page (giving the size), and
then calls routines to output strings and characters to particular
locations (row/column) on the page.  At the end, he calls a function
to output the page.  This function was written in order to do the
family tree part of geneal, but so far has not seen that use.


The geneal program has a number of non-general files intended only
for use in this program.  These were mentioned above and will be
discussed in a little more detail here.

GENEAL is the main program.  It scans the command line for arguments
and switches, calls any initialization functions, and dispatches to
the appropriate function for processing.

FAMGETDAT is the basic interface to dataman.  It is used to read certain
items of data for a particular person and return a string.  For example,
one of the functions reads the birth date and place, formats them into
a string ("b: 12-Oct-1855, Arlington, VA") and returns a pointer to that
string.  This type of routine is used to build up a list of information
about someone, to be output in some particular format.

FAMILY is a function to output information about a family in a particular
format.  This function is invoked by a switch on the command line.
The family page consists of information about the parents, their children,
and the children's spouses.  This function calls famgetdat a lot to collect
its information, then formats it and outputs it.  Mostly pretty
straightforward code.

FAMTREE is the routine which eventually will draw family trees.  At the
moment there is not very much there to that end.  It outputs the information
for one family in the format in which it will appear in the tree.  It
uses pagemap to build up this picture, so you can take a look at it if
you want to see an example of its use.

GBROWSE is the module which controls browsing through the data.  This is
a very simple control module at the moment, but should eventually grow to
have a much more sophisticated interface to allow searching of the data.

INDIVS creates a short form data page for an individual.

Have fun and send me your improvements!

			-Jim McBeath
			 15-Jan-1985
//E*O*F PGMR.DOC//

echo x - README
cat > "README" << '//E*O*F README//'
The GENEAL Program
Written by Jim McBeath (jimmc) at SCI

last edit 20-Jan-85 16:55:25 by jimmc (Jim McBeath)

This file describes what other files exist and what you will have to
do in order to make geneal useful to you.

Files of interest:
    *.[ch]	The source files for geneal.
    Makefile	Exactly that.
    README	What you are looking at right now.
    GENEAL.DOC	User documentation.  Describes (briefly) what
		geneal is all about.  Gives list of switches.
    PGMR.DOC	Programmer's documentation.  Gives more detail about some
		of the source files.  Read this if you are going to make
		any changes to the program or if you need to look for bugs.
    genealogy.dat    The data file.  This is a sample file for you to use
		as a template for creating your own data file.
    family.1003	Sample output file produced by "geneal -f 1003".

What to do (after unpacking):
1. Read GENEAL.DOC.
2. Make the program.  You should be able to simply issue a "make" command.
   If you wish, you can test it out at this point on the sample data file
   and make sure that it works.
3. Make your own data file.  Delete all of the supplied data file except
   record 0 (the description of the data format).  Add your own data in
   the same format.
4. Run the program.  There are numerous checks for data consistency, so
   you should get error messages if things do not point to each other
   properly.  These are typically easy to fix.
5. If you want to make changes, read PGMR.DOC first.
6. Send me your comments and improvements.

CAVEAT: This program was not written with portability in mind.  It runs
on a VAX with BSD4.2.  If you are running on another machine/system, you
may have problems.  If so, please let me know.

				-Jim McBeath
				 15-Jan-1985
//E*O*F README//

echo x - dataman.c
cat > "dataman.c" << '//E*O*F dataman.c//'
/* dataman - simple data manager for reading data from text files */
/* Written by Jim McBeath (jimmc) at SCI */
/* last edit 19-Jan-85 09:14:15 by jimmc (Jim McBeath) */

/* This module is a very simple data manager which allows a simple
   interface to a text file.  The text file is the database.  This
   module has routines to read that database (it is assumed that
   the writing is done by another program, e.g. a text editor).
   The format of the data file is as follows:

   A file consists of a sequence of records.  All records are ascii
   text.  Records are separated by blank lines.  Lines within each
   record contain the data for that record.  The first line of a
   record must begin with an integer.  This integer is the index
   number of that record, and is used to reference the record.  All
   other text on the line with the index number is ignored, so
   can be used as a comment line.

   The remaining lines in a record are data items for that record.
   A data item consists of a key and a payload.  The key is any
   string of printing characters followed by a colon (i.e. the
   key can't contain a colon!).  All remaining text on the line,
   after the colon, is the payload for that data item.

   Both index numbers and key strings must be unique; non-unique
   items will not be referenceable.  Index numbers need not be in
   any order.  Key strings should be short to increase speed.
   (Note that this is not designed to be a particularly fast
   system anyway!)
*/

extern char *malloc();

/*..........*/

#include <stdio.h>
#include "index.h"

struct dpoint {
    FILE *ff;		/* which data file to read */
    struct toplevel *xx;		/* pointer for index routine */
    };

struct toplevel *initIndex();

#ifdef vms
#define index strchr
#endif

int dataDebug=0;		/* set this flag to give debug output */
int dataStatus=0;		/* status after most recent operation */
char *dataErrStrs[] = {
    "successful data operation",	/* 0 */
    "can't open data file",		/* 1 */
    "no more memory",			/* 2 */
    "can't start an index table",	/* 3 */
    "invalid pointer to getData",	/* 4 */
    "no file open in getData",		/* 5 */
    "no index table in getData",	/* 6 */
    "no such index number",		/* 7 */
    "key not found",			/* 8 */
    };
#define ERET(n) { dataStatus=n; return 0; }

/*..........*/

struct dpoint *			/* a pointer to our internal struct */
				/* returns 0 on error */
initDataFile(fn)		/* init the data file to be used */
char *fn;			/* the filename to look up */
{
FILE *fp;
struct dpoint *pp;
struct toplevel *qq;
int iflag,n;
char linebuf[256];

fp = fopen(fn,"r");		/* get his data file */
if (!fp) ERET(1)		/* can't do anything if no file */
pp = (struct dpoint *)malloc(sizeof(struct dpoint));
if (!pp) {			/* if no memory for us */
    fclose(fp);			/* dump the file */
    ERET(2)			/* error return */
    }
pp->ff = fp;			/* put file pointer into our data block */
qq = initIndex();		/* start up an index table */
if (qq==0) {			/* if can't start up an index table */
    fclose(fp);
    (void)free(pp);
    ERET(3)
    }
pp->xx = qq;			/* save pointer to index table */
if (dataDebug) printf("initDataFile: index table is at %X\n", qq);
iflag = 1;			/* note next line can be index line */
while (!feof(fp)) {		/* scan through the file */
    fgets(linebuf,255,fp);	/* read a line */
    if (iflag) {		/* if we can look for an index line */
	if (sscanf(linebuf,"%d",&n)==1) {	/* and if we got a number */
	    if (dataDebug) printf("initDataFile: index %d at loc %d\n",
				n,ftell(fp));
	    setIndex(qq,n,(int)ftell(fp)); /* remember start of next line */
	    iflag=0;		/* have to get a blank line now */
	    }
	}
    else {			/* see if this is a blank line */
	if (linebuf[0]!='\n') iflag++;	/* note blank line */
	}
    }
dataStatus=0;
return pp;			/* return pointer to our structure */
}

/*..........*/

char *				/* returns a pointer to the data string */
				/*  return NULL if no data */
getData(pp,indexn,key)		/* get a data item */
struct dpoint *pp;		/* pointer to our structure */
int indexn;			/* the index of the record of interest */
char *key;			/* the key string */
{
int l;
char *cc,*index();
static char linebuf[256];

if (!pp) ERET(4)		/* check for valid pointer */
if (!(pp->ff)) ERET(5)		/* error return if no file open */
if (!(pp->xx)) ERET(6)		/* error if no index table pointer */
l = getIndex(pp->xx,indexn);	/* get the lseek value for that index */
if (l==0) ERET(7)		/* error if no lseek value for that index */
fseek(pp->ff,(long)l,0);		/* seek to the start of that line */
while (!feof(pp->ff)) {		/* read lines until eof (or blank) */
    fgets(linebuf,255,pp->ff);	/* read in a line */
    if (linebuf[0]=='\n') break;	/* stop on a blank line */
    cc = index(linebuf,':');	/* search for the colon */
    if (!cc) continue;		/* if no colon, take it as a comment line */
    *cc = 0;			/* replace colon with null */
    if (strcmp(linebuf,key)==0) {
	cc++;			/* point to start of payload */
	*(cc + strlen(cc) - 1) = 0;	/* remove \n from the end */
	dataStatus=0;
	return cc;	/* return pointer to payload */
	}
    }				/* continue - read next line and compare */
ERET(8)				/* eof or blank line, key not found */
}

/* end */
//E*O*F dataman.c//

echo x - errorman.c
cat > "errorman.c" << '//E*O*F errorman.c//'
/* errorman.c - general error handling routines */
/* Written by Jim McBeath (jimmc) at SCI */
/* last edit 19-Jan-85 09:01:20 by jimmc (Jim McBeath) */

/* To use these routines, the user must have the variable Progname declared
 * elsewhere; also, these routines call vsprintf, so that must be included
 * in the load list. */

#include <stdio.h>

#define ERROR_EXIT 1
#define BSIZE 200

extern char *Progname;		/* user must set up the progname to use */

/*..........*/

/* VARARGS1 */
warning(s,args)
char *s;
{
char buf[BSIZE];

    vsprintf(buf,s,&args);
    fprintf(stderr,"%s: warning: %s\n", Progname, buf);
}

/*..........*/

/* VARARGS1 */
fatalerr(s,args)
char *s;
{
char buf[BSIZE];

    vsprintf(buf,s,&args);
    fprintf(stderr,"%s: fatal error: %s\n", Progname, buf);
    exit(ERROR_EXIT);
}

/*..........*/

/* VARARGS1 */
errormsg(s,args)
char *s;
{
char buf[BSIZE];

    (void)vsprintf(buf,s,&args);
    fprintf(stderr,"%s: error: %s\n", Progname, buf);
}

/* end */
//E*O*F errorman.c//

echo x - famgetdat.c
cat > "famgetdat.c" << '//E*O*F famgetdat.c//'
/* famgetdat.c - routines to get pieces of info records */
/* Written by Jim McBeath (jimmc) at SCI */
/* last edit 19-Jan-85 08:45:48 by jimmc (Jim McBeath) */

/* These routines all package up various pieces of information
 * from specified records.  They each take as arguments a record
 * ID number; some take a field name as well.  Most return a pointer
 * to a string allocated from dynamic memory, which can be freed
 * by passing the pointer to the free() function.  Exceptions are
 * noted (e.g. famgetnum returns an int).
 * Functions are:
 * famgetnum(n,s)	returns the integer value of field s
 * famgetstr(n,s)	the basic string routine which returns field s
 * famgetbstr(n,s,b)	like famgetstr, but returns string in buffer b
 * famgettname(n)	all of name but last name
 * famgetbname(n)	full born (maiden) name
 * famgetfname(n)	full name (including maiden and married last names)
 * famgetbirth(n)	birth date and place
 * famgetdeath(n)	death date and place
 * famgetbirthdeath(n)	birth and death dates
 * famgetmarriage(n)	marriage date and place
 * famgetclist(n,av)	get child list; return value is ac, fills pointer av
 */

#include <stdio.h>
#include "geneal.h"
#include "famtree.h"

#define TRUE 1
#define FALSE 0

/* for forward references */
char *famgetdateplace();

/*..........*/

int
famgetnum(n,s)		/* get an item number from the data file */
int n;			/* person to get data item for */
char *s;		/* name of data item */
{
int tt, dd;
char *str;

    if (gendp==0) fatalerr("no data file opened");
    if (n<=0) return -1;
    str = getData(gendp,n,s);	/* get pointer to data item */
    if (str==0) return -1;	/* -1 if no such item */
    tt = sscanf(str,"%d", &dd);	/* get the number */
    if (tt!=1) return -1;	/* if no succesful scan */
    return dd;			/* return the number we found */
}

/*..........*/

char *
famgetstr(n,s)		/* get an item string from the data file */
int n;			/* person to get data item for */
char *s;		/* name of data item */
{
char *str;

    if (gendp==0) fatalerr("no data file opened");
    if (n<=0) return "";
    str = getData(gendp,n,s);	/* get pointer to data item */
    if (str==0) return "";	/* null string if no such item */
    return strsav(str);
}

/*..........*/

int			/* returns 1 if found, 0 if not */
famgetbstr(n,s,b)	/* get an item string from the data file */
int n;			/* person to get data item for */
char *s;		/* name of data item */
char *b;		/* the buffer to put it into */
{
char *str;

    if (gendp==0) fatalerr("no data file opened");
    if (n<=0) { *b=0; return 0; }	/* make string null */
    str = getData(gendp,n,s);	/* get pointer to data item */
    if (str==0) { *b=0; return 0; }	/* null string if no such item */
    strcpy(b,str);		/* copy the string to his buffer */
    return 1;
}

/*..........*/

char *
famgettname(n)		/* get the full name (except last name) */
int n;			/* person to get data item for */
{
char xname[200];
char fname[100], mname[100], nname[100];

    famgetbstr(n,"FN",fname);	/* get first name */
    famgetbstr(n,"MN",mname);	/* get middle name */
    famgetbstr(n,"NN",nname);	/* get nick-name */
    xname[0] = 0;		/* start with null */
    if (indexes) sprintf(xname,"[%d]", n);	/* put in the id number */
    addcstr(xname,fname);	/* add first name */
    addcstr(xname,mname);	/* add middle name */
    addpstr(xname,nname);	/* add nickname in parens */
    return strsav(xname);
}

/*..........*/

char *
famgetbname(n)		/* get the full born name */
int n;			/* person to get data item for */
{
char xname[200];
char fname[100], mname[100], nname[100], lname[100], oname[100];

    famgetbstr(n,"FN",fname);	/* get first name */
    famgetbstr(n,"MN",mname);	/* get middle name */
    famgetbstr(n,"NN",nname);	/* get nick-name */
    famgetbstr(n,"LN",lname);	/* get last name */
    famgetbstr(n,"LNM",oname);	/* original last name */
    xname[0] = 0;		/* start with null */
    if (indexes) sprintf(xname,"[%d]", n);	/* put in the id number */
    addcstr(xname,fname);
    addcstr(xname,mname);
    addpstr(xname,nname);
    addcstr(xname,oname);
    if (oname[0]==0) addcstr(xname,lname); /* use last name of no maiden */
    return strsav(xname);
}

/*..........*/

char *
famgetfname(n)		/* get the full name */
int n;			/* person to get data item for */
{
char xname[200];
char fname[100], mname[100], nname[100], lname[100], oname[100];

    famgetbstr(n,"FN",fname);	/* get first name */
    famgetbstr(n,"MN",mname);	/* get middle name */
    famgetbstr(n,"NN",nname);	/* get nick-name */
    famgetbstr(n,"LN",lname);	/* get last name */
    famgetbstr(n,"LNM",oname);	/* original last name */
    xname[0] = 0;		/* start with null */
    if (indexes) sprintf(xname,"[%d]", n);	/* put in the id number */
    addcstr(xname,fname);
    addcstr(xname,mname);
    addpstr(xname,nname);
    addcstr(xname,oname);
    addcstr(xname,lname);
    return strsav(xname);
}

/*..........*/

char *
famgetbirth(n)		/* get birth date info */
int n;			/* person to get data item for */
{
    return famgetdateplace(n,"B","BP","b");
}

/*..........*/

char *
famgetdeath(n)		/* get death date info */
int n;			/* person to get data item for */
{
    return famgetdateplace(n,"D","DP","d");
}

/*..........*/

char *
famgetbirthdeath(n)		/* get birth/death date info */
int n;			/* person to get data item for */
{
char bdate[100], ddate[100], dates[200];

    famgetbstr(n,"B",bdate);	/* get birth date */
    famgetbstr(n,"D",ddate);	/* get death date */
    if (*bdate==0 && *ddate==0) return "";
    if (*ddate==0) sprintf(dates," ( b: %s )", bdate);
    else if (*bdate==0) sprintf(dates," ( d: %s )", ddate);
    else sprintf(dates," ( b: %s, d: %s )", bdate, ddate);
    return strsav(dates);
}

/*..........*/

char *
famgetmarriage(n)		/* get marriage date info */
int n;			/* person to get data item for */
{
    return famgetdateplace(n,"M","MP","m");
}

/*..........*/

char *
famgetdateplace(n,dk,pk,cc)	/* get date/place info */
int n;			/* person to get data item for */
char *dk;		/* date keyword */
char *pk;		/* place keyword */
char *cc;		/* string to use as output key */
{
char date[100], place[100];

    famgetbstr(n,dk,date);	/* get date */
    famgetbstr(n,pk,place);	/* get place */
    if (*date==0 && *place==0) return "";
    if (*date && *place) return tprintf("%s: %s, %s", cc, date, place);
    if (*date) return tprintf("%s: %s", cc, date);
    return tprintf("%s: %s", cc, place);
}

/*..........*/

int			/* returns count */
famgetclist(n,av)
int n;			/* id of family */
int **av;		/* ADDRESS of where to put the av we return */
{
char cstr[200];
char *cstrp;
int *clist;
int i, ac, tt, dd;

    famgetbstr(n,"C",cstr);	/* get list of kids */
    for (ac=0, cstrp=cstr; *cstrp; ac++)
	cstrp=index(cstrp+1,',');	/* count separators */
    if (ac==0) return 0;
    clist = XALLOC(int,ac,"famgetclist");
    for(cstrp=cstr, i=0; (cstrp && *cstrp); i++)
    {
	if (i>=ac) fatalerr("loop too far on child list in famgetclist");
	tt = sscanf(cstrp,"%d",&dd);	/* read child number */
	if (tt==1) clist[i]=dd;
	else
	{
	    warning("bad child list format in family %d", n);
	    clist[i] = dd = -1;
	}
	cstrp = index(cstrp,',');
	if (cstrp!=0) cstrp++;
    }
    if (i!=ac)
    {
	warning("bad child list format in family %d", n);
	for (; i<ac; i++) clist[i] = -1;	/* fill with -1 */
    }
    *av = clist;	/* fill in pointer */
    return ac;		/* return count */
}

/*..........*/

addcstr(dd,ss)		/* add a string to another */
char *dd;		/* the string being built */
char *ss;		/* the string to add if not null */
{
    if (ss && *ss)
    {
	if (dd[0]) strcat(dd," ");
	strcat(dd,ss);
    }
}

/*..........*/

addpstr(dd,ss)		/* add a string in parens */
char *dd;		/* the string to add to */
char *ss;		/* the string to add in parens if not null */
{
    if (ss[0])
    {
	if (dd[0]) strcat(dd," ");
	strcat(dd,"(");
	strcat(dd,ss);		/* add the string in parens */
	strcat(dd,")");
    }
}

/* end */
//E*O*F famgetdat.c//

echo x - family.1003
cat > "family.1003" << '//E*O*F family.1003//'
    ANDREW Q. DOE & ELIZABETH NANCY (BETH) SMITH

    Andrew Q. Doe                        Elizabeth Nancy (Beth) Smith
    b: 3-Feb-1921, New York, NY          b: 4-Mar-1925, Hauppauge, NY
    m: 3-Aug-1948, San Francisco, CA

    CHILDREN                             SPOUSES OF CHILDREN

    David (Dave) Doe                     Nancy M. Walker
    b: 3-Jun-1949, Midville, USA         b: Jul-1950
    m: 6-Sep-1975

    John Michael (Norm) Doe              Jane Jill Jones
    b: 10-Feb-1952, Anytown, USA         b: 12-Dec-1953, Los Angeles, CA
    m: 15-Jun-1973, San Franciso, CA

//E*O*F family.1003//

echo x - family.c
cat > "family.c" << '//E*O*F family.c//'
/* family.c - produce a family information page */
/* Written by Jim McBeath (jimmc) at SCI */
/* last edit 19-Jan-85 08:47:46 by jimmc (Jim McBeath) */

#include <stdio.h>
#include <ctype.h>
#include "geneal.h"
#include "pagemap.h"

#define PAGEWID 80
#define LEFTMARGIN 4
#define RIGHTMARGIN 4
#define CENTERMARGIN 2
#define LEFTCOL ((PAGEWID-LEFTMARGIN-RIGHTMARGIN-CENTERMARGIN)/2)
#define RIGHTCOL (PAGEWID-LEFTMARGIN-RIGHTMARGIN-CENTERMARGIN-LEFTCOL)
#define SPOUSEOFFSET 10

#define TBLOCKSIZE 1000
#define NOTEMAX 500

struct tblock {		/* a text block */
    int width;		/* length of longest line */
    int lines;		/* number of lines */
    char *text[TBLOCKSIZE];	/* the text pointers */
    };

extern int gendp;		/* datafile index number */
extern int dataStatus;
extern char *dataErrStrs[];

int notenum;
int notecount;		/* for storing the footnotes */
char *notes[NOTEMAX];

/*..........*/

int			/* 0 if OK */
family(famnum)
int famnum;			/* the family to give info about */
{
char *rtype;
char *famname, *husbandname, *wifename;
char *husbandlname, *wifelname;
int husbandnum, wifenum;
char *headerline;
int i, cnum, *clist;
char *ss;

    notecount=notenum=0;
    rtype = famgetstr(famnum,"T");		/* get record type */
    if (rtype==0 || *rtype==0)
    {
	warning("no such record number %d", famnum);
	return 1;
    }
    if (rtype[0]!='F')
    {
	warning("record %d is not a family", famnum);
	return 1;
    }
    famname = famgetstr(famnum,"N");		/* get the family name */
    husbandnum = famgetnum(famnum,"H");	/* get husband and wife index nums */
    wifenum = famgetnum(famnum,"W");
    husbandname = famgetbname(husbandnum);
    wifename = famgetbname(wifenum);
    husbandlname = famgetstr(husbandnum,"LN");
    wifelname = famgetstr(wifenum,"LNM");
    if (wifelname==0 || wifelname[0]==0) wifelname = famgetstr(wifenum,"LN");

    if (husbandlname && husbandlname[0]==0) husbandlname=0;
    if (wifelname && wifelname[0]==0) wifelname=0;
    if (famname && famname[0]==0) famname=0;
    if (husbandname==0 || husbandname[0]==0) husbandname = "???";
    if (wifename==0 || wifename[0]==0) wifename = "???";
    if (famname && (husbandlname==0 || strcmp(famname,husbandlname)!=0))
	headerline = tprintf("%s - %s & %s", famname, husbandname, wifename);
    else
	headerline = tprintf("%s & %s", husbandname, wifename);
    for (ss=headerline; *ss; ss++)
	if (islower(*ss)) *ss = toupper(*ss);	/* convert to upper case */
    printf("%*s%s\n\n", LEFTMARGIN, "", headerline);
    printpair(0,famnum,husbandnum, wifenum);	/* print data about parents */
    cnum = famgetclist(famnum,&clist);
    if (cnum==0)
    {
/* be silent about no children...
	printf("%*s%s\n", LEFTMARGIN, "", "NO CHILDREN");
 */
    }
    else
    {
	printf("%*s%*s%*s%s\n\n", LEFTMARGIN, "", -LEFTCOL, "CHILDREN",
	    CENTERMARGIN, "", "SPOUSES OF CHILDREN");
	for (i=0; i<cnum; i++)
	{
	 int childnum, marriagenum, spousenum, chusbandnum, cwifenum;
	 int mnum;
	 char mnumstr[5];
    
	    childnum = clist[i];
	    for (mnum=0, marriagenum=1; marriagenum>0; mnum++)
			    /* until we run out of marriages */
	    {
		sprintf(mnumstr,"S%d", mnum);
		marriagenum = famgetnum(childnum,mnumstr);	/* get marriage */
		if (marriagenum>0)
		{
		    chusbandnum = famgetnum(marriagenum, "H");
		    cwifenum = famgetnum(marriagenum, "W");
		    if (childnum==chusbandnum) spousenum=cwifenum;
		    else if (childnum==cwifenum) spousenum=chusbandnum;
		    else
		    {
			warning(
			    "person %d claims marraige %d, but not vice-versa!",
			    childnum, marriagenum);
			spousenum= -1;
		    }
		}
		else spousenum = -1;
		if (mnum==0 || marriagenum>0) 
		    printpair(mnum,marriagenum,childnum,spousenum);
	    }
	}
    }
    if (notecount>0)		/* if we accumulated any notes */
    {
	printf("%*s%s\n\n", LEFTMARGIN, "", "-----------------------");
	for (i=0; i<notecount; i++)
	    printf("%*s%s\n", LEFTMARGIN, "",  notes[i]);
    }
    return 0;
}

/*..........*/

printpair(n,mn,cn,sn)	/* print info about a couple */
int n;			/* which marriage in the list this is; -1=only one */
int mn;			/* marriage number */
int cn;			/* primary person number */
int sn;			/* spouse number */
{
struct tblock cntb, sntb;	/* where to store data */
int i, max;

    fampdat(n,mn,cn,&cntb);		/* get the data */
    fampdat(-1,mn,sn,&sntb);
/* decide if they should both be on the same lines or not */
    if (cntb.width>LEFTCOL || sntb.width>RIGHTCOL) /* separate */
    {
	printtb(&cntb,LEFTMARGIN);	/* output the first one */
	printtb(&sntb,LEFTMARGIN+SPOUSEOFFSET);	/* output spouse */
    }
    else		/* both on the same line */
    {
	if (cntb.lines > sntb.lines) max = cntb.lines;
	else max = sntb.lines;
	for (i=0; i<max; i++)
	{
	    if (i>=cntb.lines)
		printf("%*s%s\n",
		    -(LEFTMARGIN+CENTERMARGIN+LEFTCOL), "", sntb.text[i]);
	    else if (i>=sntb.lines)
		printf("%*s%s\n",
		    LEFTMARGIN, "", cntb.text[i]);
	    else
		printf("%*s%*s%*s%s\n", LEFTMARGIN, "",
		    -LEFTCOL, cntb.text[i], CENTERMARGIN, "", sntb.text[i]);
	}
	printf("\n");
    }
}

/*..........*/

printtb(b,offset)		/* print a text block */
struct tblock *b;
int offset;			/* left margin offset */
{
int i;

    for (i=0; i<b->lines; i++)
	printf("%*s%s\n", offset, "", b->text[i]);
    if (b->lines!=0) printf("\n");
}

/*..........*/

fampdat(i,m,n,b)		/* get a tblock about a person */
int i;			/* iteration number to determine how to format */
int m;			/* marriage number */
int n;			/* the person to get info about */
struct tblock *b;	/* where to put the data */
{
char *name, *birth, *death, *marriage;

    b->lines = b->width = 0;	/* clear it out first */
    if (n<=0) return;
    name = famgetbname(n);
    birth = famgetbirth(n);
    death = famgetdeath(n);
    marriage = famgetmarriage(m);
    if (i<=0)
    {
	addtline(name,b);
	if (birth && *birth) addtline(birth,b);
	if (death && *death) addtline(death,b);
	if (i==0)
	{
	    if (marriage && *marriage) addtline(marriage,b);
	    addnotes(n,m,b);		/* add general comment notes */
	}
	else	/* i== -1 */
	    addnotes(n,-1,b);		/* don't add marriage notes */
    }
    else
    {
	if (marriage && *marriage) marriage = tprintf("re-%s", marriage);
	else marriage = "remarried:";
	addtline(marriage,b);
	addnotes(-1,m,b);
    }
}

/*..........*/

addnotes(n,m,b)		/* add general comment notes to a block */
int n;			/* index of person */
int m;			/* index of marriage */
struct tblock *b;	/* text block to add to */
{
    addnote(n,b,"");
    addnote(m,b," (m)");
}

/*..........*/

addnote(n,b,ss)		/* add general comment notes to a block */
int n;			/* index of person */
struct tblock *b;	/* text block to add to */
char *ss;		/* note indicator string */
{
int i;
char comnum[10];
char gencom[1000];
char *lnotes[1000];
int lcount;
int hlen;
int t;

    gencom[0]=0;		/* clear to start */
    lcount = 0;
    hlen = strlen(ss)+sizeof("Note")+2;
    for (i=0; i==0 || gencom[0]; i++)	/* read each comment */
    {
	sprintf(comnum,"GEN%d", i);
	famgetbstr(n,comnum,gencom);	/* read comment line */
	if (gencom[0]==0) break;
	lnotes[lcount++] = strsav(gencom);
    }
    if (lcount==1 && ((t=strlen(lnotes[0])+hlen)<=b->width||t<LEFTCOL))
    {		/* if we have one relatively short string, do it in-line */
	addtline(tprintf("[Note%s: %s]", ss, lnotes[0]),b);
    }
    else if (lcount>0)
    {
	addtline(tprintf("[Note %d%s]", 1+notenum, ss),b);
	notes[notecount++] = tprintf("Note %d%s:", 1+notenum++, ss);
	for (i=0; i<lcount; i++)
	{
	    notes[notecount++] = lnotes[i];	/* add string to notes */
	    if (notecount>=NOTEMAX-1)
	    {
		warning("notelist overflow!");
		notecount--;
	    }
	}
	notes[notecount++] = "";
    }
}

/*..........*/

addtline(ss,b)			/* add a line to a tblock */
char *ss;			/* the string to add */
struct tblock *b;		/* the block to add to */
{
int l;

    if (b->lines >= TBLOCKSIZE)
    {
	warning("tblock overflow!");
	return;			/* ignore the addition */
    }
    b->text[b->lines++] = ss;	/* add in the string */
    l = strlen(ss);
    if (l> b->width) b->width=l;	/* keep track of width */
}

/* end */
//E*O*F family.c//

echo x - famtree.c
cat > "famtree.c" << '//E*O*F famtree.c//'
/* famtree - make a family tree */
/* Written by Jim McBeath (jimmc) at SCI */
/* last edit 19-Jan-85 08:49:00 by jimmc (Jim McBeath) */

/* A family tree is composed of a number of family blocks
 * connected by lines.  Each family block is a rectangle, with
 * lines coming out the top and bottom for the father and mother
 * families, respectively, and one line coming out the right
 * for the child families.  The family blocks are then placed
 * in the appropriate positions and connected by extending the
 * lines.
 */

#include <stdio.h>
#include "geneal.h"
#include "famtree.h"
#include "pagemap.h"

#define TRUE 1
#define FALSE 0

Familysqp famgen();

extern int gendp;		/* the datafile index number */
extern int dataStatus;
extern char *dataErrStrs[];

/*..........*/

int			/* 0 if OK */
famtree(n)
int n;			/* index number of the person or family
				 to do the tree for */
{
Familysqp fm;

    fm = famgen(n);	/* generate this family block and all ancestors */
/*    famdump(fm);	/*** dump the info we created */
    famgtmp(fm);	/*** output a portion of the tree */
    return 0;		/* assume it all went OK! */
}

/*..........*/

Familysqp
famgen(n)		/* generate a family block for person or family n */
int n;
{
Familysqp fm;
char *rtype;
char *cstr, *cstr0;
int i;
int individual;		/* set if it is an individual; not set if family */
int nochildren;
int childfound;
int tt, dd;

    if (n<=0) return 0;
    fm = XALLOC(Familysq,1,"famgen");
    rtype = famgetstr(n,"T");		/* get record type */
    if (rtype==0 || *rtype==0) return 0;	/* must have this record! */
    if (rtype[0]=='I')		/* if an individual's record */
    {
	individual=1;
	fm->cnum = n;
	fm->pnum = famgetnum(n,"P");
    }
    else
    {
	individual=0;		/* not an individual */
	fm->cnum = -1;
	fm->pnum = n;
    }
    fm->pname = famgetstr(fm->pnum,"N");
    fm->fnum = famgetnum(fm->pnum,"H");
    fm->mnum = famgetnum(fm->pnum,"W");
    fm->ffamily = famgen(fm->fnum);
    fm->mfamily = famgen(fm->mnum);
    if (fm->pnum>0)
    {
	cstr = getData(gendp,fm->pnum,"C");	/* get child info */
	if (cstr==0 || *cstr==0 )
	{		/*** check for individual here? */
	    if (dataStatus==7)
		warning("inconsistent data: person %d claims family %d,\n\
	which does not exist!", n, fm->pnum);
	    else if (dataStatus==8)
		warning("inconsistent data: person %d claims family %d,\n\
	which has no child list!", n, fm->pnum);
	    else if (dataStatus==0)	/* must be strlen(cstr)==0 */
		warning("inconsistent data: person %d claims family %d,\n\
	which has an empty child list!", n, fm->pnum);
	    else
		warning("error in getData for family %d: %s",
			fm->pnum, dataErrStrs[dataStatus]);
	    nochildren=TRUE;
	}
	else	/* we have a cstr */
	{
	    for (i=0, cstr0=cstr; (cstr0 && *cstr0); i++)
		cstr0=index(cstr0+1,',');
		    /* count separators to determine number in list */
	    fm->ccount = i;
	    fm->clist = XALLOC(int,i,"famgen clist");
	    childfound=FALSE;
	    for(cstr0=cstr, i=0; (cstr0 && *cstr0); i++)
	    {
		if (i>=fm->ccount) fatalerr("loop too far on child list");
		tt = sscanf(cstr0,"%d",&dd);	/* read child number */
		if (tt==1) fm->clist[i]=dd;
		else
		{
		    warning("bad child list format in family %d", fm->pnum);
		    fm->clist[i] = dd = -1;
		}
		if (dd==n)
		{
		    childfound=TRUE;
		    fm->chloc = i;	/* remember his position in list */
		}
		cstr0 = index(cstr0,',');
		if (cstr0!=0) cstr0++;
	    }
	    if (individual && !childfound)
	    {
		warning("inconsistent data: person %d claims family %d,\n\
	but family does not claim child!", n, fm->pnum);
	    }
	    nochildren=FALSE;
	}
    }
    else nochildren=TRUE;
    if (nochildren)
    {
	fm->ccount = individual? 1 : 0;
	fm->clist = XALLOC(int,1,"famgen clist");
	fm->clist[0] = individual? n : 0;
	fm->chloc = 0;
    }
    fm->cnlist = XALLOC(char *,fm->ccount,"famtree cnlist");
    fm->cblist = XALLOC(char *,fm->ccount,"famtree cblist");
    fm->cols = 0;
    for (i=0; i<fm->ccount; i++)	/* fill in child info */
    {
	fm->cnlist[i] = famgettname(fm->clist[i]); /* get names */
	fm->cblist[i] = famgetbirthdeath(fm->clist[i]); /* get date info */
	if ((tt=strlen(fm->cnlist[i])) > fm->cols) fm->cols=tt;
	if ((tt=strlen(fm->cblist[i])) > fm->cols) fm->cols=tt;
		/* keep track of longest name */
    }
    if (strlen(fm->pname) > 2*fm->ccount) fm->lines = strlen(fm->pname);
    else fm->lines = 2*fm->ccount;
	/* there must be enough lines for both the vertical family name
		and the list of children (2 lines per child) */
    fm->acolmax = fm->cols;
    fm->alines = 0;
    fm->agens = 0;
    if (fm->ffamily)
    {
	if (fm->ffamily->acolmax > fm->acolmax)
	    fm->acolmax = fm->ffamily->acolmax;
	if (fm->ffamily->agens+1 > fm->agens)
	    fm->agens = fm->ffamily->agens+1;
	fm->alines += fm->ffamily->alines;
    }
    if (fm->mfamily)
    {
	if (fm->mfamily->acolmax > fm->acolmax)
	    fm->acolmax = fm->mfamily->acolmax;
	if (fm->mfamily->agens+1 > fm->agens)
	    fm->agens = fm->mfamily->agens+1;
	fm->alines += fm->mfamily->alines;
    }
    if (fm->lines > fm->alines) fm->alines = fm->lines;
/*** still need to calculate chline */
    fm->chline = 0;	/***/
    return fm;
}

/*..........*/

famdump(fm)		/* dump tree info */
Familysqp fm;
{
int i;

    if (fm==0) return;
    printf("Family at %X: \"%s\", ", fm, fm->pname);
    printf("P=%d, F=%d, M=%d, C=%d, ",
	fm->pnum, fm->fnum, fm->mnum, fm->cnum);
    printf("ccount=%d, clist=%X:\n", fm->ccount, fm->clist);
    for (i=0; i<fm->ccount; i++) printf("%2d \"%s\" %s\n",
	fm->clist[i], fm->cnlist[i], fm->cblist[i]);
    printf("lines=%d, cols=%d, chloc=%d, agens=%d, \
alines=%d, acolmax=%d, chline=%d;\n",
	fm->lines, fm->cols, fm->chloc, fm->agens,
	fm->alines, fm->acolmax, fm->chline);
    printf("ffamily=%X, mfamily=%X\n\n", fm->ffamily, fm->mfamily);
    famdump(fm->ffamily);
    famdump(fm->mfamily);
}

/*..........*/

famgtmp(fm)		/* for debugging - output one family in tree form */
Familysqp fm;
{
Pagemp pp;
int lines, cols;
int i, j, x;
char *ss;

    lines = fm->lines; cols=fm->cols;
    pp = pageInit(lines, cols+5);
    for (i=0; i<lines; i++)		/* put in the vertical line */
	pagePutc(pp,i,2,'|');
    x = (lines-strlen(fm->pname))/2;	/* calculate starting position */
    for (i=x, ss=fm->pname; *ss; i++, ss++)
	pagePutc(pp,i,0,*ss);		/* put family name in vertically */
    x = (lines-(2*fm->ccount))/2;	/* starting line for children */
    for (i=0; i<fm->ccount; i++)
    {
	if (i==fm->chloc)		/* if this is the child of interest */
	{
	    for (j=4; j<pp->cols; j++)
	    {
		pagePutc(pp,2*i+x,j,'-');
		    /* put in a row of dashes first */
	    }
	}
	pagePuts(pp,2*i+x,4,fm->cnlist[i]);
	pagePuts(pp,2*i+x+1,4,fm->cblist[i]);
    }
    pagePrint(pp,stdout);		/* output it */
}

/* end */
//E*O*F famtree.c//

echo x - famtree.h
cat > "famtree.h" << '//E*O*F famtree.h//'
/* famtree.h - header file for family tree stuff */
/* last edit 15-Sep-84 21:05:02 by jimmc (Jim McBeath) */

#define XALLOC(item, count, msg) (item *)xalloc(sizeof(item)*count,msg)

struct familysq {	/* a family square */
    char *pname;	/* name of the family */
    int pnum;		/* index number of the parents (marriage) */
    int fnum;		/* index number of the father of the family */
    int mnum;		/* index number of the mother of the family */
    int chloc;		/* where the child of interest lies in child list */
    int ccount;		/* number of children in the family */
    int *clist;		/* list of child indexes for the family */
    char **cnlist;	/* list of names for children */
    char **cblist;	/* list of birthdates for children */
    int cnum;		/* number of the child with desc. tree */
    struct familysq *ffamily;	/* pointer to father's familysq */
    struct familysq *mfamily;	/* pointer to mother's familysq */
    int lines;		/* number of lines in this square */
    int cols;		/* number of cols in this square */
    int agens;		/* number of generations previous to this one */
    int alines;		/* number of lines for all ancestor families */
    int acolmax;	/* max columns for all ancestor families */
    int chline;		/* the line where the child tree comes out */
    };

typedef struct familysq Familysq, *Familysqp;

/* end */
//E*O*F famtree.h//

echo x - gbrowse.c
cat > "gbrowse.c" << '//E*O*F gbrowse.c//'
/* gbrowse.c - browse through the genealogy file */
/* Written by Jim McBeath (jimmc) at SCI */
/* last edit 19-Jan-85 09:08:30 by jimmc (Jim McBeath) */

#include "geneal.h"

extern char *gets();

int		/* 0 if OK */
gbrowse()	/* browse the database */
{
char *ss;
char lbuf[1000];
char tbuf[1000];
int n;
int t;

    indexes++;		/* note we want to see all the index numbers */
    while (1)		/* until a break takes us out */
    {
	printf(">");		/* give the browse prompt */
	ss = gets(lbuf);		/* read in a line */
	if (ss==0) break;		/* quit on eof */
	if (lbuf[0]==0) continue;	/* ignore blank lines */
	t = sscanf(lbuf,"%d", &n);	/* read his number */
	if (t!=1)
	{
	    printf("numbers only, please\n");
	    continue;		/* read another line */
	}
	if (n<=0) continue;
	t = famgetbstr(n,"T",tbuf);	/* see what the record type is */
	if (t==0)		/* if not there */
	{
	    printf("bad record %d\n", n);
	    continue;
	}
	if (strcmp(tbuf,"I")==0) indivs(n);
	else if (strcmp(tbuf,"F")==0) family(n);
	else printf("unknown type code %s in record %d\n", tbuf, n);
/* continue the loop */
    }
    return 0;
}

/* end */
//E*O*F gbrowse.c//

echo x - geneal.c
cat > "geneal.c" << '//E*O*F geneal.c//'
/* geneal - manipulate family trees, etc. */
/* Written by Jim McBeath (jimmc) at SCI */
/* last edit 19-Jan-85 08:17:08 by jimmc (Jim McBeath) */

#include <stdio.h>
#include <ctype.h>

#define HELP_EXIT 2

char *getData(), *rindex();

char *Progname;		/* name of the program */
extern int dataDebug;	/* flag for debugging data routines */
extern int indexDebug;	/* flag for debugging index routines */

int gendp;		/* data pointer for file */
char *gendatfile="genealogy.dat";	/* filename for data file */
int indexes=0;		/* set means output index numbers */

main(ac,av)
int ac;
char *av[];
{
int i,j;
int nval=1;
int nsac=0;		/* non-switch argument count */
int fampage=0;		/* set means user wants a family info page */
int sflag=0;		/* set means use short form */
int iflag=0;		/* set means produce individual data page */
int tflag=0;		/* set means produce a tree */
int t;		/* for status values */

    Progname = rindex(av[0],'/');		/* program name */
    if (Progname==0) Progname=av[0]; else Progname++;
    nval = 0;		/* stays 0 if the user never asks for a number */
    for (i=1; i<ac; i++)	/* scan switches and args */
    {
	if (av[i][0]=='-') for (j=1; j>0 && av[i][j]; j++) switch (av[i][j])
		/* check switches */
	{
	case 'f':		/* produce family info page */
	    fampage++;
	    break;
	case 'h':		/* give help */
	    genhelp();
	    exit(HELP_EXIT);	/* don't continue */
	case 'i':		/* individual information page */
	    iflag++;
	    break;
	case 's':		/* short form */
	    sflag++;
	    break;
	case 't':		/* produce a tree */
	    tflag++;
	    break;
	case 'D':		/* debug data routines */
	    dataDebug++;
	    break;
	case 'F':
	    if (av[i][j+1]) gendatfile=av[i]+(j+1);
	    else if (av[++i]) gendatfile=av[i];
	    else fatalerr("no argument for -F switch");
	    j = -2;		/* flag this string is used up */
	    break;
	case 'I':		/* debug index routines */
	    indexDebug++;
	    break;
	case 'N':		/* output index numbers */
	    indexes++;
	    break;
	default:
	    fatalerr("unknown switch %c in %s", av[i][j], av[i]);
	}
	else			/* non-switch args */
	{
	    switch (nsac++)	/* non-switch arg count */
	    {
	    case 0:		/* first arg is number to use */
		if (isdigit(av[i][0])) nval = atoi(av[i]);
		else fatalerr("bad format for arg0 (must be a number)");
		break;
	    default:		/* too many */
		fatalerr("unknown argument %s", av[i]);
	    }
	}
    }
    if (fampage + iflag + tflag > 1)
	fatalerr("only one of -f, -i or -t may be selected");
    gendp = initDataFile(gendatfile);	/* get data file */
    if (gendp==0) fatalerr("can't open data file %s", gendatfile);
/* check for combinations which are not yet implemented */
    if (fampage && sflag) warning("short form for family is not implemented");
    if (iflag && !sflag) warning("only short form available for individuals");
    if (fampage) t = family(nval);  /* produce a family page if requested */
    else if (iflag) t = indivs(nval);  /* produce info about individual */
    else if (tflag) t = famtree(nval);		/* produce a family tree */
/* if no specific action was requested, do the default */
    else t = gbrowse();		/* go browsing */
    if (t==0)		/* if no errors, exit w/o errors */
	exit(0);
    else
	exit(1);	/* errors */
}

/*........../

/* the help table */
char *helptab[] = {
"arg0 is the ID number of interest",
"Switches:",
"-f   produce a family information page",
"-h   give this help message",
"-i   produce an individual (person) information page",
"-s   use a short form for the specified information",
"-t   produce a tree (this function is quite incomplete!)",
"-D   debug data routines",
"-F file   use the specified file as datafile",
"-I   debug index routines",
"-N   output internal index numbers with all names",
"If no switches are given, the program enters browse mode.",
0
};

genhelp()		/* give help */
{
int i;

    for (i=0; helptab[i]; i++)
	printf("%s\n", helptab[i]);		/* print out the help table */
    return;
}

/* end */
//E*O*F geneal.c//

echo x - geneal.h
cat > "geneal.h" << '//E*O*F geneal.h//'
/* geneal.h - general include stuff for geneal modules */
/* Written by Jim McBeath (jimmc) at SCI */
/* last edit 19-Jan-85 05:07:41 by jimmc (Jim McBeath) */

/* global variables */
extern int gendp;		/* data file index number */
extern int indexes;		/* set means output index numbers w names */

/* declare functions */
char *index();

char *getData();
char *strsav();
char *tprintf();

char *famgetstr();
char *famgettname();
char *famgetbname();
char *famgetfname();
char *famgetbirth();
char *famgetdeath();
char *famgetbirthdeath();
char *famgetmarriage();

/* end */
//E*O*F geneal.h//

echo x - genealogy.dat
cat > "genealogy.dat" << '//E*O*F genealogy.dat//'
0	Description of data format
:Sample Data file for genealogy records.
:All the information has been changed to protect the innocent.
:last edit 20-Jan-85 16:41:45 by jimmc (Jim McBeath)
:
:File format:
:Each record specifies either an individual or a family.
:Each record has a unique index number.
:Each record is separated by a blank line.
:Each line within a record is an item of data.
:Each item of data consists of a key, a colon, and a payload string.
:Lines which start with a colon are comment lines.
:The first line of each record starts with an integer which is
:the index number for that record.  The rest of that line is ignored.
:
:The fields used in this file are described in this record.
T:Type of record: I for individual, F for family
:fields in an individual record:
LN:Last name (family name)
LNM:Maiden name
FN:First name
MN:Middle names
NN:Nickname
B:Birthdate in the format dd-mmm-yyyy
BP:Place of birth
D:Date of death in the same format
DP:Place of death
P:Index number of parent's marriage
S0:Index number of first marriage
S1:Index number of second marriage
:A family record contains information about a marriage and its offsprings.
:fields in a family record:
N:family name
H:Index number of husband
W:Index number of wife
M:Date of marriage
MD:Date of end of marriage
MP:Place of marriage
MDP:Place of end of marriage
C:Index number list of children from this marriage
:general fields:
GEN0:General information of interest about that person.
GEN1: etc.
COM0:Comments.
COM1: etc.
:
:first real record follows

1	John Doe (1952)
T:I
LN:Doe
FN:John
MN:Michael
NN:Norm
B:10-Feb-1952
BP:Anytown, USA
P:1003
S0:1001

2	Andrew Doe (1921)
T:I
LN:Doe
FN:Andrew
MN:Q.
B:3-Feb-1921
BP:New York, NY
S0:1003
P:1004

3	Elizabeth Smith Doe (1925)
T:I
LN:Doe
LNM:Smith
FN:Elizabeth
MN:Nancy
NN:Beth
B:4-Mar-1925
BP:Hauppauge, NY
S0:1003

4	Jane Jones Doe (1953)
T:I
LN:Doe
LNM:Jones
FN:Jane
MN:Jill
B:12-Dec-1953
BP:Los Angeles, CA
S0:1001

5	Dave Doe (1949)
T:I
LN:Doe
FN:David
NN:Dave
B:3-Jun-1949
BP:Midville, USA
P:1003
S0:1002

6	Kim Doe (1975)
T:I
LN:Doe
FN:Kimberly
MN:Janice
NN:Kim
B:5-Apr-1975
BP:Oakland, CA
P:1001

8	Nancy Walker Doe (1950)
T:I
LN:Doe
LNM:Walker
FN:Nancy
MN:M.
B:Jul-1950
S0:1002

9	Hans Doe (ca 1900?)
T:I
LN:Doe
FN:Hans
B:ca. 1900?
BP:Sweden
S0:1004

10	Lita ?? Doe (ca 1900?)
T:I
LN:Doe
FN:Lita
B:ca. 1900?
S0:1004

COM0:Try to find maiden name in Oslo marriage records

1001	John Doe and Jane Jones (1973)
T:F
N:Doe
H:1
W:4
M:15-Jun-1973
MP:San Franciso, CA
C:6

1002	Dave Doe and Nancy Walker (1975)
T:F
N:Doe
H:5
W:8
M:6-Sep-1975

1003	Andrew Doe and Elizabeth Simth (1948)
T:F
N:Doe
H:2
W:3
M:3-Aug-1948
MP:San Francisco, CA
C:5,1

1004	Hans Doe and Lita ?? (1919)
T:F
N:Doe
H:9
W:10
M:1915
MP:Oslo, Sweden?
C:2
GEN0:Stowed away on a ship in 1917 to come to America.

:end of file
//E*O*F genealogy.dat//

echo x - index.c
cat > "index.c" << '//E*O*F index.c//'
/* index - index handler for large pseudo-arrays */
/* Written by Jim McBeath (jimmc) at SCI */
/* last edit 10-Jan-85 07:59:39 by jimmc (Jim McBeath) */

/* This module implements a very large pseudo-array.  There are three
   entry points, to initialize a pseudo-array, to insert an item, and
   to read an item.  The array contains integers (which can of course
   be used for another purpose, such as pointers to something else).

   Structure of a table:
   A table is a recursive structure which contains 2 words of size
   information and n words of pointers.  The first word of size
   information tells how many entries are represented by this and
   all lower tables; the second word of size information tells how
   many entries are actually in this table.  If the two numbers are
   the same, then the table is an end node which actually has the
   data entries in it.
*/

/*..........*/

#include "index.h"

int indexDebug=0;		/* a debugging flag */

/*..........*/

struct toplevel *		/* pointer to the structure for future calls */
initIndex()			/* init a new index table */
{
struct toplevel *tt;
struct tblblock *dd;
int i;

tt = (struct toplevel *)malloc(sizeof(struct toplevel));
	/* get space for top level block */
if (!tt) return 0;
dd = (struct tblblock *)malloc(sizeof(struct tblblock));
	/* get space for top level table */
if (!dd) {
    free(tt);
    return 0;
    }
tt->data = dd;			/* save pointer in our block */
tt->numlevs = 1;		/* we always start with one level */
dd->repnum = TBLSIZ;
dd->count = TBLSIZ;
for (i=0; i<dd->count; i++) dd->tdata[i] = 0;	/* clear all data */
return tt;			/* return pointer to top level block */
}

/*..........*/

setIndex(tt,ix,num)		/* put index value into table */
struct toplevel *tt;		/* table to use */
int ix;				/* the index where it goes */
int num;			/* the value to put there */
{
struct tblblock *dd, *dd0;
int i;

if (indexDebug) {
    printf("setIndex: index=%d, value=%d\n", ix, num);
    if (!tt) printf("setIndex: no table index\n");
    else if (!(tt->numlevs)) printf("setIndex: no numlevs\n");
    else if (!(tt->data)) printf("setIndex: no data array\n");
    }
if (!tt) return -1;		/* check for errors */
if (!(tt->numlevs)) return -1;
if (!(tt->data)) return -1;
dd = tt->data;			/* get data pointer */
while (ix >= dd->repnum) {	/* create a higher level */
    if (indexDebug) printf("setIndex: %d(ix) > %d(repnum)\n", ix, dd->repnum);
    dd0 = (struct tblblock *)malloc(sizeof(struct tblblock));
	/* get space for a higher level */
    if (!dd0) return -1;		/* error */
    dd0->repnum = dd->repnum*TBLSIZ;
    dd0->count = TBLSIZ;
    for (i=0; i<TBLSIZ; i++) dd0->tdata[i] = 0;	/* clear table */
    dd0->tdata[0] = (int)dd;	/* put in pointer to next level down */
    tt->data = dd0;		/* put in new top-level pointer */
    tt->numlevs++;
    if (indexDebug) printf("setIndex: numlevs=%d\n", tt->numlevs);
    dd = dd0;
    }
while (dd->repnum > dd->count) {	/* scan down to the last level */
    if (indexDebug) printf("setIndex: %d(repnum) > %d(count)\n",
		dd->repnum, dd->count);
    dd0 = (struct tblblock *)(dd->tdata[ix/dd->count]);
	/* get pointer to next table lower */
    if (!dd0) {			/* if no table there, have to make one */
	dd0 = (struct tblblock *)malloc(sizeof(struct tblblock));
	if (!dd0) return -1;		/* error */
	dd0->repnum = dd->repnum/dd->count;
	dd0->count = TBLSIZ;
	for (i=0; i<TBLSIZ; i++) dd0->tdata[i] = 0; /* clear the new table */
	dd->tdata[ix/dd->count] = (int)dd0;	/* save pointer to it */
	}
    ix %= dd->count;		/* lower the index */
    dd = dd0;
    }
dd->tdata[ix] = num;		/* put it in */
if (indexDebug) printf("setIndex: table %X, index %d, value %d\n",
	dd, ix, num);
return ix;
}

/*..........*/

int				/* return value out of table */
				/*  returns 0 if no entry */
getIndex(tt,ix)
struct toplevel *tt;
int ix;				/* the index to look for */
{
struct tblblock *dd, *dd0;

if (indexDebug) {
    printf("getIndex: index=%d\n", ix);
    if (!tt) printf("getIndex: no table\n");
    else if (!tt->data) printf("getIndex: no data array\n");
    else if (!tt->numlevs) printf("genIndex: no numlevs\n");
    }
if (!tt) return 0;		/* check for errors */
if (!tt->data) return 0;
if (!tt->numlevs) return 0;
dd = tt->data;
if (ix >= dd->repnum) {
    if (indexDebug) printf("getIndex: index %d > repnum %d\n", ix,dd->repnum);
    return 0;	/* we don't have them that high */
    }
while (dd->repnum > dd->count) {	/* scan down to bottom level */
    if (indexDebug) printf("getIndex: %d(repnum) > %d(count)\n",
		dd->repnum, dd->count);
    dd0 = (struct tblblock *)(dd->tdata[ix/dd->count]);
	/* get pointer to next level */
    if (!dd0) {
	if (indexDebug) printf("getIndex: no table\n");
	return 0;		/* nothing there */
	}
    ix %= dd->count;
    dd = dd0;
    }
if (indexDebug) printf("getIndex: table %X, index %d, value %d\n",
	dd, ix, dd->tdata[ix]);
return dd->tdata[ix];		/* this is the data entry */
}

/* end of index */
//E*O*F index.c//

echo x - index.h
cat > "index.h" << '//E*O*F index.h//'
/* index.h - definitions for index */
/* Written by Jim McBeath (jimmc) at SCI */
/* last edit 19-Jan-85 08:53:10 by jimmc (Jim McBeath) */

#define TBLSIZ 100

struct tblblock {
    int repnum;			/* number of indexes represented by this blk */
    int count;			/* count of entries in this level */
    int tdata[TBLSIZ];		/* the actual data for this level */
    };

struct toplevel {
    int numlevs;		/* how deep the table is at it's deepest */
    struct tblblock *data;	/* pointer to the top level */
    };

/* end */
//E*O*F index.h//

echo x - indivs.c
cat > "indivs.c" << '//E*O*F indivs.c//'
/* indivs.v - print out short form info about an individual */
/* Written by Jim McBeath (jimmc) at SCI */
/* last edit 19-Jan-85 08:05:47 by jimmc (Jim McBeath) */

#include "geneal.h"

int			/* 0 if OK */
indivs(n)
int n;			/* the person's ID number */
{
char lbuf[1000];
char *ss;
int pnum;	/* parent family number */
int fnum;	/* father's number */
int mnum;	/* mother's number */
int mgnum;	/* marriage number */
int snum;	/* spouse's number */
int mhnum, mwnum;
char mgstr[20];	/* used for building key string */
int i;		/* loop counter */
int t;		/* random numbers and status */
char *spousestr;

    famgetbstr(n,"T",lbuf);
    if (lbuf[0]==0 || strcmp(lbuf,"I")!=0)
    {
	warning("id %d is not an individual");
	return 1;
    }
    ss = famgetfname(n);
    printf("Name: %s\n", ss);
    ss = famgetbirth(n);
    if (ss && *ss) printf("%s\n", ss);
    ss = famgetdeath(n);
    if (ss && *ss) printf("%s\n", ss);
    pnum = famgetnum(n,"P");	/* get number of parent family */
    if (pnum>0)			/* if we got a family */
    {
	if (indexes) printf("Parent family: %d\n", pnum);
	fnum = famgetnum(pnum,"H");
	if (fnum>=0)
	{
	    ss = famgetfname(fnum);
	    printf("  Father: %s\n", ss);
	    ss = famgetbirthdeath(fnum);
	    if (ss && *ss) printf("  %s\n", ss);
	}
	mnum = famgetnum(pnum,"W");
	if (mnum>=0)
	{
	    ss = famgetfname(mnum);
	    printf("  Mother: %s\n", ss);
	    ss = famgetbirthdeath(mnum);
	    if (ss && *ss) printf("  %s\n", ss);
	}
	ss = famgetmarriage(pnum);
	if (ss) printf("Parent's marriage: %s\n", ss);
	doiclist(pnum,"Sibling","Siblings",n);	/* do the siblings */
    }
    else	/* no parent in this record */
    {
	if (indexes) printf("No parent family specified\n");
    }
    for (i=0; ; i++)	/* look at marriages */
    {
	sprintf(mgstr,"S%d", i);	/* build indicator string */
	mgnum = famgetnum(n,mgstr);	/* get number of marriage */
	if (mgnum<=0) break;		/* stop if no more */
	if (indexes) printf("Marriage S%d: %d\n", i, mgnum);
	mhnum = famgetnum(mgnum,"H");	/* are we the husband? */
	mwnum = famgetnum(mgnum,"W");	/*  or the wife */
	if (n!=mhnum && n!= mwnum)
	    warning("person %d claims marriage %d, but not vice-versa",
		n, mgnum);
	if (n==mhnum)
	{
	    snum = mwnum;
	    spousestr = "Wife";
	}
	else
	{
	    snum = mhnum;
	    spousestr = "Husband";
	}
	if (snum>0)
	{
	    ss = famgetbname(snum);
	    printf(" %s: %s\n", spousestr, ss);
	    ss = famgetbirthdeath(snum);
	    if (ss && *ss) printf(" %s\n", ss);
	}
	ss = famgetmarriage(mgnum);
	if (ss && *ss) printf(" %s\n", ss);
	doiclist(mgnum,"Child","Children",-1);
    }
    for (i=0; ; i++)
    {
	sprintf(mgstr,"GEN%d", i);
	t = famgetbstr(n,mgstr,lbuf);
	if (t==0) break;		/* no more strings */
	if (i==0) printf("General:\n");
	printf("%s\n", lbuf);		/* print each line of GEN */
    }
    return 0;			/* finished it all OK */
}

/*..........*/

doiclist(pnum,c1,cm,xn)		/* print out info about children */
int pnum;			/* the family to deal with */
char *c1;			/* string to use if one child */
char *cm;			/* string to use if more than one child */
int xn;		/* number of special sibling, or -1 if none */
{
int i;
int t;
char *ss;
int *clist;
int cflag=0;

    t = famgetclist(pnum,&clist);	/* get list of kids */
    if (t==0)
    {
	if (indexes) printf("No %s specified\n", cm);
	if (xn>0) warning("person %d claims parents %d but not vice-versa",
		xn, pnum);
    }
    else
    {
	if (t==1) printf("  1 %s:\n",c1);
	else printf("  %d %s:\n",t,cm);
	for (i=0; i<t; i++)	/* for each child */
	{
	    if (clist[i]==xn) cflag++;	/* note found special sibling */
	    if (indexes) printf("  %s %d:", c1, i);
	    ss = famgettname(clist[i]);
	    printf("  %s%s\n",(clist[i]==xn?"*":""),ss);
	    ss = famgetbirthdeath(clist[i]);
	    if (ss&&*ss) printf("  %s\n", ss);
	}
	if (xn>0 && cflag==0)
	    warning("person %d claims parents %d but not vice-versa",
		xn, pnum);
    }
}

/* end */
//E*O*F indivs.c//

echo x - pagemap.c
cat > "pagemap.c" << '//E*O*F pagemap.c//'
/* pagemap - functions to manipulate a page of character data */
/* Written by Jim McBeath (jimmc) at SCI */
/* last edit 15-Sep-84 22:24:02 by jimmc (Jim McBeath) */

#include "pagemap.h"
#include <stdio.h>

Pagemp
pageInit(r,c)		/* make a new page of data */
int r,c;		/* rows an columns desired in the page */
{
Pagemp pp;
int i, j;
char *ss;

    pp = XALLOC(Pagem,1,"pageInit");	/* get the top level structure */
    pp->rows = r;
    pp->cols = c;		/* put in his numbers */
    pp->data = XALLOC(char *,r,"pageInit rowpointers");
    for (i=0; i<r; i++)
    {
	pp->data[i] = ss = XALLOC(char,c+2,"pageInit data");
	for (j=0; j<=c; j++) ss[j]=' ';		/* fill with spaces */
	ss[c+1] = 0;		/* null terminated */
    }
    return pp;			/* return the pointer to him */
}

/*..........*/

pagePuts(pp,r,c,ss)
Pagemp pp;
int r,c;
char *ss;
{
char *dd;
int n;

    dd = &pp->data[r][c];		/* where to put it */
    n = strlen(ss);			/* number of chars to move */
    if (pp->cols - c < n) n = pp->cols - c;	/* don't run off page */
    for ( ; n>0; n--) *(dd++) = *(ss++);	/* transfer string */
}

/*..........*/

pagePrint(pp,f)		/* output the page */
Pagemp pp;		/* pointer to the page to output */
FILE *f;		/* stream to output to */
{
int r,c;
int lastline;
char *ss;

    lastline = -1;
    for (r=0; r<pp->rows; r++)	/* for each row */
    {
	ss = pp->data[r];		/* faster access */
	for (c=pp->cols+1; c>=0; c--)
	    if (ss[c]>' ') break;	/* strip trailing spaces and nulls */
	ss[++c] = 0;			/* make it null terminated */
	if (c>0) lastline=r;		/* remember where the last line is */
    }
    for (r=0; r<=lastline; r++)		/* now output the lines */
	fprintf(f,"%s\n",pp->data[r]);
}

/* end */
//E*O*F pagemap.c//

echo x - pagemap.h
cat > "pagemap.h" << '//E*O*F pagemap.h//'
/* pagemap.h - definition for the pagemap functions */
/* last edit 15-Sep-84 21:52:47 by jimmc (Jim McBeath) */

#define XALLOC(item, count, msg) (item *)xalloc(sizeof(item)*count,msg)

struct pagem {
    int rows;		/* number of rows (lines) on the page */
    int cols;		/* number of columns on the page */
    char **data;	/* pointer to an array of char pointers */
    };

typedef struct pagem Pagem, *Pagemp;

/* data is stored in row major order; given the data pointer from the
 * above structure, data[x] points to row x, and data[x][y] points to
 * character y in row x.
 *
 * The number of columns is actually greater by two than the number
 * x->cols indicates; this is to leave space for a newline and a null
 * at the end.
 */

/* access macros */
#define pagePutc(pp,r,c,ch) ((pp)->data[r][c] = (ch))
#define pageGetc(pp,r,c)    ((pp)->data[r][c])

extern Pagemp pageInit();

/* end */
//E*O*F pagemap.h//

echo x - strsav.c
cat > "strsav.c" << '//E*O*F strsav.c//'
/* strsav - make a copy of a string and return a pointer to it */
/* Written by Jim McBeath (jimmc) at SCI */
/* last edit  6-Jan-85 22:21:20 by jimmc (Jim McBeath) */

char *
strsav(ss)
char *ss;
{
char *dd;

    dd = (char *)xalloc(strlen(ss)+1,"strsav");
    strcpy(dd,ss);		/* make a copy of the string */
    return dd;			/* return the copy */
}

/* end */
//E*O*F strsav.c//

echo x - tprintf.c
cat > "tprintf.c" << '//E*O*F tprintf.c//'
/* tprintf - do a printf into a temp buffer, then make a copy of the
 * string and return a pointer to the copy */
/* Written by Jim McBeath (jimmc) at SCI */
/* last edit 19-Jan-85 08:59:43 by jimmc (Jim McBeath) */

#define BUFSIZ 1000

extern char *strsav();

/* VARARGS1 */
char *
tprintf(fmt,args)
char *fmt;
{
char buf[BUFSIZ];

    vsprintf(buf,fmt,&args);	/* printf the string */
    return strsav(buf);		/* return a copy */
}

/* end */
//E*O*F tprintf.c//

echo x - vsprintf.c
cat > "vsprintf.c" << '//E*O*F vsprintf.c//'
/* @(#)sprintf.c	4.1 (Berkeley) 12/21/80 */
/* vsprintf from sprintf */
/* Created from unix sprintf by Jim McBeath (jimmc) at SCI */
/* last edit 19-Jan-85 09:00:15 by jimmc (Jim McBeath) */
#include	<stdio.h>

/* vsprintf is like sprintf, but instead of passing a list of arguments,
 * the address of the list is passed.  This is typically used to implement
 * a function which accepts a format string and list of arguments of
 * its own.
 */

/* VARARGS2 */
char *vsprintf(str, fmt, argv)
char *str, *fmt;
{
	struct _iobuf _strbuf;

	_strbuf._flag = _IOWRT+_IOSTRG;
	_strbuf._ptr = str;
	_strbuf._cnt = 32767;
	_doprnt(fmt, argv, &_strbuf);
	putc('\0', &_strbuf);
	return(str);
}
//E*O*F vsprintf.c//

echo x - xalloc.c
cat > "xalloc.c" << '//E*O*F xalloc.c//'
/* xalloc - allocate memory, give error message and die if no more */
/* Written by Jim McBeath (jimmc) at SCI */
/* last edit 15-Sep-84 16:50:25 by jimmc (Jim McBeath) */

#include <stdio.h>

#define ERROR_EXIT 1

static int totalused=0;

char *
xalloc( size, msg )
int size;		/* number of bytes to allocate */
char *msg;		/* error string */
{
char *x;

    x = (char *)malloc( size );
    if (x==0)
    {
	fprintf(stderr,"\nNo more memory (%s)\n", msg);
	fprintf(stderr,"Previously used: %d; this request: %d\n",
			totalused, size);
	exit(ERROR_EXIT);
    }
    totalused += size;
    return x;
}

/* end */
//E*O*F xalloc.c//

exit 0