/*** ged2ped.c ************************************************************
    Version 1.4.  June 28, 1999.
  Utility to create ancestor overview pedigrees of someone in a gedcom file.
    Copyright 1996 by Randy Wilson.  You are free to use this program for
    your personal use, but please contact me before using it for any commercial
    product or use.  Also, I would appreciate hearing about any modifications
    and improvements that are made to this code.
       e-mail:        randy@axon.cs.byu.edu
       WWW home page: http://axon.cs.byu.edu/~randy/randy.html
       My Genealogy:  http://axon.cs.byu.edu/~randy/genealogy.html
       (Take a peek there for examples of this program, with and without
        the hyperlinks built in).

  Usage:
    overview file.ged [-a rootPersonID] [-h] [-l INDIV] [-w width] [> outfile.txt]
      where 'rootPersonID' is the ID# of the first person in the pedigree;
          (default=1).
      -h => output in html format
      -l INDIV => scan for the 'D0000/I123.html'-type entries in the
        directory pointed to by 'INDIV', and thus create a hyperlink to
        the individual's record for each person in the pedigree chart.
        Assumes that you have created individual records via ged2html.
        Also assumes that the files are of the form 'I#.html', where '#'
        is the id number.  If your database is different, it shouldn't be
        too hard to rewrite that portion of the code.
      -w width => make the chart fit in 'width' columns, doing intelligent
        wrap-arounds if necessary.

  Versions:
    1.0, April 18, 1996 -- First major internet release.
    1.2, Nov. 4, 1996   -- added 'width' option.
    1.3, Jun. 28, 1999  -- Fixed a bug.
    1.4, Jun. 28, 1999  -- Got rid of "strcasecmp" since it's non-standard.
    This utility takes a GEDCOM file as input, scans it for relationship
    information, decides which individuals are ancestors of the root
    person (#1 by default, but can be anyone), then outputs a text file
    (to standard out, so redirect it to the file you want) containing
    a pedigree chart with one person per line, and that person's birth
    and death year, and birth and death place.
      One bummer: this code assumes that cross-reference pointers are
    of the form '@I123@' for individual #123, and uses such numbers for
    a variety of indexing.  If this is a big problem for people, I may
    be persuaded to use a more flexible scheme, but I'll wait until
    someone needs this to be done.
      This utility handles duplicate ancestors by repeating the second
    occurance of an ancestor and placing a note ("[See above]") following
    their name.  The remaining ancestors of such a duplicate are NOT
    repeated, which is why the person really should 'see above.'
      This utility will also output the text file in html format by using
    the -h flag.  Basically all this does is add header and footer records,
    use the preformatted mode for the chart, and use bold for the names.
      In addition, the '-l' flag will add hyperlinks from each name to
    the proper individual in a collection of html individual records
    generated by the 'ged2html' by Gene Stark. (See my genealogy page
    on the WWW above for information on where to get that program).
    Just use '-l INDIV' where 'INDIV' is the name of the directory
    (relative to where this overview chart will be) that contains the
    D0001, D0002, etc. directories.  This program will scan each of
    those directories in order to find out which directory each individual
    is in, so 'INDIV' also needs to be that directory relative to where
    you're running this program from.  Therefore, it's best to have the
    executable sitting in the same place where you're going to want your
    overview charts to be. [Unfortunately, I took a short cut and assumed
    that the individual cross-reference identifiers would be of the form
    'I#' where '#' is the number of the individual, since this is the case
    with my GEDCOM files which were generated with PAF.  It wouldn't be
    too much more difficult to have it just store the actual cross-ref.
    names and look them up, but I'll wait until someone needs that done
    (and then maybe tell them to do it! ;)].

      Please send me comments or questions by e-mail.

    --Randy Wilson, randy@axon.cs.byu.edu

***************************************************************************/

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#define  DOS 0

/***************** Structure definitions *****************************/
typedef struct {
    int valid;    /* flag to show that this person is real. */  
    int spouses;  /* number of spouses */
    int fams[40]; /* pointer to family of marriage 0..spouses-1 */
    int famc;     /* id of parents' marriage. */
    int ancestor; /* true if this person is an ancestor of 'mainID' */
    int level;    /* horizontal level (0=leftmost) */
    int pos;      /* vertical line number of ancestor (0=topmost) */
    char *info;
  } Person;

typedef struct {
    int valid;
    int children;
    int child[50];
    int husband;
    int wife;
  } Marriage;

typedef struct {
    char buf[225];
    int  level; /* level # of this gedcom line. */
    int  tag;   /* tag from enum below (FAM, INDI, etc.) */
    int  id;    /* id# in pointer, if any */
    int  pos;   /* position in 'buf' of the first character of extra data.*/
  } Line;

enum {FAM=1, INDI, NAME, BIRT, DATE, PLAC, DEAT, BURI,
       FAMS, FAMC, HUSB, WIFE, CHIL, TRLR, NA};

