home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
DP Tool Club 24
/
CD_ASCQ_24_0995.iso
/
vrac
/
ged2ht23.zip
/
OUTPUT.C
< prev
next >
Wrap
Text File
|
1995-07-14
|
43KB
|
1,648 lines
/*
* Copyright (c) 1995 Eugene W. Stark
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Eugene W. Stark.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
* 5. No copying or redistribution in any form for commercial purposes is
* permitted without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY EUGENE W. STARK (THE AUTHOR) ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* HTML Output Routines
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#ifdef MSDOS
#include <direct.h>
#else
#include <sys/stat.h>
#endif
#ifndef HAVE_STRDUP
char *strdup(const char *s);
#endif
#include "tags.h"
#include "node.h"
#include "database.h"
#include "output.h"
#ifndef FILENAME_MAX
#define FILENAME_MAX 1024
#endif
#ifdef MSDOS
char *file_template="%s.htm";
int files_per_directory = 100;
int indivs_per_directory = 1000;
int indivs_per_file = 10;
#else
char *file_template="%s.html";
int files_per_directory = 100;
int indivs_per_directory = 100;
int indivs_per_file = 0;
#endif
int index_levels = 0;
int pedigree_depth = 2;
/*
* The templates are split into several strings, because the
* "stone knives and bearskins" Microsoft compiler has a limit on
* the length of a string literal.
*
* The splitting is now used as a convenient way to substitute certain
* option-dependent portions of the template, such as "../INDEX.html" versus
* "INDEX.html"
*/
char *individual_template_base[] =
{
/* 0 */
"<HTML>\n" \
"<HEAD>\n" \
"!IF @.TITLE\n" \
"<TITLE>${@.XREF}: ${@.NAME} (${@.TITLE})</TITLE>\n" \
"!ELSE\n" \
"<TITLE>${@.XREF}: ${@.NAME}</TITLE>\n" \
"!ENDIF\n" \
"</HEAD>\n" \
"<BODY>\n"
"!IF @.TITLE\n" \
"<H1><A NAME=\"${@.XREF}\">${@.NAME} (${@.TITLE})</A></H1>\n" \
"!ELSE\n" \
"<H1><A NAME=\"${@.XREF}\">${@.NAME}</A></H1>\n" \
"!ENDIF\n" \
"!INCLUDE @.img\n" \
"!IF @.EVENT\n" \
"<UL>\n" \
"!RESET i\n" \
"!WHILE @.EVENT[i]\n" \
"!IF @.EVENT[i].DATE\n" \
"!IF @.EVENT[i].PLACE\n" \
"<LI><EM>${@.EVENT[i].TAG}</EM>: ${@.EVENT[i].DATE}, ${@.EVENT[i].PLACE.NAME}\n" \
"!ELSE\n" \
"<LI><EM>${@.EVENT[i].TAG}</EM>: ${@.EVENT[i].DATE}\n" \
"!ENDIF\n" \
"!ELSE\n" \
"<LI><EM>${@.EVENT[i].TAG}</EM>: ${@.EVENT[i].PLACE.NAME}\n" \
"!ENDIF\n" \
"!INCREMENT i\n" \
"!END\n" \
"</UL>\n" \
"!ENDIF\n" \
"!IF @.FATHER\n" \
"<EM>Father:</EM> <A HREF=\"${@.FATHER.&}\">${@.FATHER.NAME}</A><BR>\n" \
"!ENDIF\n" \
"!IF @.MOTHER\n" \
"<EM>Mother:</EM> <A HREF=\"${@.MOTHER.&}\">${@.MOTHER.NAME}</A><BR>\n" \
"!ENDIF\n" \
"<BR>\n" \
"!RESET i\n" \
"!WHILE @.FAMS[i]\n" \
"<EM>Family ${i}</EM>:\n" \
"!IF @.ISMALE\n" \
"!IF @.FAMS[i].FAMILY.WIFE\n" \
"<A HREF=\"${@.FAMS[i].FAMILY.WIFE.&}\">${@.FAMS[i].FAMILY.WIFE.NAME}</A>\n" \
"!ENDIF\n" \
"!ENDIF\n"
"!IF @.ISFEMALE\n" \
"!IF @.FAMS[i].FAMILY.HUSBAND\n" \
"<A HREF=\"${@.FAMS[i].FAMILY.HUSBAND.&}\">${@.FAMS[i].FAMILY.HUSBAND.NAME}</A>\n" \
"!ENDIF\n" \
"!ENDIF\n" \
"!IF @.FAMS[i].FAMILY.EVENT\n" \
"<UL>\n" \
"!RESET j\n" \
"!WHILE @.FAMS[i].FAMILY.EVENT[j]\n" \
"!IF @.FAMS[i].FAMILY.EVENT[j].DATE\n" \
"!IF @.FAMS[i].FAMILY.EVENT[j].PLACE\n" \
"<LI><EM>${@.FAMS[i].FAMILY.EVENT[j].TAG}</EM>: ${@.FAMS[i].FAMILY.EVENT[j].DATE}, ${@.FAMS[i].FAMILY.EVENT[j].PLACE.NAME}\n" \
"!ELSE\n" \
"<LI><EM>${@.FAMS[i].FAMILY.EVENT[j].TAG}</EM>: ${@.FAMS[i].FAMILY.EVENT[j].DATE}\n" \
"!ENDIF\n" \
"!ELSE\n" \
"<LI><EM>${@.FAMS[i].FAMILY.EVENT[j].TAG}</EM>: ${@.FAMS[i].FAMILY.EVENT[j].PLACE.NAME}\n" \
"!ENDIF\n" \
"!INCREMENT j\n" \
"!END\n" \
"</UL>\n" \
"!ELSE\n" \
"<BR>\n" \
"!ENDIF\n",
/* 1 */
"!IF @.FAMS[i].FAMILY.CHILDREN\n" \
"<OL>\n" \
"!RESET j\n" \
"!WHILE @.FAMS[i].FAMILY.CHILDREN[j]\n" \
"<LI><A HREF=\"${@.FAMS[i].FAMILY.CHILDREN[j].INDIV.&}\">" \
"${@.FAMS[i].FAMILY.CHILDREN[j].INDIV.NAME}</A>\n" \
"!INCREMENT j\n" \
"!END\n" \
"</OL>\n" \
"!ELSE\n" \
"<BR>\n" \
"!ENDIF\n" \
"!INCREMENT i\n" \
"!END\n",
/* 2 */ /* Pedigree diagram, if available */
"!PEDIGREE @\n",
/* 3 */ /* This should be the value of INDEX_ANCHOR_POS in output.h */
INDEX_ANCHOR_NOSUBDIR \
"<P>\n",
/* 4 */
"!IF @.NOTE\n" \
"<H2>Notes</H2>\n" \
"!RESET i\n" \
"!WHILE @.NOTE[i]\n" \
"${@.NOTE[i].TEXT}\n" \
"!RESET j\n" \
"!WHILE @.NOTE[i].CONT[j]\n" \
"${@.NOTE[i].CONT[j].TEXT}\n" \
"!INCREMENT j\n" \
"!END\n" \
"<P>\n" \
"!INCREMENT i\n" \
"!END\n" \
"!ENDIF\n" \
"<BR>\n" \
"!IF @.SOURCE\n" \
"<H2>Sources</H2>\n" \
"!RESET i\n" \
"!WHILE @.SOURCE[i]\n" \
"${@.SOURCE[i].SOURCE.TEXT}\n" \
"!RESET j\n" \
"!WHILE @.SOURCE[i].SOURCE.CONT[j]\n" \
"${@.SOURCE[i].SOURCE.CONT[j].TEXT}\n" \
"!INCREMENT j\n" \
"!END\n" \
"<P>\n" \
"!INCREMENT i\n" \
"!END\n" \
"!ENDIF\n" \
"!INCLUDE @.inc\n" \
"</BODY>\n" \
"</HTML>\n"
};
int individual_template_base_size =
sizeof(individual_template_base)/sizeof(char *);
char *individual_template = NULL;
char *index_template =
"<HTML>\n<HEAD>\n<TITLE>" \
"(${@.PARENT.FIRST.NAME} - ${@.PARENT.LAST.NAME})" \
"</TITLE>\n</HEAD>\n" \
"<BODY>\n<H2>Index of Persons</H2>\n" \
"!WHILE @\n" \
"!IF @.CHILDREN\n" \
"<A HREF=\"${@.&}\">\n" \
"${@.FIRST.NAME} - ${@.LAST.NAME}\n" \
"</A><BR>\n" \
"!ELSE\n" \
"!IF @.FIRST.TITLE\n" \
"<A NAME=\"${@.FIRST.XREF}\"></A>\n" \
"<A HREF=\"${@.FIRST.&}\">" \
"${@.FIRST.NAME} (${@.FIRST.TITLE})</A>\n" \
"!ELSE\n" \
"<A NAME=\"${@.FIRST.XREF}\"></A>\n" \
"<A HREF=\"${@.FIRST.&}\">" \
"${@.FIRST.NAME}</A>\n" \
"!ENDIF\n" \
" (${@.FIRST.BIRTH.DATE}\n" \
/* "!IF @.FIRST.BIRTH.PLACE.NAME\n" \
", ${@.FIRST.BIRTH.PLACE.NAME}\n" \
"!ENDIF\n" \ */
" - ${@.FIRST.DEATH.DATE}\n" \
/* "!IF @.LAST.BIRTH.PLACE.NAME\n" \
", ${@.LAST.BIRTH.PLACE.NAME}\n" \
"!ENDIF\n" \ */
")<BR>\n" \
"!ENDIF\n" \
"!IF @.NEXT\n" \
"!ELSE\n" \
"</P>\n" \
"!IF @.PARENT\n" \
"!IF @.PARENT.PARENT\n" \
"<A HREF=\"${@.PARENT.PARENT.&}\">UP</A> " \
"(${@.PARENT.PARENT.FIRST.NAME} - ${@.PARENT.PARENT.LAST.NAME}\n" \
"</A>)<BR>\n" \
"!ENDIF\n" \
"!IF @.PARENT.PREV\n" \
"<A HREF=\"${@.PARENT.PREV.&}\">BACK</A> " \
"(${@.PARENT.PREV.FIRST.NAME} - ${@.PARENT.PREV.LAST.NAME}\n" \
"</A>)<BR>\n" \
"!ENDIF\n" \
"!IF @.PARENT.NEXT\n" \
"<A HREF=\"${@.PARENT.NEXT.&}\">NEXT</A> " \
"(${@.PARENT.NEXT.FIRST.NAME} - ${@.PARENT.NEXT.LAST.NAME}\n" \
"</A>)<BR>\n" \
"!ENDIF\n" \
"!ENDIF\n" \
"!ENDIF\n" \
"!NEXT\n" \
"!END\n" \
"<P>\n"
#ifdef MSDOS
"<A HREF=\"surnames.htm\">SURNAMES</A>\n"
#else
"<A HREF=\"SURNAMES.html\">SURNAMES</A>\n"
#endif
"</BODY>\n</HTML>\n";
char *surname_template =
"<HTML>\n<HEAD>\n<TITLE>" \
"Index of Surnames" \
"</TITLE>\n</HEAD>\n" \
"<BODY>\n<H2>Index of Surnames</H2>\n" \
"!WHILE @\n" \
"!IF @.NEXT\n" \
"<A HREF=\"${@.PARENT.&}#${@.FIRST.XREF}\">${@.FIRST.SURNAME}</A>,\n" \
"!ELSE\n" \
"<A HREF=\"${@.PARENT.&}#${@.FIRST.XREF}\">${@.FIRST.SURNAME}</A>\n" \
"!ENDIF\n" \
"!NEXT\n" \
"!END\n" \
"<P>\n" \
INDEX_ANCHOR_NOSUBDIR \
"</BODY>\n</HTML>\n";
/*
* Record types
*/
typedef enum {
T_INTEGER, T_STRING, T_PLACE, T_NOTE, T_XREF, T_SOURCE,
T_FAMEVENT, T_EVENT, T_INDIV, T_FAMILY, T_CONT, T_URL, T_INDEX
} record_type;
/*
* Interpreter state
*/
int skipping;
#define CONTROL_STACK_SIZE 100
char *while_stack[CONTROL_STACK_SIZE];
int while_stack_top;
int if_stack[CONTROL_STACK_SIZE];
int if_stack_top;
char *template;
char *template_start;
int current_index;
record_type current_type = T_INTEGER;
union value {
int integer;
char *string;
struct place_structure *place;
struct note_structure *note;
struct xref *xref;
struct event_structure *event;
struct individual_record *indiv;
struct family_record *family;
struct continuation *cont;
struct source_record *source;
struct index_node *index;
char *url;
} current_value;
union value root;
record_type root_type;
char current_url[FILENAME_MAX+1];
int doing_index = 0;
void interpret(FILE *ofile);
void variable(FILE *ofile);
void command(FILE *ofile);
void collect_identifier(char **ret);
void skip_white_space();
void output_error(char *msg);
void set_variable(char *name, int value);
int get_variable(char *name);
void family_select(char *field);
void indiv_select(char *field);
void event_select(char *field);
void note_select(char *field);
void source_select(char *field);
void cont_select(char *field);
void place_select(char *field);
void xref_select(char *field);
void index_select(char *field);
void construct_url(char *dest);
void output_pedigree(FILE *ofile, struct individual_record *rt, int depth);
void compute_pedigree_widths(struct individual_record *rt, int depth,
int *widths);
void output_pedigree_info(FILE *ofile, struct individual_record *rt,
int maxdepth, int *widths, int curdepth,
unsigned int parities);
void output_pedigree_name(FILE *ofile, struct individual_record *indiv,
int width, char *hdr);
void pedigree_indent(FILE *ofile, int *widths, int curdepth,
unsigned int parities);
void output_individual(struct individual_record *rt)
{
FILE *ofile;
char path[FILENAME_MAX+1];
char url[FILENAME_MAX+1];
char id[9];
if(indivs_per_directory) {
sprintf(path, "D%04d",
(rt->serial / indivs_per_directory) + 1);
#ifdef MSDOS
_mkdir(path);
#else
mkdir(path, 0777);
#endif
strcat(path, "/");
} else {
sprintf(path, "%s", "");
}
if(indivs_per_file) {
sprintf(id, "S%07d", ((rt->serial - 1) / indivs_per_file) + 1);
sprintf(url, file_template, id);
} else {
sprintf(url, file_template, rt->xref);
}
strcat(path, url);
if(indivs_per_file && rt->serial%indivs_per_file != 1) {
ofile = fopen(path, "a");
} else {
#ifdef MSDOS
fprintf(stderr, "Creating %s\n", path);
#endif
ofile = fopen(path, "w");
}
if(ofile == NULL) {
fprintf(stderr, "Failed to create individual file %s\n", path);
return;
}
template = template_start = individual_template;
root.indiv = rt;
root_type = T_INDIV;
interpret(ofile);
fclose(ofile);
}
/*
* Recursive routine to create hierarchical index.
* Assumes that inp is the root of a tree of index nodes.
* Leaves are identifiable as those with inp->children == NULL.
* In this case, inp->first == inp->last, a pointer to the
* single individual at this leaf.
* The index template expects the head of a list of index nodes as the root.
* For each node in the list, it determines whether that node is an
* internal node or a leaf. If a leaf, then it outputs a leaf entry for
* the one individual under that node. If an internal node, it outputs
* a range entry for that node.
*/
void output_index(struct index_node *inp)
{
FILE *ofile;
struct index_node *inc;
char path[FILENAME_MAX+1];
char base[8];
if(inp->children == NULL)
return;
if(inp->id == 0)
sprintf(path, file_template, "INDEX");
else {
sprintf(base, "IND%04d", inp->id);
sprintf(path, file_template, base);
}
#ifdef MSDOS
fprintf(stderr, "Creating %s\n", path);
#endif
if((ofile = fopen(path, "w")) == NULL) {
fprintf(stderr, "Failed to create index file %s\n", path);
return;
}
template = template_start = index_template;
root.index = inp->children;
root_type = T_INDEX;
doing_index = 1;
interpret(ofile);
doing_index = 0;
fclose(ofile);
for(inc = inp->children; inc != NULL; inc = inc->next)
output_index(inc);
}
/*
* Output surname index with links to level 1 index nodes.
* The argument is the head of a list of index nodes, one for each surname.
* Each index node is linked (via FIRST and LAST) to the first and last
* individuals with that surname, and via PARENT to the level 1 index node
* containing the first index entry for that surname.
*/
void output_surnames(struct index_node *inp)
{
FILE *ofile;
char path[FILENAME_MAX+1];
sprintf(path, file_template, "SURNAMES");
#ifdef MSDOS
fprintf(stderr, "Creating %s\n", path);
#endif
if((ofile = fopen(path, "w")) == NULL) {
fprintf(stderr, "Failed to create surnames file %s\n", path);
return;
}
template = template_start = surname_template;
root.index = inp;
root_type = T_INDEX;
doing_index = 1;
interpret(ofile);
doing_index = 0;
fclose(ofile);
}
/*
* Output index file for use by GenWeb automatic indexers.
*
* First version produces a format suitable for AWK/SORT processing:
* each individual is on a single line, with fields terminated by '|'.
*/
void output_gendex(struct individual_record *rt)
{
FILE *ofile;
char path[FILENAME_MAX+1], *cp;
struct event_structure *ep;
int found;
sprintf(path, "%s", "GENDEX.txt");
#ifdef MSDOS
fprintf(stderr, "Creating %s\n", path);
#endif
if((ofile = fopen(path, "w")) == NULL) {
fprintf(stderr, "Failed to create index file %s\n", path);
return;
}
for( ; rt != NULL; rt = rt->next) {
/*
* First field is the filename/URL
*/
current_type = T_INDIV;
current_value.indiv = rt;
doing_index = 1;
construct_url(current_url);
doing_index = 0;
fprintf(ofile, "%s|", current_url);
/*
* Second field is the surname
*/
fprintf(ofile, "%s|", rt->personal_name && rt->personal_name->surname ?
rt->personal_name->surname : "");
/*
* Third field is the full name as from a GEDCOM file
*/
if(rt->personal_name && rt->personal_name->name) {
for(cp = rt->personal_name->name; *cp != '\0'; cp++) {
if(cp - rt->personal_name->name
== rt->personal_name->surname_start
|| cp - rt->personal_name->name
== rt->personal_name->surname_end)
fputc('/', ofile);
else
fputc(*cp, ofile);
}
}
fprintf(ofile, "|");
/*
* Fourth and fifth fields are the birth date and place
*/
found = 0;
for(ep = rt->events; ep != NULL; ep = ep->next) {
if(ep->tag && ep->tag->value == BIRT) {
found++;
if(ep->date)
fprintf(ofile, "%s", ep->date);
fprintf(ofile, "|");
if(ep->place)
fprintf(ofile, "%s", ep->place->name);
fprintf(ofile, "|");
}
}
if(found == 0)
fprintf(ofile, "||");
/*
* Sixth and seventh fields are the death date and place
*/
found = 0;
for(ep = rt->events; ep != NULL; ep = ep->next) {
if(ep->tag && ep->tag->value == DEAT) {
found++;
if(ep->date)
fprintf(ofile, "%s", ep->date);
fprintf(ofile, "|");
if(ep->place)
fprintf(ofile, "%s", ep->place->name);
fprintf(ofile, "|");
}
}
if(found == 0)
fprintf(ofile, "||");
fprintf(ofile, "\n");
}
fclose(ofile);
}
/*
* This version outputs GEDCOM
*/
/*
void output_gendex(struct individual_record *rt)
{
FILE *ofile;
char path[FILENAME_MAX+1], *cp;
struct event_structure *ep;
sprintf(path, "%s", "GENDEX.ged");
#ifdef MSDOS
fprintf(stderr, "Creating %s\n", path);
#endif
if((ofile = fopen(path, "w")) == NULL) {
fprintf(stderr, "Failed to create index file %s\n", path);
return;
}
fprintf(ofile, "0 HEAD\n");
fprintf(ofile, "1 SOUR GED2HTML\n");
fprintf(ofile, "1 CHAR ANSEL\n");
fprintf(ofile, "0 NOTE This limited-information GEDCOM file was automatically\n");
fprintf(ofile, "1 CONT produced by 'GED2HTML' for use by automatic indexers.\n");
fprintf(ofile, "1 CONT It contains individual's names, birth and death dates and\n");
fprintf(ofile, "1 CONT places, and the URL of the HTML individual data page,\n");
fprintf(ofile, "1 CONT relative to the location of this file.\n");
for( ; rt != NULL; rt = rt->next) {
fprintf(ofile, "0 @%s@ INDI\n", rt->xref ? rt->xref : "");
if(rt->personal_name && rt->personal_name->name) {
fprintf(ofile, "1 NAME ");
for(cp = rt->personal_name->name; *cp != '\0'; cp++) {
if(cp - rt->personal_name->name
== rt->personal_name->surname_start
|| cp - rt->personal_name->name
== rt->personal_name->surname_end)
fputc('/', ofile);
else
fputc(*cp, ofile);
}
fprintf(ofile, "\n");
}
for(ep = rt->events; ep != NULL; ep = ep->next) {
if(ep->tag) {
switch(ep->tag->value) {
case BIRT:
fprintf(ofile, "1 BIRT\n");
if(ep->date)
fprintf(ofile, "2 DATE %s\n", ep->date);
if(ep->place)
fprintf(ofile, "2 PLAC %s\n", ep->place->name);
break;
case DEAT:
fprintf(ofile, "1 DEAT\n");
if(ep->date)
fprintf(ofile, "2 DATE %s\n", ep->date);
if(ep->place)
fprintf(ofile, "2 PLAC %s\n", ep->place->name);
break;
default:
break;
}
}
}
current_type = T_INDIV;
current_value.indiv = rt;
doing_index = 1;
construct_url(current_url);
doing_index = 0;
fprintf(ofile, "1 NOTE URL: %s\n", current_url);
if(rt->personal_name && rt->personal_name->surname)
fprintf(ofile, "1 NOTE SURNAME: %s\n", rt->personal_name->surname);
}
fprintf(ofile, "0 TRLR\n");
fclose(ofile);
}
*/
/*
* Construction of pedigree charts
*/
#define PEDIGREE_MINW 2
void output_pedigree(FILE *ofile, struct individual_record *rt, int depth)
{
int *widths, i;
if((widths = malloc((depth+1) * sizeof(int))) == NULL)
out_of_memory();
for(i = 0; i <= depth; i++)
widths[i] = PEDIGREE_MINW;
compute_pedigree_widths(rt, depth, widths);
fprintf(ofile, "<HR>\n<PRE>\n");
output_pedigree_info(ofile, rt, depth, widths, 0, 0);
fprintf(ofile, "</PRE>\n<HR>\n");
free(widths);
}
void compute_pedigree_widths(struct individual_record *rt, int depth,
int *widths)
{
char *cp;
int i, space = 0;
if(rt->personal_name) {
for(cp = rt->personal_name->name, i = 0; *cp != '\0'; cp++) {
if(!space || *cp != ' ')
i++;
if(*cp == ' ')
space = 1;
else
space = 0;
}
i += 2;
if(i > *widths)
*widths = i;
}
if(depth && rt->famc && rt->famc->pointer.family) {
if(rt->famc->pointer.family->husband
&& rt->famc->pointer.family->husband->pointer.individual)
compute_pedigree_widths(rt->famc->pointer.family->
husband->pointer.individual,
depth-1, widths+1);
if(rt->famc->pointer.family->wife
&& rt->famc->pointer.family->wife->pointer.individual)
compute_pedigree_widths(rt->famc->pointer.family->
wife->pointer.individual,
depth-1, widths+1);
}
}
void output_pedigree_info(FILE *ofile, struct individual_record *rt,
int maxdepth, int *widths, int curdepth,
unsigned int parities)
{
struct family_record *family = NULL;
struct individual_record *husband = NULL, *wife = NULL;
if(curdepth > maxdepth)
return;
if(rt && rt->famc) {
family = rt->famc->pointer.family;
if(family && family->husband)
husband = family->husband->pointer.individual;
if(family && family->wife)
wife = family->wife->pointer.individual;
}
if(curdepth == 0) {
output_pedigree_info(ofile, husband, maxdepth,
widths, 1, parities);
fprintf(ofile, "|\n");
fprintf(ofile, "|--");
output_pedigree_name(ofile, rt, 0, "");
fprintf(ofile, "\n");
fprintf(ofile, "|\n");
output_pedigree_info(ofile, wife, maxdepth,
widths, 1, parities | 1);
} else {
output_pedigree_info(ofile, husband, maxdepth,
widths, curdepth+1, parities);
pedigree_indent(ofile, widths, curdepth, parities);
output_pedigree_name(ofile, rt, widths[curdepth], "_");
if(curdepth < maxdepth)
fprintf(ofile, "|");
fprintf(ofile, "\n");
output_pedigree_info(ofile, wife, maxdepth, widths,
curdepth+1, parities | (1 << curdepth));
}
}
void output_pedigree_name(FILE *ofile, struct individual_record *indiv,
int width, char *hdr)
{
char *np;
int space = 0;
if(indiv) {
current_value.indiv = indiv;
current_type = T_INDIV;
construct_url(current_url);
} else
*current_url = '\0';
fprintf(ofile, "<A HREF=\"%s\">", current_url);
for(np = hdr; *np != '\0'; np++, width--)
fputc(*np, ofile);
if(indiv && indiv->personal_name)
np = indiv->personal_name->name;
else
np = "";
for( ; *np != '\0'; np++) {
if(!space || *np != ' ') {
fputc(*np, ofile);
width--;
}
if(*np == ' ')
space = 1;
else
space = 0;
}
while(width-- > 0)
fputc('_', ofile);
fprintf(ofile, "</A>");
}
void pedigree_indent(FILE *ofile, int *widths, int curdepth,
unsigned int parities)
{
int i, j, p, q;
for(i = 1; i < curdepth; i++) {
/*
* Determine whether the line being printed is in the middle
* two quarters of the subtree headed at depth i.
*/
p = parities & (1<<(i-1)) ? (~0) : 0;
q = parities & (1<<i) ? (~0) : 0;
if(p ^ q)
fprintf(ofile, "|");
else
fprintf(ofile, " ");
for(j = 0; j < widths[i]; j++)
fprintf(ofile, " ");
}
if(parities & (1<<(curdepth-1)))
fprintf(ofile, "|");
else
fprintf(ofile, " ");
}
/*
* Interpret the template stored in "template", outputting results
* to "ofile". The value "root" is the starting point for
* the interpretation of variables. Its type is given by "root_type".
*/
void interpret(FILE *ofile)
{
char c;
int start_of_line = 1;
while((c = *template++) != '\0') {
switch(c) {
case '\n':
start_of_line = 1;
/* fall through intentional */
case ' ':
case '\t':
if(!skipping)
fputc(c, ofile);
continue;
case '!':
if(!start_of_line) {
if(!skipping)
fputc(c, ofile);
continue;
}
template--;
command(ofile);
continue;
case '$':
start_of_line = 0;
variable(ofile);
if(!skipping) {
if(current_type == T_STRING) {
fprintf(ofile, "%s", current_value.string);
} else if(current_type == T_URL) {
fprintf(ofile, current_value.url);
} else if(current_type == T_INTEGER) {
/* Integer variables start from 1 */
fprintf(ofile, "%d", current_value.integer + 1);
} else {
output_error("Attempt to output something not an integer or string");
}
}
continue;
default:
start_of_line = 0;
if(!skipping)
fputc(c, ofile);
continue;
}
}
}
/*
* After having seen the initial $, interpret a simple or compound variable.
*/
void variable(FILE *ofile)
{
char c, *selector;
int braces = 0;
int first = 1;
/*
* $$ means output a single $
*/
if(*template == '$') {
if(!skipping)
fputc(*template++, ofile);
return;
}
while((c = *template) != '\0') {
switch(c) {
/*
* An '@' indicates the current individual
*/
case '@':
first = 0;
template++;
if(!skipping) {
current_value = root;
current_type = root_type;
}
if(*template == '.') {
template++;
continue;
} else if(*template == '[')
continue;
else
return;
/*
* Braces in variables simply serve as delimiters
*/
case '{':
template++;
braces++;
continue;
case '}':
template++;
if(braces) {
braces--;
if(braces)
continue;
else
return;
} else {
if(!skipping)
fputc(c, ofile);
return;
}
/*
* Brackets indicate integer subscripts
*/
case '[':
first = 0;
template++;
/*
* Integer constant
*/
if(*template >= '0' && *template <= '9') {
/*
* Convert integer value and leave on top of stack
* (if not skipping)
*/
}
/*
* Variable subscript
*/
else {
record_type previous_type;
union value previous_value;
previous_type = current_type;
previous_value = current_value;
variable(ofile);
if(!skipping && current_type != T_INTEGER)
output_error("Subscript is not an integer variable");
else {
current_index = current_value.integer;
current_type = previous_type;
current_value = previous_value;
}
if(*template != ']') {
output_error("Subscript fails to end with ']'");
} else {
template++;
}
}
if(!skipping) {
switch(current_type) {
case T_INTEGER:
case T_STRING:
output_error("Can't apply subscript to an integer or string");
break;
case T_PLACE:
while(current_index--) place_select("NEXT");
break;
case T_NOTE:
while(current_index--) note_select("NEXT");
break;
case T_SOURCE:
while(current_index--) source_select("NEXT");
break;
case T_CONT:
while(current_index--) cont_select("NEXT");
break;
case T_EVENT:
while(current_index--) event_select("NEXT");
break;
case T_INDIV:
while(current_index--) indiv_select("NEXT");
break;
case T_FAMILY:
while(current_index--) family_select("NEXT");
break;
case T_XREF:
while(current_index--) xref_select("NEXT");
break;
case T_INDEX:
while(current_index--) index_select("NEXT");
break;
default:
break;
}
}
if(*template == '.') {
template++;
continue;
} else if(*template == '[' || *template == '}')
continue;
else
return;
/*
* An ampersand means turn the current individual or index into a URL
*/
case '&':
template++;
if(!skipping) {
construct_url(current_url);
current_type = T_URL;
current_value.url = current_url;
}
if(*template == '}')
continue;
else
return;
/*
* Alphabetic characters indicate selector name.
* Anything else is a delimiter.
*/
default:
collect_identifier(&selector);
if(*selector == '\0')
return;
if(first) {
if(!skipping) {
current_value.integer = get_variable(selector);
current_type = T_INTEGER;
}
if(*template == '}')
continue;
else
return;
}
if(!skipping) {
switch(current_type) {
case T_INTEGER:
case T_STRING:
output_error("Can't apply selector to an integer or string");
break;
case T_PLACE:
place_select(selector);
break;
case T_NOTE:
note_select(selector);
break;
case T_SOURCE:
source_select(selector);
break;
case T_CONT:
cont_select(selector);
break;
case T_EVENT:
event_select(selector);
break;
case T_INDIV:
indiv_select(selector);
break;
case T_FAMILY:
family_select(selector);
break;
case T_XREF:
xref_select(selector);
break;
case T_INDEX:
index_select(selector);
break;
default:
break;
}
}
if(*template == '.') {
template++;
continue;
} else if(*template == '[' || *template == '}')
continue;
else
return;
}
}
}
/*
* Record field selection operations
*/
void family_select(char *field)
{
struct family_record *r = current_value.family;
if(!strcmp(field, "XREF")) {
current_type = T_STRING;
current_value.string = (r && r->xref) ? r->xref: "";
} else if(!strcmp(field, "REFN")) {
current_type = T_STRING;
current_value.string = (r && r->refn) ? r->refn: "";
} else if(!strcmp(field, "HUSBAND")) {
current_type = T_INDIV;
current_value.indiv =
(r && r->husband) ? r->husband->pointer.individual : NULL;
} else if(!strcmp(field, "WIFE")) {
current_type = T_INDIV;
current_value.indiv =
(r && r->wife) ? r->wife->pointer.individual: NULL;
} else if(!strcmp(field, "CHILDREN")) {
current_type = T_XREF;
current_value.xref =
(r && r->children) ? r->children: NULL;
} else if(!strcmp(field, "NOTE")) {
current_type = T_NOTE;
current_value.note = r ? r->notes: NULL;
} else if(!strcmp(field, "EVENT")) {
current_type = T_EVENT;
current_value.event = r ? r->events: NULL;
} else if(!strcmp(field, "NEXT")) {
current_value.family = r ? r->next: NULL;
} else {
output_error("Unrecognized selector applied to family record");
}
}
void indiv_select(char *field)
{
struct individual_record *r = current_value.indiv;
if(!strcmp(field, "XREF")) {
current_type = T_STRING;
current_value.string = (r && r->xref) ? r->xref: "";
} else if(!strcmp(field, "NAME")) {
current_type = T_STRING;
current_value.string = (r && r->personal_name) ?
r->personal_name->name: "???";
} else if(!strcmp(field, "SURNAME")) {
current_type = T_STRING;
current_value.string = (r && r->personal_name
&& r->personal_name->surname) ?
r->personal_name->surname: "???";
} else if(!strcmp(field, "TITLE")) {
current_type = T_STRING;
current_value.string = (r && r->title) ? r->title: "";
} else if(!strcmp(field, "ISMALE")) {
current_type = T_INTEGER;
current_value.integer = (r && r->sex == 'M');
} else if(!strcmp(field, "ISFEMALE")) {
current_type = T_INTEGER;
current_value.integer = (r && r->sex == 'F');
} else if(!strcmp(field, "REFN")) {
current_type = T_STRING;
current_value.string = (r && r->refn) ? r->refn: "";
} else if(!strcmp(field, "RFN")) {
current_type = T_STRING;
current_value.string = (r && r->rfn) ? r->rfn: "";
} else if(!strcmp(field, "AFN")) {
current_type = T_STRING;
current_value.string = (r && r->afn) ? r->afn: "";
} else if(!strcmp(field, "FAMC")) {
current_type = T_XREF;
current_value.xref = r ? r->famc: NULL;
} else if(!strcmp(field, "FAMS")) {
current_type = T_XREF;
current_value.xref = r ? r->fams: NULL;
} else if(!strcmp(field, "FATHER")) {
current_value.indiv =
(r && r->famc && r->famc->pointer.family
&& r->famc->pointer.family->husband)
? r->famc->pointer.family->husband->pointer.individual: NULL;
} else if(!strcmp(field, "MOTHER")) {
current_value.indiv =
(r && r->famc && r->famc->pointer.family
&& r->famc->pointer.family->wife)
? r->famc->pointer.family->wife->pointer.individual: NULL;
} else if(!strcmp(field, "NOTE")) {
current_type = T_NOTE;
current_value.note = r ? r->notes: NULL;
} else if(!strcmp(field, "SOURCE")) {
current_type = T_XREF;
current_value.xref =
(r && r->sources) ? r->sources: NULL;
} else if(!strcmp(field, "EVENT")) {
current_type = T_EVENT;
current_value.event = r ? r->events: NULL;
} else if(!strcmp(field, "BIRTH")) {
struct event_structure *ep;
current_type = T_EVENT;
current_value.event = NULL;
for(ep = r->events; ep != NULL; ep = ep->next) {
if(ep->tag->value == BIRT)
current_value.event = ep;
}
} else if(!strcmp(field, "DEATH")) {
struct event_structure *ep;
current_type = T_EVENT;
current_value.event = NULL;
for(ep = r->events; ep != NULL; ep = ep->next) {
if(ep->tag->value == DEAT)
current_value.event = ep;
}
} else if(!strcmp(field, "NEXT")) {
current_value.indiv = r ? r->next: NULL;
} else {
output_error("Unrecognized selector applied to individual record");
}
}
void event_select(char *field)
{
struct event_structure *r = current_value.event;
if(!strcmp(field, "TAG")) {
current_type = T_STRING;
current_value.string = (r && r->tag) ? r->tag->pname[default_language]: "";
} else if(!strcmp(field, "DATE")) {
current_type = T_STRING;
current_value.string = (r && r->date) ? r->date: "";
} else if(!strcmp(field, "PLACE")) {
current_type = T_PLACE;
current_value.place = r ? r->place: NULL;
} else if(!strcmp(field, "NEXT")) {
current_value.event = r ? r->next: NULL;
} else {
output_error("Unrecognized selector applied to event structure");
}
}
void note_select(char *field)
{
struct note_structure *r = current_value.note;
if(!strcmp(field, "XREF")) {
current_type = T_STRING;
current_value.string = (r && r->xref) ? r->xref: "";
} else if(!strcmp(field, "TEXT")) {
current_type = T_STRING;
current_value.string = (r && r->text) ? r->text: "";
} else if(!strcmp(field, "NEXT")) {
current_value.note = r ? r->next : NULL;
} else if(!strcmp(field, "CONT")) {
current_type = T_CONT;
current_value.cont = r ? r->cont: NULL;
} else {
output_error("Unrecognized selector applied to note structure");
}
}
void source_select(char *field)
{
struct source_record *r = current_value.source;
if(!strcmp(field, "XREF")) {
current_type = T_STRING;
current_value.string = (r && r->xref) ? r->xref: "";
} else if(!strcmp(field, "TEXT")) {
current_type = T_STRING;
current_value.string = (r && r->text) ? r->text: "";
} else if(!strcmp(field, "CONT")) {
current_type = T_CONT;
current_value.cont = r ? r->cont: NULL;
} else {
output_error("Unrecognized selector applied to source record");
}
}
void cont_select(char *field)
{
struct continuation *c = current_value.cont;
if(!strcmp(field, "TEXT")) {
current_type = T_STRING;
current_value.string = (c && c->text) ? c->text: "";
} else if(!strcmp(field, "NEXT")) {
current_value.cont = c ? c->next: NULL;
} else {
output_error("Unrecognized selector applied to continuation structure");
}
}
void place_select(char *field)
{
struct place_structure *r = current_value.place;
if(!strcmp(field, "NAME")) {
current_type = T_STRING;
current_value.string = (r && r->name) ? r->name: "";
} else if(!strcmp(field, "NOTE")) {
current_type = T_NOTE;
current_value.note = r ? r->notes: NULL;
} else {
output_error("Unrecognized selector applied to place structure");
}
}
void xref_select(char *field)
{
struct xref *r = current_value.xref;
if(!strcmp(field, "INDIV")) {
current_type = T_INDIV;
current_value.indiv = r ? r->pointer.individual: NULL;
} else if(!strcmp(field, "FAMILY")) {
current_type = T_FAMILY;
current_value.family = r ? r->pointer.family: NULL;
} else if(!strcmp(field, "SOURCE")) {
current_type = T_SOURCE;
current_value.source = r ? r->pointer.source: NULL;
} else if(!strcmp(field, "NEXT")) {
current_value.xref = r ? r->next: NULL;
} else {
output_error("Unrecognized selector applied to cross-reference");
}
}
void index_select(char *field)
{
struct index_node *r = current_value.index;
if(!strcmp(field, "FIRST")) {
current_type = T_INDIV;
current_value.indiv = r ? r->first: NULL;
} else if(!strcmp(field, "LAST")) {
current_type = T_INDIV;
current_value.indiv = r ? r->last: NULL;
} else if(!strcmp(field, "PARENT")) {
current_value.index = r ? r->parent: NULL;
} else if(!strcmp(field, "CHILDREN")) {
current_value.index = r ? r->children: NULL;
} else if(!strcmp(field, "NEXT")) {
current_value.index = r ? r->next: NULL;
} else if(!strcmp(field, "PREV")) {
current_value.index = r ? r->prev: NULL;
} else {
output_error("Unrecognized selector applied to cross-reference");
}
}
/*
* Perform a control command.
*/
void command(FILE *ofile)
{
char *buf;
char *start = template++;
collect_identifier(&buf);
skip_white_space();
if(!strcmp(buf, "RESET")) {
collect_identifier(&buf);
if(*template == '\n')
template++;
else {
output_error("Newline expected");
}
set_variable(buf, 0);
} else if(!strcmp(buf, "INCREMENT")) {
collect_identifier(&buf);
if(*template == '\n')
template++;
else {
output_error("Newline expected");
}
set_variable(buf, get_variable(buf)+1);
} else if(!strcmp(buf, "IF")) {
variable(ofile);
if(*template == '\n') {
template++;
if(current_type == T_STRING) {
if(!strcmp(current_value.string, "")) {
skipping++;
if_stack[if_stack_top++] = 1;
} else {
if_stack[if_stack_top++] = 0;
}
} else {
if(!current_value.integer) {
skipping++;
if_stack[if_stack_top++] = 1;
} else {
if_stack[if_stack_top++] = 0;
}
}
} else {
output_error("Newline expected");
}
} else if(!strcmp(buf, "ELSE")) {
if(*template == '\n') {
template++;
if(if_stack[if_stack_top-1]) {
skipping--;
if_stack[if_stack_top-1] = 0;
} else {
skipping++;
if_stack[if_stack_top-1] = 1;
}
} else {
output_error("Newline expected");
}
} else if(!strcmp(buf, "ENDIF")) {
if(*template == '\n') {
template++;
if(if_stack[--if_stack_top])
skipping--;
} else {
output_error("Newline expected");
}
} else if(!strcmp(buf, "WHILE")) {
variable(ofile);
if(*template == '\n') {
template++;
while_stack[while_stack_top++] = start;
if(!skipping) {
if(current_type == T_STRING) {
if(!strcmp(current_value.string, ""))
skipping++;
} else {
if(!current_value.integer)
skipping++;
}
} else {
skipping++;
}
} else {
output_error("Newline expected");
}
} else if(!strcmp(buf, "END")) {
if(*template == '\n') {
template++;
if(skipping) {
skipping--;
if(while_stack_top)
while_stack_top--;
} else {
if(while_stack_top)
template = while_stack[--while_stack_top];
}
} else {
output_error("Newline expected");
}
} else if(!strcmp(buf, "NEXT")) {
if(*template == '\n') {
template++;
if(!skipping) {
if(root_type == T_INDIV && root.indiv)
root.indiv = root.indiv->next;
else if(root_type == T_INDEX && root.index)
root.index = root.index->next;
else
output_error("'NEXT' applied to something not an individual or index\n");
}
} else {
output_error("Newline expected");
}
} else if(!strcmp(buf, "PEDIGREE")) {
variable(ofile);
if(*template == '\n') {
template++;
if(!skipping) {
if(current_type == T_INDIV) {
if(pedigree_depth)
output_pedigree(ofile, current_value.indiv, pedigree_depth);
} else {
output_error("Attempt to output pedigree for non-individual\n");
}
}
} else {
output_error("Newline expected");
}
} else if(!strcmp(buf, "INCLUDE")) {
char path[FILENAME_MAX+1], *pp;
FILE *incf;
skip_white_space();
for(pp = path; pp - path <= FILENAME_MAX
&& *template && *template != '\n'; ) {
if(*template == '@') {
template++;
if(*template == '@') {
template++;
*pp++ = '@';
} else {
char *id;
if(root_type == T_INDIV)
id = root.indiv->xref;
else
output_error("Use of '!INCLUDE @' on non-individual\n");
while(*id && pp - path <= FILENAME_MAX)
*pp++ = *id++;
}
} else {
*pp++ = *template++;
}
}
*pp = '\0';
if((incf = fopen(path, "r")) != NULL) {
char c;
while((c = fgetc(incf)) != EOF)
fputc(c, ofile);
fclose(incf);
}
} else {
output_error("Unrecognized control command");
}
}
/*
* Pick up the next identifier in the template.
*/
#define IDENTIFIER_MAX 63
void collect_identifier(char **ret)
{
static char id[IDENTIFIER_MAX+1];
char *ip, c;
ip = id;
while(((c = *template) >= 'A' && c <= 'Z')
|| (c >= 'a' && c <= 'z')) {
template++;
if(ip - id < IDENTIFIER_MAX)
*ip++ = c;
}
*ip = '\0';
*ret = id;
}
void skip_white_space()
{
while(*template == ' ' || *template == '\t')
template++;
}
struct binding {
char *name;
int value;
struct binding *next;
} *environment;
void set_variable(char *name, int value)
{
struct binding *b;
for(b = environment; b != NULL; b = b->next) {
if(!strcmp(name, b->name)) {
b->value = value;
return;
}
}
if((b = malloc(sizeof(struct binding))) == NULL)
out_of_memory();
if((b->name = strdup(name)) == NULL)
out_of_memory();
b->value = value;
b->next = environment;
environment = b;
}
int get_variable(char *name)
{
struct binding *b;
for(b = environment; b != NULL; b = b->next) {
if(!strcmp(name, b->name))
return(b->value);
}
set_variable(name, 0);
return(0);
}
void output_error(char *msg)
{
char *tp;
int line = 1;
for(tp = template_start; tp < template; tp++)
if(*tp == '\n')
line++;
fprintf(stderr, "Output error: ");
fprintf(stderr, "%s template line %d: %s\n",
template_start == individual_template ? "individual" :
template_start == index_template ? "index" : "surname",
line, msg);
}
void construct_url(char *dest)
{
char url[FILENAME_MAX+1];
char id[9];
*dest = '\0';
if(current_type == T_INDIV) {
if(indivs_per_directory) {
if(!doing_index)
sprintf(dest, "../");
sprintf(url, "D%04d/",
current_value.indiv ?
(current_value.indiv->serial / indivs_per_directory) + 1 :
0);
} else {
sprintf(url, "%s", "");
}
strcat(dest, url);
if(indivs_per_file) {
sprintf(id, "S%07d",
current_value.indiv ?
((current_value.indiv->serial - 1) / indivs_per_file) + 1:
0);
sprintf(url, file_template, id);
strcat(dest, url);
sprintf(url, "#%s",
current_value.indiv ? current_value.indiv->xref : "");
strcat(dest, url);
} else {
sprintf(url, file_template,
current_value.indiv ? current_value.indiv->xref : "");
strcat(dest, url);
}
} else if(current_type == T_INDEX) {
/*
* I build the indexes myself, so I assume that current_value.index
* is nonnull if we arrive here.
*/
if(current_value.index->id == 0)
sprintf(dest, file_template, "INDEX");
else {
sprintf(id, "IND%04d", current_value.index->id);
sprintf(dest, file_template, id);
}
} else {
output_error("Can only make a URL from an individual or index\n");
}
#ifdef MSDOS
{
char *cp;
for(cp = dest; *cp != '\0' && *cp != '#'; cp++) {
if(isupper(*cp))
*cp = tolower(*cp);
}
}
#endif
}