/* * 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 #include #include #include #ifdef MSDOS #include #else #include #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 */ "\n" \ "\n" \ "!IF @.TITLE\n" \ "${@.XREF}: ${@.NAME} (${@.TITLE})\n" \ "!ELSE\n" \ "${@.XREF}: ${@.NAME}\n" \ "!ENDIF\n" \ "\n" \ "\n" "!IF @.TITLE\n" \ "

${@.NAME} (${@.TITLE})

\n" \ "!ELSE\n" \ "

${@.NAME}

\n" \ "!ENDIF\n" \ "!INCLUDE @.img\n" \ "!IF @.EVENT\n" \ "
    \n" \ "!RESET i\n" \ "!WHILE @.EVENT[i]\n" \ "!IF @.EVENT[i].DATE\n" \ "!IF @.EVENT[i].PLACE\n" \ "
  • ${@.EVENT[i].TAG}: ${@.EVENT[i].DATE}, ${@.EVENT[i].PLACE.NAME}\n" \ "!ELSE\n" \ "
  • ${@.EVENT[i].TAG}: ${@.EVENT[i].DATE}\n" \ "!ENDIF\n" \ "!ELSE\n" \ "
  • ${@.EVENT[i].TAG}: ${@.EVENT[i].PLACE.NAME}\n" \ "!ENDIF\n" \ "!INCREMENT i\n" \ "!END\n" \ "
\n" \ "!ENDIF\n" \ "!IF @.FATHER\n" \ "Father: ${@.FATHER.NAME}
\n" \ "!ENDIF\n" \ "!IF @.MOTHER\n" \ "Mother: ${@.MOTHER.NAME}
\n" \ "!ENDIF\n" \ "
\n" \ "!RESET i\n" \ "!WHILE @.FAMS[i]\n" \ "Family ${i}:\n" \ "!IF @.ISMALE\n" \ "!IF @.FAMS[i].FAMILY.WIFE\n" \ "${@.FAMS[i].FAMILY.WIFE.NAME}\n" \ "!ENDIF\n" \ "!ENDIF\n" "!IF @.ISFEMALE\n" \ "!IF @.FAMS[i].FAMILY.HUSBAND\n" \ "${@.FAMS[i].FAMILY.HUSBAND.NAME}\n" \ "!ENDIF\n" \ "!ENDIF\n" \ "!IF @.FAMS[i].FAMILY.EVENT\n" \ "
    \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" \ "
  • ${@.FAMS[i].FAMILY.EVENT[j].TAG}: ${@.FAMS[i].FAMILY.EVENT[j].DATE}, ${@.FAMS[i].FAMILY.EVENT[j].PLACE.NAME}\n" \ "!ELSE\n" \ "
  • ${@.FAMS[i].FAMILY.EVENT[j].TAG}: ${@.FAMS[i].FAMILY.EVENT[j].DATE}\n" \ "!ENDIF\n" \ "!ELSE\n" \ "
  • ${@.FAMS[i].FAMILY.EVENT[j].TAG}: ${@.FAMS[i].FAMILY.EVENT[j].PLACE.NAME}\n" \ "!ENDIF\n" \ "!INCREMENT j\n" \ "!END\n" \ "
\n" \ "!ELSE\n" \ "
\n" \ "!ENDIF\n", /* 1 */ "!IF @.FAMS[i].FAMILY.CHILDREN\n" \ "
    \n" \ "!RESET j\n" \ "!WHILE @.FAMS[i].FAMILY.CHILDREN[j]\n" \ "
  1. " \ "${@.FAMS[i].FAMILY.CHILDREN[j].INDIV.NAME}\n" \ "!INCREMENT j\n" \ "!END\n" \ "
\n" \ "!ELSE\n" \ "
\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 \ "

\n", /* 4 */ "!IF @.NOTE\n" \ "

Notes

\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" \ "

\n" \ "!INCREMENT i\n" \ "!END\n" \ "!ENDIF\n" \ "
\n" \ "!IF @.SOURCE\n" \ "

Sources

\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" \ "

\n" \ "!INCREMENT i\n" \ "!END\n" \ "!ENDIF\n" \ "!INCLUDE @.inc\n" \ "\n" \ "\n" }; int individual_template_base_size = sizeof(individual_template_base)/sizeof(char *); char *individual_template = NULL; char *index_template = "\n\n" \ "(${@.PARENT.FIRST.NAME} - ${@.PARENT.LAST.NAME})" \ "\n\n" \ "\n

Index of Persons

\n" \ "!WHILE @\n" \ "!IF @.CHILDREN\n" \ "\n" \ "${@.FIRST.NAME} - ${@.LAST.NAME}\n" \ "
\n" \ "!ELSE\n" \ "!IF @.FIRST.TITLE\n" \ "\n" \ "" \ "${@.FIRST.NAME} (${@.FIRST.TITLE})\n" \ "!ELSE\n" \ "\n" \ "" \ "${@.FIRST.NAME}\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" \ */ ")
\n" \ "!ENDIF\n" \ "!IF @.NEXT\n" \ "!ELSE\n" \ "

\n" \ "!IF @.PARENT\n" \ "!IF @.PARENT.PARENT\n" \ "UP " \ "(${@.PARENT.PARENT.FIRST.NAME} - ${@.PARENT.PARENT.LAST.NAME}\n" \ ")
\n" \ "!ENDIF\n" \ "!IF @.PARENT.PREV\n" \ "BACK " \ "(${@.PARENT.PREV.FIRST.NAME} - ${@.PARENT.PREV.LAST.NAME}\n" \ ")
\n" \ "!ENDIF\n" \ "!IF @.PARENT.NEXT\n" \ "NEXT " \ "(${@.PARENT.NEXT.FIRST.NAME} - ${@.PARENT.NEXT.LAST.NAME}\n" \ ")
\n" \ "!ENDIF\n" \ "!ENDIF\n" \ "!ENDIF\n" \ "!NEXT\n" \ "!END\n" \ "

\n" #ifdef MSDOS "SURNAMES\n" #else "SURNAMES\n" #endif "\n\n"; char *surname_template = "\n\n" \ "Index of Surnames" \ "\n\n" \ "\n

Index of Surnames

\n" \ "!WHILE @\n" \ "!IF @.NEXT\n" \ "${@.FIRST.SURNAME},\n" \ "!ELSE\n" \ "${@.FIRST.SURNAME}\n" \ "!ENDIF\n" \ "!NEXT\n" \ "!END\n" \ "

\n" \ INDEX_ANCHOR_NOSUBDIR \ "\n\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, "


\n
\n");
  output_pedigree_info(ofile, rt, depth, widths, 0, 0);
  fprintf(ofile, "
\n
\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, "", 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, ""); } 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<= '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 }