/******************* Function Prototypes ********************************/
void Barf(char *s);
void GetArguments();
FILE *OpenFile(char *f);
long Max(long a, long b);
void Strip(char *s);
int GetIndices();
int CountRecords();
int GetTag(char *s, int pos);
int ReadLine(FILE *fp, Line *line);
int ParseFAM(FILE *fp, Line *line);
int ParseINDI(FILE *fp, Line *line);
int GetRelations();
int MarkParents(int i, int level);
int Mark();
char *NewString(char *s);
int ExtractYear(char *s);
int GetEvent(FILE *fp, Line *line, int *year, char *place);
char ToUpper(char c);
int GetIndiText(FILE *fp, Line *line);
int GetAncestorText();
void OutputParents(int i,char *above, char *at, char *below);
int OutputChart();

/********************* Global variables ********************************/

Person *p;
Marriage *m; 
char filename[120];
int  mainID=1, parentID=1, doParent=0;
int numIndiv, numMarr;
int debug=0;
int markpos; /* Keeps track of order in which individuals are added to
                the chart, so that we can tell if they are in there more
                than once, in which case we don't traverse the ancestors again.*/
int html=0;
int links=0;
int width=0; /* 0=> no limit on width of a line. */
char indivDir[200];
char **fileLink;

/******************** Main *********************************************/
main(int argc, char *argv[])
{ int i;

  if (argc<2)
    { printf("Usage: %s input.ged [-a mainID] [-h] [-l INDIVdir] [-w width] [> output.txt]\n",argv[0]);
      printf("  Utility to create an ancestry chart for all ancestors of\n");
      printf("  'mainID' (default=1) in text format.\n");
      printf("  -h => output html format.\n");
      printf("  -l => use hyperlinks to INDIVdir/D*/*.html\n");
      printf("  -w => make chart fit in 'width' columns.\n");
#if DOS
    if (argc<2)
      GetArguments();
#else
    exit(0);
#endif   
  }
  else
  {
     strcpy(filename,argv[1]);
     for (i=2; i<argc; i++)
     { if (strcmp(argv[i],"-a")==0)
         mainID = atoi(argv[++i]);
       if (strcmp(argv[i],"-h")==0)
         html=1;
       if (strcmp(argv[i],"-l")==0)
         { links=1;
           html=1;
           strcpy(indivDir,argv[++i]);
           if (indivDir[strlen(indivDir)-1]=='/')
             indivDir[strlen(indivDir)-1]=0x00;
         }
       if (strcmp(argv[i],"-w")==0)
         width = atoi(argv[++i]);
     } /* for i */
  }
  
  fprintf(stderr,"GEDCOM file='%s'. MainID=%d\n",filename,mainID);
  fprintf(stderr,"Counting records...\n");
  CountRecords();
  fprintf(stderr,"  -> %d individuals and %d marriages.\n",numIndiv,numMarr);
  if (links)
    { fprintf(stderr,"Getting indices...\n");
      GetIndices();
    }
  fprintf(stderr,"Building relations...\n");
  GetRelations();
  fprintf(stderr,"Marking ancestors...\n");
  Mark();
  fprintf(stderr,"Collecting text for ancestors...\n");
  GetAncestorText();
  fprintf(stderr,"And the output:\n\n");
  OutputChart();
  fflush(stdout);
  fprintf(stderr,"Completed.\n");
  return(0);
}

