#include "tags.h"
#include "node.h"
#include "database.h"
#include "output.h"
#ifndef FILENAME_MAX
#define FILENAME_MAX 1024
#endif
char *url_template="%s.html";
char *file_template="%s.html";
#ifdef MSDOS
int max_per_directory = 100;
#else
int max_per_directory = 0;
#endif
/*
* I don't like repeating these two templates, which are the same
* except for "INDEX" versus "../INDEX", but I am tired of working on this
* right now and I want to get something out that works right.
*/
char *individual_template_subdir =
"\n" \
"\n" \
"${@.XREF}: ${@.NAME}\n" \
"\n" \
"\n"
"${@.NAME}
\n" \
"!INCLUDE @.img\n" \
"!IF @.EVENT\n" \
"\n" \
"!RESET i\n" \
"!WHILE @.EVENT[i]\n" \
"- ${@.EVENT[i].TAG}: ${@.EVENT[i].DATE}, ${@.EVENT[i].PLACE.NAME}\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" \
"- ${@.FAMS[i].FAMILY.EVENT[j].TAG}: " \
"${@.FAMS[i].FAMILY.EVENT[j].DATE}, ${@.FAMS[i].FAMILY.EVENT[j].PLACE.NAME}\n" \
"!INCREMENT j\n" \
"!END\n" \
"
\n" \
"!ELSE\n" \
"
\n" \
"!ENDIF\n" \
"!IF @.FAMS[i].FAMILY.CHILDREN\n" \
"\n" \
"!RESET j\n" \
"!WHILE @.FAMS[i].FAMILY.CHILDREN[j]\n" \
"- " \
"${@.FAMS[i].FAMILY.CHILDREN[j].INDIV.NAME}\n" \
"!INCREMENT j\n" \
"!END\n" \
"
\n" \
"!ENDIF\n" \
"!INCREMENT i\n" \
"!END\n" \
"INDEX
\n" \
"!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" \
"
\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" \
"
\n" \
"!INCREMENT j\n" \
"!END\n" \
"
\n" \
"!INCREMENT i\n" \
"!END\n" \
"!ENDIF\n" \
"!INCLUDE @.inc\n" \
"\n" \
"\n";
char *individual_template_nosubdir =
"\n" \
"\n" \
"${@.XREF}: ${@.NAME}\n" \
"\n" \
"\n"
"${@.NAME}
\n" \
"!INCLUDE @.img\n" \
"!IF @.EVENT\n" \
"\n" \
"!RESET i\n" \
"!WHILE @.EVENT[i]\n" \
"- ${@.EVENT[i].TAG}: ${@.EVENT[i].DATE}, ${@.EVENT[i].PLACE.NAME}\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" \
"- ${@.FAMS[i].FAMILY.EVENT[j].TAG}: " \
"${@.FAMS[i].FAMILY.EVENT[j].DATE}, ${@.FAMS[i].FAMILY.EVENT[j].PLACE.NAME}\n" \
"!INCREMENT j\n" \
"!END\n" \
"
\n" \
"!ELSE\n" \
"
\n" \
"!ENDIF\n" \
"!IF @.FAMS[i].FAMILY.CHILDREN\n" \
"\n" \
"!RESET j\n" \
"!WHILE @.FAMS[i].FAMILY.CHILDREN[j]\n" \
"- " \
"${@.FAMS[i].FAMILY.CHILDREN[j].INDIV.NAME}\n" \
"!INCREMENT j\n" \
"!END\n" \
"
\n" \
"!ENDIF\n" \
"!INCREMENT i\n" \
"!END\n" \
"INDEX
\n" \
"!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" \
"
\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" \
"
\n" \
"!INCREMENT j\n" \
"!END\n" \
"
\n" \
"!INCREMENT i\n" \
"!END\n" \
"!ENDIF\n" \
"!INCLUDE @.inc\n" \
"\n" \
"\n";
char *individual_template = NULL;
char *index_template =
"\n\nIndex of Persons\n\n" \
"\nIndex of Persons
\n\n" \
"!WHILE @\n" \
"!IF @.TITLE\n" \
"${@.NAME} (${@.TITLE})\n" \
"!ELSE\n" \
"${@.NAME}\n" \
"!ENDIF\n" \
" (${@.BIRTH.DATE} - ${@.DEATH.DATE})
\n" \
"!NEXT\n" \
"!END\n" \
"
\n\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
} 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;
struct individual_record *root;
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;
char *url;
} current_value;
char current_url[FILENAME_MAX+1];
int doing_index;
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 construct_url(char *dest, struct individual_record *indiv);
void output_individual(struct individual_record *rt)
{
FILE *ofile;
char path[FILENAME_MAX+1];
char url[FILENAME_MAX+1];
if(max_per_directory) {
sprintf(path, "D%07d",
rt->serial / max_per_directory);
mkdir(path, 0777);
strcat(path, "/");
} else {
sprintf(path, "");
}
sprintf(url, file_template, rt->xref);
strcat(path, url);
if((ofile = fopen(path, "w")) == NULL) {
fprintf(stderr, "Failed to create individual file %s\n", path);
return;
}
#ifdef MSDOS
fprintf(stderr, "Created %s\n", path);
#endif
template = template_start = individual_template;
root = rt;
interpret(ofile);
fclose(ofile);
}
void output_index(struct individual_record *rt)
{
FILE *ofile;
char path[FILENAME_MAX+1];
sprintf(path, file_template, "INDEX");
if((ofile = fopen(path, "w")) == NULL) {
fprintf(stderr, "Failed to create index file %s\n", path);
return;
}
template = template_start = index_template;
root = rt;
doing_index = 1;
interpret(ofile);
doing_index = 0;
fclose(ofile);
}
/*
* Interpret the template stored in "template", outputting results
* to "ofile". The individual "root" is the starting point for
* the interpretation of variables.
*/
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.indiv = root;
current_type = T_INDIV;
}
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;
}
}
if(*template == '.') {
template++;
continue;
} else if(*template == '[' || *template == '}')
continue;
else
return;
/*
* An ampersand means turn the current individual into a URL
*/
case '&':
template++;
if(!skipping) {
if(current_type == T_INDIV) {
current_type = T_URL;
construct_url(current_url, current_value.indiv);
current_value.url = current_url;
} else
output_error("Can only make a URL from an individual\n");
}
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;
}
}
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, "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");
}
}
/*
* 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 && root)
root = root->next;
}
} 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 = root->xref;
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();
b->name = strdup(name);
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" : "index",
line, msg);
}
void construct_url(char *dest, struct individual_record *indiv)
{
char url[FILENAME_MAX+1];
*dest = '\0';
if(max_per_directory) {
if(!doing_index)
sprintf(dest, "../");
sprintf(url, "D%07d/", indiv->serial / max_per_directory);
} else {
sprintf(url, "");
}
strcat(dest, url);
sprintf(url, url_template, indiv->xref);
strcat(dest, url);
}