#if DOS
/******************** GetArguments ***********************************/
void GetArguments()
{ /* Since DOS and some other environments may have trouble with
     command-line arguments, and since some environments may not be
     able to redirect output to a file, this routine prompts for each
     of the arguments. */
  /* Usage: ged2ped input.ged [-a mainID] [-h] [-l INDIVdir] [> output.txt]
        'mainID' (default=1) in text format.
        -h => output html format.
        -l => use hyperlinks to INDIVdir/Dxxxx/I*.html
  */
  char buf[1024], outname[150];
  
  printf("Ged2Ped is a utility to create a pedigree overview chart\n");
  printf("from a GEDCOM file.  It can also link the individuals in\n");
  printf("the chart to html documents created by Gene Stark's ged2html\n");
  printf("program.  For more information on this utility, see the\n");
  printf("WWW page http://axon.cs.byu.edu/~randy/gen/ged2ped/ged2ped.html\n");
  printf("\n");
  printf("Before the program runs, you will be asked to provide:\n");
  printf(" - The name of the GEDCOM file used as input;\n");
  printf(" - The ID# of the 'root' person of the pedigree (default=1)\n");
  printf(" - Whether you want html tags to be added to the text\n");
  printf(" - Whether you want to include hyperlinks to ged2html\n");
  printf("     documents, and if so, where to look for those.\n");
  printf(" - The name of the output file.\n");
  printf("\n");
  
  printf("What is the name of the GEDCOM file (include path if it\n");
  printf("is not in the current directory)? ");
  gets(filename);
  
  printf(" Each person in the database has an ID# (e.g., '@I1@' for person 1)\n");
  printf(" This program starts with a root person and displays that\n");
  printf(" person and all their ancestors.  The default option is to\n");
  printf(" use '1' as the root person.\n\n");
  printf("Enter the number of the root person (or return for '1'): ");
  gets(buf);
  mainID = atoi(buf);
  if (mainID<1)
    mainID=1;
  
  printf(" The output can be in plain text, or it can have html tags\n");
  printf(" added to it so that it will look right as a WWW page.\n\n");
  printf("Do you want to add html tags to your output (y/n)? ");
  gets(buf);
  if (buf[0]=='y' || buf[0]=='Y')
    html=1;
  else html=0;
  
  printf(" The output can be linked to individual pages generated by\n");
  printf(" Gene Stark's 'ged2html' program.  In order for this to work,\n");
  printf(" the ged2html pages must be created already and be in\n");
  printf(" directories with names like 'D0001/I123.html'.  If you\n");
  printf(" include these links, you must give the path relative to\n");
  printf(" where your output file will be, and the current directory\n");
  printf(" must be that same path.  For example, if my output file\n");
  printf(" were 'ped.html' in the directory 'gen', and my ged2html\n");
  printf(" documents were in files like 'gen/wilson/D0001/I128.html'\n");
  printf(" then I would first change to the directory 'gen', then run\n");
  printf(" this program and specify the link directory as 'wilson',\n");
  printf(" since that's the directory the 'D' directories are in,\n");
  printf(" relative to where the output will be (and where we are now).\n");
  printf("So, given all of that, do you want to use these links (y/n)? ");
  gets(buf);
  if (buf[0]=='y' || buf[0]=='Y')
    links=html=1;
  else links=0;
  if (links)
  { printf("Where are the 'D' directories stored? ");
    gets(indivDir);
    while (indivDir[strlen(indivDir)-1]<=' ')
      indivDir[strlen(indivDir)-1]=0x00;
    if (indivDir[strlen(indivDir)-1]=='/')
      indivDir[strlen(indivDir)-1]=0x00;
  }
  strcpy(outname,filename);
  if (strlen(outname)>4 && (strcmp(&outname[strlen(outname)-4],".ged")==0 ||
                            strcmp(&outname[strlen(outname)-4],".GED")==0))
    outname[strlen(outname)-4]=0x00;
  
  printf("By default, each individual will be on their own line, but\n");
  printf("these lines could become very long.  Enter a number (such as 80)\n");
  printf("to limit the length of each line (by wrapping around to the next\n");
  printf("line in an intelligent way), or press return to use long lines:");
  gets(buf);
  width = atoi(buf); /* which should be 0 if they hit return. */

  if (html)
    strcat(outname,".html");
  else strcat(outname,".txt");
  printf("\nWhat will be the name of your output file\n");
  printf("  (press return for %s)? ",outname);
  gets(buf);
  if (strlen(buf)==0)
    strcpy(buf, outname);
  /* Rewrite standard out to the output file.*/
  freopen(buf,"wt",stdout);

} /* GetArguments */
#endif

/******************* Barf ********************************/
void Barf(char *s)
{ /* Give an error message and quit. */
  printf("%s\n",s);
  exit(0);
}

/******************* OpenFile ****************************/
FILE *OpenFile(char *f)
{ /* Open a text file with name 'f' for reading, and return
     the file pointer to it.  If there is an error, print an
     error message and terminate the program. */ 
  FILE *fp;
  fp = fopen(f,"rt");
  if (fp==NULL)
    { printf("Couldn't open %s for reading.\n",f);
      exit(0);
    }
  return(fp);
} /* OpenFile */

/******** Max ***********/
long Max(long a, long b)
{ /* Return the maximum of a and b */
  if (a>b)
    return(a);
  else return(b);
}

/******** Strip *****************/
void Strip(char *s)
{ /* Strip trailing white space from the string 's'. */
  int i;
  for (i=strlen(s)-1; i>=0 &&s[i]<=' '; i--)
    s[i]=0x00;
}

/*********** GetIndices ********************************/
int GetIndices()
{ /* Assumes that the individual records (as produced by ged2html)
     are in the directories indivDir/D * / I*.html; Creates a
     directory listing, parses it to find the filename of each
     individual number, and puts these in the array fileLink[i]. */
  char line[300], curDir[300], curFile[300];
  FILE *fp;
  int id;
  
  fileLink = (char **) malloc(sizeof(char *) * (numIndiv+1));
  /** Create a file called 'tempIndivDir' that contains the names
      of all the individual records */
  sprintf(line,"ls %s/D* > tempIndivDir",indivDir);
  fprintf(stderr,"%s\n",line);
  system(line);

  fp = OpenFile("tempIndivDir");
  while (!feof(fp))
    { fgets(line,250,fp);  /* Get a file or directory name */
      Strip(line);
      while (!feof(fp) && strlen(line)==0)
        { /* make sure 'line' isn't blank */
          fgets(line,250,fp);
          Strip(line);
        }
      if (strlen(line)>0)
      { if (line[strlen(line)-1]==':')
         { /* This is a new directory, so switch curDir */
           strcpy(curDir,line);
           curDir[strlen(curDir)-1]='/';
         }
        else if (line[0]=='I')
         { /* This is an individual's file, so allocate a string for
              it and put it into 'fileLink[id]', where 'id' is the
              number following the 'I'. */
           id = atoi(&line[1]);
           sprintf(curFile,"%s%s",curDir,line);
           if (id>numIndiv)
             Barf("Too big a number!!");
           fileLink[id] = (char *) malloc(strlen(curFile)+1);
           if (fileLink[id]==NULL)
             Barf("Couldn't allocate string.");
           strcpy(fileLink[id],curFile);
         }
      } /* if valid line */
    }
  fclose(fp);
  system("rm tempIndivDir");
  return(0);
} /* GetIndices */

/*********** CountRecords ******************************/
int CountRecords()
{ /* Quickly scan through a GEDCOM file to find out how many individuals
     and marriages there are. */
  FILE *fp;
  char line[120];
  
  numIndiv = numMarr = 0;
  
  fp = OpenFile(filename);
  fgets(line,120,fp);
  while (!feof(fp) && strcmp(line,"0 TRLR")!=0)
    { while (!feof(fp) && line[0]!='0') /* skip until we have a 0-level line*/
        fgets(line,120,fp);
      if (line[3]=='I') /* Individual record */
        numIndiv = Max(numIndiv,atol(&line[4]));
      else if (line[3]=='F')
        numMarr  = Max(numMarr, atol(&line[4]));
      fgets(line,120,fp);
    }
  fclose(fp);
  return(0);
} /* CountRecords */

/****************** GetTag ********************************/
int GetTag(char *s, int pos)
{ /* Return the enumerated type tag value of the tag at 'pos' in 's'. 
     Note that there are many more tags than this in the GEDCOM standard,
     but these are the only ones that we care about for this application.
     All other unrecognized tags are skipped appropriately. */
  int tag;
  if (debug) printf("GetTag:\n");
  if      (strncmp(&s[pos],"INDI",4)==0) tag=INDI;
  else if (strncmp(&s[pos],"BIRT",4)==0) tag=BIRT;
  else if (strncmp(&s[pos],"DATE",4)==0) tag=DATE;
  else if (strncmp(&s[pos],"PLAC",4)==0) tag=PLAC;
  else if (strncmp(&s[pos],"DEAT",4)==0) tag=DEAT;
  else if (strncmp(&s[pos],"BURI",4)==0) tag=BURI;
  else if (strncmp(&s[pos],"NAME",4)==0) tag=NAME;
  else if (strncmp(&s[pos],"FAMS",4)==0) tag=FAMS;
  else if (strncmp(&s[pos],"FAMC",4)==0) tag=FAMC;
  else if (strncmp(&s[pos],"HUSB",4)==0) tag=HUSB;
  else if (strncmp(&s[pos],"WIFE",4)==0) tag=WIFE;
  else if (strncmp(&s[pos],"CHIL",4)==0) tag=CHIL;
  else if (strncmp(&s[pos],"TRLR",4)==0) tag=TRLR;
  else if (strncmp(&s[pos],"FAM", 3)==0) tag=FAM;
  else tag=NA;
  return(tag);
} /* GetTag */

/***************** ReadLine *********************************/
int ReadLine(FILE *fp, Line *line)
{ /* Read a line from the file 'fp', and fill the various members of the
     structure 'line'. */
  int pos;
  fgets(line->buf,120,fp);

  for (pos=strlen(line->buf)-1; line->buf[pos]<=' ' && pos>=0; pos--)
    line->buf[pos]=0x00;
  line->id    = 0; /* default, implies no id number */
  line->level = atoi(line->buf);
  line->pos   = 7; /* i.e., skip tag to find start of name or place.
                      This would be the wrong index number for a 'FAM'
                      record, since it has only three characters in the
                      tag name, but then again, there is no text we care
                      about in a 'FAM' record, so we're ok. */
  if (line->buf[2]=='@')
    { /* then there is a cross-reference (i.e., id number) here, so
         yank out the id number, and grab the tag after that. */
      line->id = atoi(&line->buf[4]);
      for (pos=4; line->buf[pos]!='@'; pos++)
        ;
      pos+=2;
      line->pos = pos+5;
      line->tag=GetTag(line->buf,pos);
    }
  else /* no x-ref before tag, so get the tag immediately following the level#*/
    { line->tag=GetTag(line->buf,2);
      for (pos=2; line->buf[pos]>' '; pos++)
        ;
      if (line->buf[pos]==' ')
        pos++;
      line->pos = pos;
      if (line->buf[pos]=='@')
        { /* Then there is a x-ref after the tag. */
          line->id = atoi(&line->buf[pos+2]);
          pos++;
          while (line->buf[pos]!=0x00 && line->buf[pos]!='@')
            pos++;
          if (line->buf[pos]=='@')
            pos++;
          line->pos = pos;
        } /* if x-ref */
    } /* else */
  return(0);
} /* ReadLine */

/********************* ParseFAM **********************************/
int ParseFAM(FILE *fp, Line *line)
{ /* Parses a "FAM" record, beginning with 'line', which contains information
     about the line containing the 'FAM' tag. Returns with the next level-0
     line in 'line'.*/
  int id = line->id;
  if (id==0)
    Barf("Yikes! Marriage ID = 0\n");
  m[id].valid=1;
  m[id].children=0;
  m[id].husband=0;
  m[id].wife=0;
  ReadLine(fp,line);
  while (!feof(fp) && line->level>0)
    { switch (line->tag) {
        case HUSB: m[id].husband                 = line->id; break;
        case WIFE: m[id].wife                    = line->id; break;
        case CHIL: m[id].child[m[id].children++] = line->id; break;
       }
      ReadLine(fp,line);
    }
  return(0);
} /* ParseFAM */

/****************** ParseINDI ***************************************/
int ParseINDI(FILE *fp, Line *line)
{ /* Parse an 'INDI' record.  Assumes that 'line' contains the line with
     the 'INDI' tag.  Reads and deals with all subsequent lines up to the
     next level-0 line, which it returns in 'line'. */
  int id = line->id;
  if (id==0)
    Barf("Yikes! Individual ID is 0!\n");
  if (id>numIndiv)
    Barf("Yikes! Individual ID is too big!");
  p[id].valid = 1;
  p[id].spouses=0;
  p[id].famc   =0;
  p[id].ancestor=0;
  p[id].pos     =-1;
  p[id].level   =-1;
  
  ReadLine(fp,line);
  while (!feof(fp) && line->level>0)
    { switch (line->tag) {
        case FAMS: p[id].fams[p[id].spouses++] = line->id; break;
        case FAMC: p[id].famc = line->id; break;
      } /* switch */
      ReadLine(fp,line);
    }
  return(0);
} /* ParseINDI */

/*************** GetRelations **************************/
int GetRelations()
{ /* Scan through gedcom file 'filename', and build structure of individuals
     and marriages. */
  FILE *fp;
  Line lineRec, *line;
  int  id;
  line = &lineRec;
  
  p = (Person *) malloc(sizeof(Person)*(numIndiv+1));
  m = (Marriage *) malloc(sizeof(Marriage)*(numMarr+1));
  if (p==NULL || m==NULL)
    Barf("Out of memory in GetRelations while allocating big arrays.");

  for (id=0; id<numIndiv; id++)
    p[id].valid=0;
  for (id=0; id<numMarr; id++)
    m[id].valid=0;
  
  fp = OpenFile(filename);
  ReadLine(fp,line);
  while (!feof(fp) && line->tag!=TRLR)
    { while (line->level !=0 && !feof(fp))
        ReadLine(fp,line);
      switch(line->tag) {
        case FAM: ParseFAM(fp,line); break;
        case INDI: ParseINDI(fp,line); break;
        case TRLR: break;
        default:   ReadLine(fp,line); break;
      } /* switch */
    }
  fclose(fp);
  return(0);
} /* GetRelations */

/*********************** MarkParents ******************************/
int MarkParents(int i, int level)
{ if (i<=0)
    return(0); /* terminate search on this branch */
  if (p[i].ancestor)
    return(0); /* avoid cycles */
  p[i].ancestor = 1;
  p[i].level    = level;
  if (p[i].famc > 0) /* has parents */
    { MarkParents(m[p[i].famc].husband, level+1);
      p[i].pos = markpos++;
      MarkParents(m[p[i].famc].wife, level+1);
    }
  else p[i].pos = markpos++;
  return(0);
} /* MarkParents */

/****************** Mark ********************************************/
int Mark()
{ /* find all ancestors of person mainID and set their 'ancestor' flag to true.*/
  markpos=0;
  MarkParents(mainID,0);
  return(0);
}

/****************** NewString *******************/
char *NewString(char *s)
{ /* Allocates a string just big enough to hold 's' (and a terminating 0x00),
     copies 's' into it, and returns a pointer to the new string.  This
     string may be deallocated by calling free(newString), where 'newString'
     is the pointer returned by this function. */
  int len=strlen(s);
  char *temp;
  if (len==0)
    return(NULL);
  temp = (char *) malloc(len+1);
  strcpy(temp,s);
  temp[strlen(s)]='\0';
  return(temp);
} /* NewString */

/****************** ExtractYear ***********************/
int ExtractYear(char *s)
{ /* Returns year in a date string pointed to by 's'. Assumes that the
     string ends with either a year (as in "2 DATE 23 APR 1873"), or a
     year followed by a ']' (as in "2 DATE [ABT 1873]").  Ignores any
     trailing white space, and any characters before the year. */
  int pos,year;
  if (s==NULL)
    return(0);
  pos = strlen(s)-1;
  while ((s[pos]<=' ' || s[pos]==']') && pos>=0)
    pos--;
  if (pos<0)
    return(0);
  while(s[pos]>='0' && s[pos]<='9')
    pos--;
  year=atoi(&s[pos+1]);
  if (year<0 || year>2000)
    return(0);
  else return(year);
} /* GetYear */


/*******************************************************/
int GetEvent(FILE *fp, Line *line, int *year, char *place)
{ /* Assumes that 'line' contains the level-1 event (e.g., "1 BIRT").
     Reads lines until a level 1 or level 0 line is found, and line
     will have this in it upon return. Copies the year into 'year'
     and the place into 'place' (with spaces added after commas),
     if they are included in the GEDCOM file. */
  char temp[200];
  int i,j;
  
  place[0]=0x00; /* zero these two values in case they aren't found. */
  *year = 0;
  ReadLine(fp,line);
  while (!feof(fp) && line->level > 1)
    { switch(line->tag) {
        case DATE:
           *year = ExtractYear(line->buf);
          break;
        case PLAC:
           strcpy(temp,&line->buf[line->pos]);
           for (j=i=0; i<(int)strlen(temp); i++)
             { place[j++]=temp[i];
               if (temp[i]==',' && temp[i+1]>' ')
                 place[j++]=' ';
             }
           place[j]=0x00;
          break;
        } /* switch */
      ReadLine(fp,line);
    }
  return(0);
} /* GetEvent */

/**************** ToUpper *********************************************/
char ToUpper(char c)
{ if (c>='a' && c<='z')
    return((c+'A')-'a');
  else return(c);
}

/****************** GetIndiText ***************************************/
int GetIndiText(FILE *fp, Line *line)
{ int id = line->id, i, birthyear=0, deathyear=0;
  char given[80],surname[80], buf[300], birthplace[120],deathplace[120];
  char burplace[120];
  int  buryear = 0;
  given[0]=surname[0]=buf[0]=birthplace[0]=deathplace[0]=burplace[0]=0x00;

  if (id==0)
    Barf("Yikes! Individual ID is 0!\n");
  ReadLine(fp,line);
  while (!feof(fp) && line->level>0)
    { switch (line->tag) {
        case NAME:
            for (i=line->pos; i<(int)strlen(line->buf) && line->buf[i]!='/'; i++)
              if (line->buf[i]=='\\')
                line->buf[i]='/';
            strncpy(given,&line->buf[line->pos],i - line->pos);
            given[i - line->pos]='\0';
	    while (given[strlen(given)-1]==' ')
	      given[strlen(given)-1]=0x00;
	    if (line->buf[i]=='/')
          { /* Pull out surname */
             strcpy(surname,&line->buf[i+1]);
             surname[strlen(surname)-1]='\0'; /* get rid of final '/' */       
             for (i=0; i<(int)strlen(surname); i++)
                surname[i]=ToUpper(surname[i]);
          }
          ReadLine(fp,line);
          break;
        case BIRT: 
            GetEvent(fp,line,&birthyear,birthplace);
          break;
        case DEAT:
            GetEvent(fp,line,&deathyear,deathplace);
          break;
        case BURI:
            GetEvent(fp,line,&buryear,burplace);
          break;
        default: ReadLine(fp,line);   
      } /* switch */
    }
  if (html && links)
    { strcat(buf,"<a href=\"");
      strcat(buf,fileLink[id]);
      strcat(buf,"\">");
    }
  if (html)
    strcat(buf,"<b>");
  if (strlen(given)>0)
    { strcat(buf,given);
      if (strlen(surname)>0)
        strcat(buf," ");
    }
  if (strlen(surname)>0)
    strcat(buf,surname);
  if (html)
    strcat(buf,"</b>");
  if (html && links)
    strcat(buf,"</a>");
  if (birthyear>0)
    sprintf(&buf[strlen(buf)],", (%d - ",birthyear);
  else sprintf(&buf[strlen(buf)],", ( - ");
  if (deathyear>0)
    sprintf(&buf[strlen(buf)],"%d)",deathyear);
  else if (buryear>0)
    sprintf(&buf[strlen(buf)],"%d)",buryear);
  else sprintf(&buf[strlen(buf)]," )");
  if (strlen(birthplace)>0)
    sprintf(&buf[strlen(buf)],"; b. %s",birthplace);
  if (strlen(deathplace)>0)
    sprintf(&buf[strlen(buf)],"; d. %s",deathplace);
  else if (strlen(burplace)>0)
    sprintf(&buf[strlen(buf)],"; bur. %s",burplace);
  p[id].info = NewString(buf);
  return(0);
}

/*************** GetAncestorText **************************/
int GetAncestorText()
{ /* Scan through gedcom file 'filename' one more time, and for anyone
     who is a direct ancestor, grab their name and other information,
     and allocate & contruct a text line, and put it in p[i].info. */
  FILE *fp;
  Line lineRec, *line;
  line = &lineRec;

  if (debug)
    printf("Get AncestorText...\n");
  fp = OpenFile(filename);
  ReadLine(fp,line);
  while (!feof(fp) && line->tag!=TRLR)
    { if (debug) printf("Trying for a level-0 line...\n");
      while (line->level !=0 && !feof(fp))
        ReadLine(fp,line);
      if (debug) printf("Got a level-0 line...\n");
      if (line->tag==INDI && p[line->id].ancestor)
        GetIndiText(fp,line);
      else ReadLine(fp,line);
    }
  fclose(fp);
  return(0);
} /* GetAncestorText */

/*************************** TheLength *******************************/
int TheLength(char *s)
{ /* Returns the length of the string s, NOT including html tags,
     i.e., anything enclosed in <> pairs. */
  int length, pos;
  for (length=pos=0; s[pos]; pos++)
  { if (s[pos]=='<')
      while (s[pos] && s[pos]!='>')
        pos++;
    else length++;
  } /* for */
  return length;
} /* TheLength */

/********************* PrintSection ******************************/
void PrintSection(char *s, int start, int end)
{ /* Print from position 'start' to 'end', NOT counting html tags,
     but do go ahead and print the tags when they occur.  For example,
     the string "<b>hi</b> there" with start=0, end=4, would print
     "<b>hi</b> t", since 'h' is in position 0, and t is in position
     4 (not counting html tags).  This routine will also print out any
     tags attached to the end of the non-html stuff.  e.g., with
     start=2, end=3, it would print " ", not "</b> ", but with start=1,
     end=2, it would print "i</b>".  The exception is that it will
     print tags occuring at the beginning of the string when start=0.*/
  int pos, /* position in html string */
      len; /* position in non-html string */
  for (pos=len=0; s[pos] && (len<start || s[pos]=='<'); pos++)
    { if (s[pos]=='<')
        while (s[pos] && s[pos]!='>')
          pos++;
      else len++;
    }
  if (start==0)
    pos=0; /* Make sure we don't skip leading html tags.*/
  for (len=start; s[pos] && (len<end || s[pos]=='<'); pos++)
    { if (s[pos]=='<')
        while(s[pos] && s[pos]!='>')
          printf("%c",s[pos++]);
      else len++;
      printf("%c",s[pos]);
    }
  printf("\n");
} /* PrintSection */

/*************************** BreakPoint ******************************/
int BreakPoint(char *s, int max)
{ /* Looks through the text string 's' and finds the best place to break
     it so that it is no longer than 'max' characters.  Assumes that the
     string will be something like:
       Levi B. GARRISON, (1798 - 1869); b. Franklin Co.(?), GA; d. Maysville, Jackson, GA
     The heuristic for the break proceeds as follows:
       1. Use the rightmost semicolon such that everything up to that fits in max.
       2. Use the rightmost comma such that everything up to that fits in max.
       3. Use the rightmost space such that everything up to that fits in max.
       4. Use the entire string.
     Returns the first character of the next line, which is also the
     number of characters at the beginning of 's' to use on the first line.
   */
   int best, len, pos;
   len = strlen(s);
   best= len;
   if (best<=0)
     return(strlen(s));
   for (pos=max-1; pos>0; pos--)
     { if (s[pos]==';')
         return pos+1; /* Found a good semicolon, so stop. */
     }
   for (pos=max-1; pos>0; pos--)
     { if (s[pos]==',')
	 return pos+1; /* Found a comma, so stop. */
     }
   for (pos=max-1; pos>0; pos--)
     { if (s[pos]==' ' && best>max)
	 best = pos+1; /* Found a space, so stop. */
     }
} /* BreakPoint */
/*************************** OutputIndividual ************************/
void OutputIndividual(char *s, char *at, char *below)
{ /* Outputs the information contained in 's', preceded by the string 'at',
     and, if longer than width (and width not 0), wraps the line smartly,
     using 'below' to prepend such wrapped lines. If there are 10 or
     less characters available for 's', it is just printed, since wrapping
     could get really ugly when you run out of room.  */
  char *temp; /* Copy of 's' with html tags stripped out. */
  int pos, len, start,end;
  if (width && (TheLength(at)+TheLength(s)) > width
            && (TheLength(at)+10) < width)
    { /* The info won't fit on this line, so break it up. */
      
      /* First make a copy 'temp' of 's' with html stuff removed. */
      temp = (char *) malloc(TheLength(s)+1);
      for (pos=len=0; s[pos]; pos++)
      { if (s[pos]=='<')
          while(s[pos] && s[pos]!='>')
            pos++;
        else temp[len++]=s[pos];
      }
      temp[len]=0x00; /* null terminate string */
      while (len>0 && temp[len-1] && temp[len-1]<=' ')
	temp[--len]=0x00; /* Strip white space */
     /* Find the best place to split the line. */
      end = BreakPoint(temp,width-TheLength(at));
      start=end;
      while (end>0 && temp[end-1] && temp[end-1]<=' ')
	end--; /* Strip trailing white space. */
      while (temp[start] && temp[start]<=' ')
	start++; /* Skip leading white space on next line. */
      
      /* Print leading stuff */
      printf("%s",at);
      
      /* Print first 'end' characters of 'temp', along with
         any html stuff that was in the original 's' in that range. */
      PrintSection(s,0,end);
      
      while ((int)strlen(temp)-start > width-TheLength(at)-3)
      { /* Break the string again */
        end = BreakPoint(&temp[start],width-TheLength(at)-3)+start;
        printf("%s",below);
        while (end>0 && temp[end-1] && temp[end-1]<=' ')
	  end--; /* Strip trailing white space. */
        PrintSection(s,start,end);
        start=end;
	while (temp[start] && temp[start]<=' ')
	  start++; /* Skip leading white space on next line. */
      }
      end=strlen(temp);
      if (start<end)
        { printf("%s",below);
	  PrintSection(s,start,end);
	}
    } /* if need to break line */
  else printf("%s%s\n",at,s);
            
} /* OutputIndividual */

/*************************** OutputParents ***************************/
void OutputParents(int i,char *above, char *at, char *below)
{ /* Outputs person 'i', along with all of that person's ancestors.
     If 'i' has already been output, then prints the person's info
     and a message to '[see above]', without outputting all of that
     persons ancestors again. 'above', 'at', and 'below' are the
     strings which should be prepended to ancestors who are above,
     at, or below person i, respectively (i.e., they keep track of
     vertical lines and spaces). */
  char *newAbove,*newAt,*newBelow;
  if (i<=0)
    return;
  if (p[i].famc > 0) /* i.e., if this person has parents...*/
    { if (p[i].pos >= markpos)
        { /* then this person has not yet been output, so we
             should output their parents as well. */
          newAbove = (char *) malloc(strlen(above)+4);
          newAt    = (char *) malloc(strlen(above)+4);
          newBelow = (char *) malloc(strlen(above)+4);
          if (newAbove==NULL || newAt==NULL || newBelow==NULL)
            { printf("Out of memory in OutputParents.\n");
              exit(0);
            }
          /* Print father & his ancestors */
          sprintf(newAbove,"%s   ",above);
          sprintf(newAt,   "%s+- ",above);
          sprintf(newBelow,"%s|  ",above);
          OutputParents(m[p[i].famc].husband,newAbove,newAt,newBelow);
          sprintf(newAbove,"%s|  ",below);
          sprintf(newAt,   "%s+- ",below);
          sprintf(newBelow,"%s   ",below);
          
          /* Print individual's information, possibly wrapped to fit width.*/
          if (m[p[i].famc].wife > 0)
	    OutputIndividual(p[i].info,at,newAbove);
          else OutputIndividual(p[i].info,at,newBelow);
          /* Print mother & her ancestors. */
          markpos++;
          OutputParents(m[p[i].famc].wife,newAbove,newAt,newBelow);
          free(newAbove);
          free(newAt);
          free(newBelow);
        } /* if hasn't output yet */
      else printf("%s%s -- [see above]\n",at,p[i].info);
    } /* if has parents */
  else
    { newBelow= (char *) malloc(strlen(below)+4);
      if (!newBelow)
	{ printf("Out of memory in OutputParents\n");
	  exit(0);
	}
      sprintf(newBelow,"%s   ",below);
      OutputIndividual(p[i].info,at,newBelow);
      /* printf("%s%s\n",at,p[i].info); */
      free(newBelow);
      markpos++;
    }
} /* OutputParents */

/******************* OutputChart **********************************/
int OutputChart()
{ /* Main routine to output the chart, once the family info has already
     been read in. */
  markpos = 0;
  if (html)
    { printf("<html>\n<head>\n<title>%s overview\n",filename);
      printf("</title>\n</head>\n<body>\n");
      printf("<h3>Ancestry chart from %s</h3>\n",filename);
      
      /* Put any other html lines in here if you want them. */
      
      printf("<p>\n<hr>\n<pre>\n");
    }
  OutputParents(mainID,"","","");
  if (html)
    { printf("</pre><hr>\n");
      printf("<i>This chart generated by\n  ");
      printf("<a href=\"http://axon.cs.byu.edu/~randy/gen/ged2ped/ged2ped.html\">");
      printf("ged2ped</a> by\n  ");
      printf("<a href=\"http://axon.cs.byu.edu/~randy/randy.html\">Randy Wilson.</i>\n");
      printf("</body>\n</html>");
    }
  return(0);
} /* OutputChart */
