home *** CD-ROM | disk | FTP | other *** search
/ The Best Internet Programs / BESTINTERNET.bin / latest / ged2ht20 / output.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-04-08  |  25.8 KB  |  1,074 lines

  1. /*
  2.  * HTML Output Routines
  3.  */
  4.  
  5. #include <stdlib.h>
  6. #include <stdio.h>
  7. #include <string.h>
  8.  
  9. #include "tags.h"
  10. #include "node.h"
  11. #include "database.h"
  12. #include "output.h"
  13.  
  14. #ifndef FILENAME_MAX
  15. #define FILENAME_MAX 1024
  16. #endif
  17.  
  18. char *url_template="%s.html";
  19. char *file_template="%s.html";
  20.  
  21. #ifdef MSDOS
  22. int max_per_directory = 100;
  23. #else
  24. int max_per_directory = 0;
  25. #endif
  26.  
  27. /*
  28.  * I don't like repeating these two templates, which are the same
  29.  * except for "INDEX" versus "../INDEX", but I am tired of working on this
  30.  * right now and I want to get something out that works right.
  31.  */
  32.  
  33. char *individual_template_subdir =
  34.   "<HTML>\n" \
  35.   "<HEAD>\n" \
  36.   "<TITLE>${@.XREF}: ${@.NAME}</TITLE>\n" \
  37.   "</HEAD>\n" \
  38.   "<BODY>\n"
  39.   "<H1>${@.NAME}</H1>\n" \
  40.   "!INCLUDE @.img\n" \
  41.   "!IF @.EVENT\n" \
  42.     "<UL>\n" \
  43.     "!RESET i\n" \
  44.     "!WHILE @.EVENT[i]\n" \
  45.       "<LI><EM>${@.EVENT[i].TAG}</EM>: ${@.EVENT[i].DATE}, ${@.EVENT[i].PLACE.NAME}\n" \
  46.       "!INCREMENT i\n" \
  47.     "!END\n" \
  48.     "</UL>\n" \
  49.    "!ENDIF\n" \
  50.    "!IF @.FATHER\n" \
  51.      "<EM>Father:</EM> <A HREF=\"${@.FATHER.&}\">${@.FATHER.NAME}</A><BR>\n" \
  52.   "!ENDIF\n" \
  53.   "!IF @.MOTHER\n" \
  54.     "<EM>Mother:</EM> <A HREF=\"${@.MOTHER.&}\">${@.MOTHER.NAME}</A><BR>\n" \
  55.   "!ENDIF\n" \
  56.   "<BR>\n" \
  57.   "!RESET i\n" \
  58.   "!WHILE @.FAMS[i]\n" \
  59.     "</EM>Family ${i}</EM>:\n" \
  60.     "!IF @.ISMALE\n" \
  61.       "!IF @.FAMS[i].FAMILY.WIFE\n" \
  62.         "<A HREF=\"${@.FAMS[i].FAMILY.WIFE.&}\">${@.FAMS[i].FAMILY.WIFE.NAME}</A>\n" \
  63.       "!ENDIF\n" \
  64.     "!ENDIF\n"
  65.     "!IF @.ISFEMALE\n" \
  66.       "!IF @.FAMS[i].FAMILY.HUSBAND\n" \
  67.         "<A HREF=\"${@.FAMS[i].FAMILY.HUSBAND.&}\">${@.FAMS[i].FAMILY.HUSBAND.NAME}</A>\n" \
  68.       "!ENDIF\n" \
  69.     "!ENDIF\n" \
  70.     "!IF @.FAMS[i].FAMILY.EVENT\n" \
  71.       "<UL>\n" \
  72.       "!RESET j\n" \
  73.       "!WHILE @.FAMS[i].FAMILY.EVENT[j]\n" \
  74.         "<LI><EM>${@.FAMS[i].FAMILY.EVENT[j].TAG}</EM>: " \
  75.         "${@.FAMS[i].FAMILY.EVENT[j].DATE}, ${@.FAMS[i].FAMILY.EVENT[j].PLACE.NAME}\n" \
  76.         "!INCREMENT j\n" \
  77.       "!END\n" \
  78.       "</UL>\n" \
  79.     "!ELSE\n" \
  80.       "<BR>\n" \
  81.     "!ENDIF\n" \
  82.     "!IF @.FAMS[i].FAMILY.CHILDREN\n" \
  83.       "<OL>\n" \
  84.       "!RESET j\n" \
  85.       "!WHILE @.FAMS[i].FAMILY.CHILDREN[j]\n" \
  86.         "<LI><A HREF=\"${@.FAMS[i].FAMILY.CHILDREN[j].INDIV.&}\">" \
  87.         "${@.FAMS[i].FAMILY.CHILDREN[j].INDIV.NAME}</A>\n" \
  88.         "!INCREMENT j\n" \
  89.       "!END\n" \
  90.       "</OL>\n" \
  91.     "!ENDIF\n" \
  92.     "!INCREMENT i\n" \
  93.   "!END\n" \
  94.   "<A HREF=\"../INDEX.html\">INDEX</A><BR><BR>\n" \
  95.   "!IF @.NOTE\n" \
  96.     "<H2>Notes</H2>\n" \
  97.     "!RESET i\n" \
  98.     "!WHILE @.NOTE[i]\n" \
  99.       "${@.NOTE[i].TEXT}\n" \
  100.       "!RESET j\n" \
  101.       "!WHILE @.NOTE[i].CONT[j]\n" \
  102.         "${@.NOTE[i].CONT[j].TEXT}\n" \
  103.         "<BR>\n" \
  104.         "!INCREMENT j\n" \
  105.       "!END\n" \
  106.       "<BR>\n" \
  107.       "!INCREMENT i\n" \
  108.     "!END\n" \
  109.   "!ENDIF\n" \
  110.   "<BR>\n" \
  111.   "!IF @.SOURCE\n" \
  112.     "<H2>Sources</H2>\n" \
  113.     "!RESET i\n" \
  114.     "!WHILE @.SOURCE[i]\n" \
  115.       "${@.SOURCE[i].SOURCE.TEXT}\n" \
  116.       "!RESET j\n" \
  117.       "!WHILE @.SOURCE[i].SOURCE.CONT[j]\n" \
  118.         "${@.SOURCE[i].SOURCE.CONT[j].TEXT}\n" \
  119.         "<BR>\n" \
  120.         "!INCREMENT j\n" \
  121.       "!END\n" \
  122.       "<BR>\n" \
  123.       "!INCREMENT i\n" \
  124.     "!END\n" \
  125.   "!ENDIF\n" \
  126.   "!INCLUDE @.inc\n" \
  127.   "</BODY>\n" \
  128.   "</HTML>\n";
  129.  
  130. char *individual_template_nosubdir =
  131.   "<HTML>\n" \
  132.   "<HEAD>\n" \
  133.   "<TITLE>${@.XREF}: ${@.NAME}</TITLE>\n" \
  134.   "</HEAD>\n" \
  135.   "<BODY>\n"
  136.   "<H1>${@.NAME}</H1>\n" \
  137.   "!INCLUDE @.img\n" \
  138.   "!IF @.EVENT\n" \
  139.     "<UL>\n" \
  140.     "!RESET i\n" \
  141.     "!WHILE @.EVENT[i]\n" \
  142.       "<LI><EM>${@.EVENT[i].TAG}</EM>: ${@.EVENT[i].DATE}, ${@.EVENT[i].PLACE.NAME}\n" \
  143.       "!INCREMENT i\n" \
  144.     "!END\n" \
  145.     "</UL>\n" \
  146.    "!ENDIF\n" \
  147.    "!IF @.FATHER\n" \
  148.      "<EM>Father:</EM> <A HREF=\"${@.FATHER.&}\">${@.FATHER.NAME}</A><BR>\n" \
  149.   "!ENDIF\n" \
  150.   "!IF @.MOTHER\n" \
  151.     "<EM>Mother:</EM> <A HREF=\"${@.MOTHER.&}\">${@.MOTHER.NAME}</A><BR>\n" \
  152.   "!ENDIF\n" \
  153.   "<BR>\n" \
  154.   "!RESET i\n" \
  155.   "!WHILE @.FAMS[i]\n" \
  156.     "</EM>Family ${i}</EM>:\n" \
  157.     "!IF @.ISMALE\n" \
  158.       "!IF @.FAMS[i].FAMILY.WIFE\n" \
  159.         "<A HREF=\"${@.FAMS[i].FAMILY.WIFE.&}\">${@.FAMS[i].FAMILY.WIFE.NAME}</A>\n" \
  160.       "!ENDIF\n" \
  161.     "!ENDIF\n"
  162.     "!IF @.ISFEMALE\n" \
  163.       "!IF @.FAMS[i].FAMILY.HUSBAND\n" \
  164.         "<A HREF=\"${@.FAMS[i].FAMILY.HUSBAND.&}\">${@.FAMS[i].FAMILY.HUSBAND.NAME}</A>\n" \
  165.       "!ENDIF\n" \
  166.     "!ENDIF\n" \
  167.     "!IF @.FAMS[i].FAMILY.EVENT\n" \
  168.       "<UL>\n" \
  169.       "!RESET j\n" \
  170.       "!WHILE @.FAMS[i].FAMILY.EVENT[j]\n" \
  171.         "<LI><EM>${@.FAMS[i].FAMILY.EVENT[j].TAG}</EM>: " \
  172.         "${@.FAMS[i].FAMILY.EVENT[j].DATE}, ${@.FAMS[i].FAMILY.EVENT[j].PLACE.NAME}\n" \
  173.         "!INCREMENT j\n" \
  174.       "!END\n" \
  175.       "</UL>\n" \
  176.     "!ELSE\n" \
  177.       "<BR>\n" \
  178.     "!ENDIF\n" \
  179.     "!IF @.FAMS[i].FAMILY.CHILDREN\n" \
  180.       "<OL>\n" \
  181.       "!RESET j\n" \
  182.       "!WHILE @.FAMS[i].FAMILY.CHILDREN[j]\n" \
  183.         "<LI><A HREF=\"${@.FAMS[i].FAMILY.CHILDREN[j].INDIV.&}\">" \
  184.         "${@.FAMS[i].FAMILY.CHILDREN[j].INDIV.NAME}</A>\n" \
  185.         "!INCREMENT j\n" \
  186.       "!END\n" \
  187.       "</OL>\n" \
  188.     "!ENDIF\n" \
  189.     "!INCREMENT i\n" \
  190.   "!END\n" \
  191.   "<A HREF=\"INDEX.html\">INDEX</A><BR><BR>\n" \
  192.   "!IF @.NOTE\n" \
  193.     "<H2>Notes</H2>\n" \
  194.     "!RESET i\n" \
  195.     "!WHILE @.NOTE[i]\n" \
  196.       "${@.NOTE[i].TEXT}\n" \
  197.       "!RESET j\n" \
  198.       "!WHILE @.NOTE[i].CONT[j]\n" \
  199.         "${@.NOTE[i].CONT[j].TEXT}\n" \
  200.         "<BR>\n" \
  201.         "!INCREMENT j\n" \
  202.       "!END\n" \
  203.       "<BR>\n" \
  204.       "!INCREMENT i\n" \
  205.     "!END\n" \
  206.   "!ENDIF\n" \
  207.   "<BR>\n" \
  208.   "!IF @.SOURCE\n" \
  209.     "<H2>Sources</H2>\n" \
  210.     "!RESET i\n" \
  211.     "!WHILE @.SOURCE[i]\n" \
  212.       "${@.SOURCE[i].SOURCE.TEXT}\n" \
  213.       "!RESET j\n" \
  214.       "!WHILE @.SOURCE[i].SOURCE.CONT[j]\n" \
  215.         "${@.SOURCE[i].SOURCE.CONT[j].TEXT}\n" \
  216.         "<BR>\n" \
  217.         "!INCREMENT j\n" \
  218.       "!END\n" \
  219.       "<BR>\n" \
  220.       "!INCREMENT i\n" \
  221.     "!END\n" \
  222.   "!ENDIF\n" \
  223.   "!INCLUDE @.inc\n" \
  224.   "</BODY>\n" \
  225.   "</HTML>\n";
  226.  
  227. char *individual_template = NULL;
  228.  
  229. char *index_template =
  230.   "<HTML>\n<HEAD>\n<TITLE>Index of Persons</TITLE>\n</HEAD>\n" \
  231.   "<BODY>\n<H1>Index of Persons</H1>\n<P>\n" \
  232.   "!WHILE @\n" \
  233.     "!IF @.TITLE\n" \
  234.       "<A HREF=\"${@.&}\">${@.NAME} (${@.TITLE})</A>\n" \
  235.     "!ELSE\n" \
  236.       "<A HREF=\"${@.&}\">${@.NAME}</A>\n" \
  237.     "!ENDIF\n" \
  238.     " (${@.BIRTH.DATE} - ${@.DEATH.DATE})<BR>\n" \
  239.     "!NEXT\n" \
  240.   "!END\n" \
  241.   "</P>\n</BODY>\n</HTML>\n";
  242.  
  243. /*
  244.  * Record types
  245.  */
  246.  
  247. typedef enum {
  248.   T_INTEGER, T_STRING, T_PLACE, T_NOTE, T_XREF, T_SOURCE,
  249.   T_FAMEVENT, T_EVENT, T_INDIV, T_FAMILY, T_CONT, T_URL
  250. } record_type;
  251.  
  252. /*
  253.  * Interpreter state
  254.  */
  255.  
  256. int skipping;
  257.  
  258. #define CONTROL_STACK_SIZE 100
  259. char *while_stack[CONTROL_STACK_SIZE];
  260. int while_stack_top;
  261. int if_stack[CONTROL_STACK_SIZE];
  262. int if_stack_top;
  263.  
  264. struct individual_record *root;
  265.  
  266. char *template;
  267. char *template_start;
  268. int current_index;
  269. record_type current_type = T_INTEGER;
  270. union value {
  271.   int integer;
  272.   char *string;
  273.   struct place_structure *place;
  274.   struct note_structure *note;
  275.   struct xref *xref;
  276.   struct event_structure *event;
  277.   struct individual_record *indiv;
  278.   struct family_record *family;
  279.   struct continuation *cont;
  280.   struct source_record *source;
  281.   char *url;
  282. } current_value;
  283.  
  284. char current_url[FILENAME_MAX+1];
  285. int doing_index;
  286.  
  287. void interpret(FILE *ofile);
  288. void variable(FILE *ofile);
  289. void command(FILE *ofile);
  290. void collect_identifier(char **ret);
  291. void skip_white_space();
  292. void output_error(char *msg);
  293.  
  294. void set_variable(char *name, int value);
  295. int get_variable(char *name);
  296.  
  297. void family_select(char *field);
  298. void indiv_select(char *field);
  299. void event_select(char *field);
  300. void note_select(char *field);
  301. void source_select(char *field);
  302. void cont_select(char *field);
  303. void place_select(char *field);
  304. void xref_select(char *field);
  305.  
  306. void construct_url(char *dest, struct individual_record *indiv);
  307.  
  308. void output_individual(struct individual_record *rt)
  309. {
  310.   FILE *ofile;
  311.   char path[FILENAME_MAX+1];
  312.   char url[FILENAME_MAX+1];
  313.  
  314.   if(max_per_directory) {
  315.     sprintf(path, "D%07d",
  316.         rt->serial / max_per_directory);
  317.     mkdir(path, 0777);
  318.     strcat(path, "/");
  319.   } else {
  320.     sprintf(path, "");
  321.   }
  322.   sprintf(url, file_template, rt->xref);
  323.   strcat(path, url);
  324.   if((ofile = fopen(path, "w")) == NULL) {
  325.     fprintf(stderr, "Failed to create individual file %s\n", path);
  326.     return;
  327.   }
  328. #ifdef MSDOS
  329.   fprintf(stderr, "Created %s\n", path);
  330. #endif
  331.   template = template_start = individual_template;
  332.   root = rt;
  333.   interpret(ofile);
  334.   fclose(ofile);
  335. }
  336.  
  337. void output_index(struct individual_record *rt)
  338. {
  339.   FILE *ofile;
  340.   char path[FILENAME_MAX+1];
  341.  
  342.   sprintf(path, file_template, "INDEX");
  343.   if((ofile = fopen(path, "w")) == NULL) {
  344.     fprintf(stderr, "Failed to create index file %s\n", path);
  345.     return;
  346.   }
  347.   template = template_start = index_template;
  348.   root = rt;
  349.   doing_index = 1;
  350.   interpret(ofile);
  351.   doing_index = 0;
  352.   fclose(ofile);
  353. }
  354.  
  355. /*
  356.  * Interpret the template stored in "template", outputting results
  357.  * to "ofile".  The individual "root" is the starting point for
  358.  * the interpretation of variables.
  359.  */
  360.  
  361. void interpret(FILE *ofile)
  362. {
  363.   char c;
  364.   int start_of_line = 1;
  365.  
  366.   while((c = *template++) != '\0') {
  367.     switch(c) {
  368.     case '\n':
  369.       start_of_line = 1;
  370.       /* fall through intentional */
  371.     case ' ':
  372.     case '\t':
  373.       if(!skipping)
  374.     fputc(c, ofile);
  375.       continue;
  376.     case '!':
  377.       if(!start_of_line) {
  378.     if(!skipping)
  379.       fputc(c, ofile);
  380.     continue;
  381.       }
  382.       template--;
  383.       command(ofile);
  384.       continue;
  385.     case '$':
  386.       start_of_line = 0;
  387.       variable(ofile);
  388.       if(!skipping) {
  389.     if(current_type == T_STRING) {
  390.       fprintf(ofile, "%s", current_value.string);
  391.     } else if(current_type == T_URL) {
  392.       fprintf(ofile, current_value.url);
  393.     } else if(current_type == T_INTEGER) {
  394.       /* Integer variables start from 1 */
  395.       fprintf(ofile, "%d", current_value.integer + 1);
  396.     } else {
  397.       output_error("Attempt to output something not an integer or string");
  398.     }
  399.       }
  400.       continue;
  401.     default:
  402.       start_of_line = 0;
  403.       if(!skipping)
  404.     fputc(c, ofile);
  405.       continue;
  406.     }
  407.   }
  408. }
  409.  
  410. /*
  411.  * After having seen the initial $, interpret a simple or compound variable. 
  412.  */
  413.  
  414. void variable(FILE *ofile)
  415. {
  416.   char c, *selector;
  417.   int braces = 0;
  418.   int first = 1;
  419.   /*
  420.    * $$ means output a single $
  421.    */
  422.   if(*template == '$') {
  423.     if(!skipping)
  424.       fputc(*template++, ofile);
  425.     return;
  426.   }
  427.   while((c = *template) != '\0') {
  428.     switch(c) {
  429.       /*
  430.        * An '@' indicates the current individual
  431.        */
  432.     case '@':
  433.       first = 0;
  434.       template++;
  435.       if(!skipping) {
  436.     current_value.indiv = root;
  437.     current_type = T_INDIV;
  438.       }
  439.       if(*template == '.') {
  440.     template++;
  441.     continue;
  442.       } else if(*template == '[') 
  443.     continue;
  444.       else
  445.     return;
  446.       /*
  447.        * Braces in variables simply serve as delimiters
  448.        */
  449.     case '{':
  450.       template++;
  451.       braces++;
  452.       continue;
  453.     case '}':
  454.       template++;
  455.       if(braces) {
  456.     braces--;
  457.     if(braces)
  458.       continue;
  459.     else
  460.       return;
  461.       } else {
  462.     if(!skipping)
  463.       fputc(c, ofile);
  464.     return;
  465.       }
  466.       /*
  467.        * Brackets indicate integer subscripts
  468.        */
  469.     case '[':
  470.       first = 0;
  471.       template++;
  472.       /*
  473.        * Integer constant
  474.        */
  475.       if(*template >= '0' && *template <= '9') {
  476.     /*
  477.      * Convert integer value and leave on top of stack
  478.      * (if not skipping)
  479.      */
  480.       }
  481.       /*
  482.        * Variable subscript
  483.        */
  484.       else {
  485.     record_type previous_type;
  486.     union value previous_value;
  487.     
  488.     previous_type = current_type;
  489.     previous_value = current_value;
  490.     variable(ofile);
  491.     if(!skipping && current_type != T_INTEGER)
  492.       output_error("Subscript is not an integer variable");
  493.     else {
  494.       current_index = current_value.integer;
  495.       current_type = previous_type;
  496.       current_value = previous_value;
  497.     }
  498.     if(*template != ']') {
  499.       output_error("Subscript fails to end with ']'");
  500.     } else {
  501.       template++;
  502.     }
  503.       }
  504.       if(!skipping) {
  505.     switch(current_type) {
  506.     case T_INTEGER:
  507.     case T_STRING:
  508.       output_error("Can't apply subscript to an integer or string");
  509.       break;
  510.     case T_PLACE:
  511.       while(current_index--) place_select("NEXT");
  512.       break;
  513.     case T_NOTE:
  514.       while(current_index--) note_select("NEXT");
  515.       break;
  516.     case T_SOURCE:
  517.       while(current_index--) source_select("NEXT");
  518.       break;
  519.     case T_CONT:
  520.       while(current_index--) cont_select("NEXT");
  521.       break;
  522.     case T_EVENT:
  523.       while(current_index--) event_select("NEXT");
  524.       break;
  525.     case T_INDIV:
  526.       while(current_index--) indiv_select("NEXT");
  527.       break;
  528.     case T_FAMILY:
  529.       while(current_index--) family_select("NEXT");
  530.       break;
  531.     case T_XREF:
  532.       while(current_index--) xref_select("NEXT");
  533.       break;
  534.     }
  535.       }
  536.       if(*template == '.') {
  537.     template++;
  538.     continue;
  539.       } else if(*template == '[' || *template == '}') 
  540.     continue;
  541.       else
  542.     return;
  543.       /*
  544.        * An ampersand means turn the current individual into a URL
  545.        */
  546.     case '&':
  547.       template++;
  548.       if(!skipping) {
  549.     if(current_type == T_INDIV) {
  550.       current_type = T_URL;
  551.       construct_url(current_url, current_value.indiv);
  552.       current_value.url = current_url;
  553.     } else
  554.       output_error("Can only make a URL from an individual\n");
  555.       }
  556.       if(*template == '}')
  557.     continue;
  558.       else
  559.     return;
  560.       /*
  561.        * Alphabetic characters indicate selector name.
  562.        * Anything else is a delimiter.
  563.        */
  564.     default:
  565.       collect_identifier(&selector);
  566.       if(*selector == '\0')
  567.     return;
  568.       if(first) {
  569.     if(!skipping) {
  570.       current_value.integer = get_variable(selector);
  571.       current_type = T_INTEGER;
  572.     }
  573.     if(*template == '}')
  574.       continue;
  575.     else
  576.       return;
  577.       }
  578.       if(!skipping) {
  579.     switch(current_type) {
  580.     case T_INTEGER:
  581.     case T_STRING:
  582.       output_error("Can't apply selector to an integer or string");
  583.       break;
  584.     case T_PLACE:
  585.       place_select(selector);
  586.       break;
  587.     case T_NOTE:
  588.       note_select(selector);
  589.       break;
  590.     case T_SOURCE:
  591.       source_select(selector);
  592.       break;
  593.     case T_CONT:
  594.       cont_select(selector);
  595.       break;
  596.     case T_EVENT:
  597.       event_select(selector);
  598.       break;
  599.     case T_INDIV:
  600.       indiv_select(selector);
  601.       break;
  602.     case T_FAMILY:
  603.       family_select(selector);
  604.       break;
  605.     case T_XREF:
  606.       xref_select(selector);
  607.       break;
  608.     }
  609.       }
  610.       if(*template == '.') {
  611.     template++;
  612.     continue;
  613.       } else if(*template == '[' || *template == '}') 
  614.     continue;
  615.       else
  616.     return;
  617.     }
  618.   }
  619. }
  620.  
  621. /*
  622.  * Record field selection operations
  623.  */
  624.  
  625. void family_select(char *field)
  626. {
  627.   struct family_record *r = current_value.family;
  628.   if(!strcmp(field, "XREF")) {
  629.     current_type = T_STRING;
  630.     current_value.string = (r && r->xref) ? r->xref: "";
  631.   } else if(!strcmp(field, "REFN")) {
  632.     current_type = T_STRING;
  633.     current_value.string = (r && r->refn) ? r->refn: "";
  634.   } else if(!strcmp(field, "HUSBAND")) {
  635.     current_type = T_INDIV;
  636.     current_value.indiv =
  637.       (r && r->husband) ? r->husband->pointer.individual : NULL;
  638.   } else if(!strcmp(field, "WIFE")) {
  639.     current_type = T_INDIV;
  640.     current_value.indiv =
  641.       (r && r->wife) ? r->wife->pointer.individual: NULL;
  642.   } else if(!strcmp(field, "CHILDREN")) {
  643.     current_type = T_XREF;
  644.     current_value.xref =
  645.       (r && r->children) ? r->children: NULL;
  646.   } else if(!strcmp(field, "NOTE")) {
  647.     current_type = T_NOTE;
  648.     current_value.note = r ? r->notes: NULL;
  649.   } else if(!strcmp(field, "EVENT")) {
  650.     current_type = T_EVENT;
  651.     current_value.event = r ? r->events: NULL;
  652.   } else if(!strcmp(field, "NEXT")) {
  653.     current_value.family = r ? r->next: NULL;
  654.   } else {
  655.     output_error("Unrecognized selector applied to family record");
  656.   }
  657. }
  658.  
  659. void indiv_select(char *field)
  660. {
  661.   struct individual_record *r = current_value.indiv;
  662.   if(!strcmp(field, "XREF")) {
  663.     current_type = T_STRING;
  664.     current_value.string = (r && r->xref) ? r->xref: "";
  665.   } else if(!strcmp(field, "NAME")) {
  666.     current_type = T_STRING;
  667.     current_value.string = (r && r->personal_name) ?
  668.       r->personal_name->name: "???";
  669.   } else if(!strcmp(field, "TITLE")) {
  670.     current_type = T_STRING;
  671.     current_value.string = (r && r->title) ? r->title: "";
  672.   } else if(!strcmp(field, "ISMALE")) {
  673.     current_type = T_INTEGER;
  674.     current_value.integer = (r && r->sex == 'M');
  675.   } else if(!strcmp(field, "ISFEMALE")) {
  676.     current_type = T_INTEGER;
  677.     current_value.integer = (r && r->sex == 'F');
  678.   } else if(!strcmp(field, "REFN")) {
  679.     current_type = T_STRING;
  680.     current_value.string = (r && r->refn) ? r->refn: "";
  681.   } else if(!strcmp(field, "RFN")) {
  682.     current_type = T_STRING;
  683.     current_value.string = (r && r->rfn) ? r->rfn: "";
  684.   } else if(!strcmp(field, "AFN")) {
  685.     current_type = T_STRING;
  686.     current_value.string = (r && r->afn) ? r->afn: "";
  687.   } else if(!strcmp(field, "FAMC")) {
  688.     current_type = T_XREF;
  689.     current_value.xref = r ? r->famc: NULL;
  690.   } else if(!strcmp(field, "FAMS")) {
  691.     current_type = T_XREF;
  692.     current_value.xref = r ? r->fams: NULL;
  693.   } else if(!strcmp(field, "FATHER")) {
  694.     current_value.indiv =
  695.       (r && r->famc && r->famc->pointer.family
  696.        && r->famc->pointer.family->husband)
  697.     ? r->famc->pointer.family->husband->pointer.individual: NULL;
  698.   } else if(!strcmp(field, "MOTHER")) {
  699.     current_value.indiv =
  700.       (r && r->famc && r->famc->pointer.family
  701.        && r->famc->pointer.family->wife)
  702.     ? r->famc->pointer.family->wife->pointer.individual: NULL;
  703.   } else if(!strcmp(field, "NOTE")) {
  704.     current_type = T_NOTE;
  705.     current_value.note = r ? r->notes: NULL;
  706.   } else if(!strcmp(field, "SOURCE")) {
  707.     current_type = T_XREF;
  708.     current_value.xref =
  709.       (r && r->sources) ? r->sources: NULL;
  710.   } else if(!strcmp(field, "EVENT")) {
  711.     current_type = T_EVENT;
  712.     current_value.event = r ? r->events: NULL;
  713.   } else if(!strcmp(field, "BIRTH")) {
  714.     struct event_structure *ep;
  715.     current_type = T_EVENT;
  716.     current_value.event = NULL;
  717.     for(ep = r->events; ep != NULL; ep = ep->next) {
  718.       if(ep->tag->value == BIRT)
  719.     current_value.event = ep;
  720.     }
  721.   } else if(!strcmp(field, "DEATH")) {
  722.     struct event_structure *ep;
  723.     current_type = T_EVENT;
  724.     current_value.event = NULL;
  725.     for(ep = r->events; ep != NULL; ep = ep->next) {
  726.       if(ep->tag->value == DEAT)
  727.     current_value.event = ep;
  728.     }
  729.   } else if(!strcmp(field, "NEXT")) {
  730.     current_value.indiv = r ? r->next: NULL;
  731.   } else {
  732.     output_error("Unrecognized selector applied to individual record");
  733.   }
  734. }
  735.  
  736. void event_select(char *field)
  737. {
  738.   struct event_structure *r = current_value.event;
  739.   if(!strcmp(field, "TAG")) {
  740.     current_type = T_STRING;
  741.     current_value.string = (r && r->tag) ? r->tag->pname[default_language]: "";
  742.   } else if(!strcmp(field, "DATE")) {
  743.     current_type = T_STRING;
  744.     current_value.string = (r && r->date) ? r->date: "";
  745.   } else if(!strcmp(field, "PLACE")) {
  746.     current_type = T_PLACE;
  747.     current_value.place = r ? r->place: NULL;
  748.   } else if(!strcmp(field, "NEXT")) {
  749.     current_value.event = r ? r->next: NULL;
  750.   } else {
  751.     output_error("Unrecognized selector applied to event structure");
  752.   }
  753. }
  754.  
  755. void note_select(char *field)
  756. {
  757.   struct note_structure *r = current_value.note;
  758.  
  759.   if(!strcmp(field, "XREF")) {
  760.     current_type = T_STRING;
  761.     current_value.string = (r && r->xref) ? r->xref: "";
  762.   } else if(!strcmp(field, "TEXT")) {
  763.     current_type = T_STRING;
  764.     current_value.string = (r && r->text) ? r->text: "";
  765.   } else if(!strcmp(field, "NEXT")) {
  766.     current_value.note = r ? r->next : NULL;
  767.   } else if(!strcmp(field, "CONT")) {
  768.     current_type = T_CONT;
  769.     current_value.cont = r ? r->cont: NULL;
  770.   } else {
  771.     output_error("Unrecognized selector applied to note structure");
  772.   }
  773. }
  774.  
  775. void source_select(char *field)
  776. {
  777.   struct source_record *r = current_value.source;
  778.  
  779.   if(!strcmp(field, "XREF")) {
  780.     current_type = T_STRING;
  781.     current_value.string = (r && r->xref) ? r->xref: "";
  782.   } else if(!strcmp(field, "TEXT")) {
  783.     current_type = T_STRING;
  784.     current_value.string = (r && r->text) ? r->text: "";
  785.   } else if(!strcmp(field, "CONT")) {
  786.     current_type = T_CONT;
  787.     current_value.cont = r ? r->cont: NULL;
  788.   } else {
  789.     output_error("Unrecognized selector applied to source record");
  790.   }
  791. }
  792.  
  793. void cont_select(char *field)
  794. {
  795.   struct continuation *c = current_value.cont;
  796.  
  797.   if(!strcmp(field, "TEXT")) {
  798.     current_type = T_STRING;
  799.     current_value.string = (c && c->text) ? c->text: "";
  800.   } else if(!strcmp(field, "NEXT")) {
  801.     current_value.cont = c ? c->next: NULL;
  802.   } else {
  803.     output_error("Unrecognized selector applied to continuation structure");
  804.   }
  805. }
  806.  
  807. void place_select(char *field)
  808. {
  809.   struct place_structure *r = current_value.place;
  810.   if(!strcmp(field, "NAME")) {
  811.     current_type = T_STRING;
  812.     current_value.string = (r && r->name) ? r->name: "";
  813.   } else if(!strcmp(field, "NOTE")) {
  814.     current_type = T_NOTE;
  815.     current_value.note = r ? r->notes: NULL;
  816.   } else {
  817.     output_error("Unrecognized selector applied to place structure");
  818.   }
  819. }
  820.  
  821. void xref_select(char *field)
  822. {
  823.   struct xref *r = current_value.xref;
  824.   if(!strcmp(field, "INDIV")) {
  825.     current_type = T_INDIV;
  826.     current_value.indiv = r ? r->pointer.individual: NULL;
  827.   } else if(!strcmp(field, "FAMILY")) {
  828.     current_type = T_FAMILY;
  829.     current_value.family = r ? r->pointer.family: NULL;
  830.   } else if(!strcmp(field, "SOURCE")) {
  831.     current_type = T_SOURCE;
  832.     current_value.source = r ? r->pointer.source: NULL;
  833.   } else if(!strcmp(field, "NEXT")) {
  834.     current_value.xref = r ? r->next: NULL;
  835.   } else {
  836.     output_error("Unrecognized selector applied to cross-reference");
  837.   }
  838. }
  839.  
  840. /*
  841.  * Perform a control command.
  842.  */
  843.  
  844. void command(FILE *ofile)
  845. {
  846.   char *buf;
  847.   char *start = template++; 
  848.  
  849.   collect_identifier(&buf);
  850.   skip_white_space();
  851.   if(!strcmp(buf, "RESET")) {
  852.     collect_identifier(&buf);
  853.     if(*template == '\n')
  854.       template++;
  855.     else {
  856.       output_error("Newline expected");
  857.     }
  858.     set_variable(buf, 0);
  859.   } else if(!strcmp(buf, "INCREMENT")) {
  860.     collect_identifier(&buf);
  861.     if(*template == '\n')
  862.       template++;
  863.     else {
  864.       output_error("Newline expected");
  865.     }
  866.     set_variable(buf, get_variable(buf)+1);
  867.   } else if(!strcmp(buf, "IF")) {
  868.     variable(ofile);
  869.     if(*template == '\n') {
  870.       template++;
  871.       if(current_type == T_STRING) {
  872.     if(!strcmp(current_value.string, "")) {
  873.       skipping++;
  874.       if_stack[if_stack_top++] = 1;
  875.     } else {
  876.       if_stack[if_stack_top++] = 0;
  877.     }
  878.       } else {
  879.     if(!current_value.integer) {
  880.       skipping++;
  881.       if_stack[if_stack_top++] = 1;
  882.     } else {
  883.       if_stack[if_stack_top++] = 0;
  884.     }
  885.       }
  886.     } else {
  887.       output_error("Newline expected");
  888.     }
  889.   } else if(!strcmp(buf, "ELSE")) {
  890.     if(*template == '\n') {
  891.       template++;
  892.       if(if_stack[if_stack_top-1]) {
  893.     skipping--;
  894.     if_stack[if_stack_top-1] = 0;
  895.       } else {
  896.     skipping++;
  897.     if_stack[if_stack_top-1] = 1;
  898.       }
  899.     } else {
  900.       output_error("Newline expected");
  901.     }
  902.   } else if(!strcmp(buf, "ENDIF")) {
  903.     if(*template == '\n') {
  904.       template++;
  905.       if(if_stack[--if_stack_top])
  906.     skipping--;
  907.     } else {
  908.       output_error("Newline expected");
  909.     }
  910.   } else if(!strcmp(buf, "WHILE")) {
  911.     variable(ofile);
  912.     if(*template == '\n') {
  913.       template++;
  914.       while_stack[while_stack_top++] = start;
  915.       if(!skipping) {
  916.     if(current_type == T_STRING) {
  917.       if(!strcmp(current_value.string, ""))
  918.         skipping++;
  919.     } else {
  920.       if(!current_value.integer)
  921.         skipping++;
  922.     }
  923.       } else {
  924.     skipping++;
  925.       }
  926.     } else {
  927.       output_error("Newline expected");
  928.     }
  929.   } else if(!strcmp(buf, "END")) {
  930.     if(*template == '\n') {
  931.       template++;
  932.       if(skipping) {
  933.     skipping--;
  934.         if(while_stack_top)
  935.       while_stack_top--;
  936.       } else {
  937.         if(while_stack_top)
  938.       template = while_stack[--while_stack_top];
  939.       }
  940.     } else {
  941.       output_error("Newline expected");
  942.     }
  943.   } else if(!strcmp(buf, "NEXT")) {
  944.     if(*template == '\n') {
  945.       template++;
  946.       if(!skipping && root)
  947.     root = root->next;
  948.     }
  949.   } else if(!strcmp(buf, "INCLUDE")) {
  950.     char path[FILENAME_MAX+1], *pp;
  951.     FILE *incf;
  952.  
  953.     skip_white_space();
  954.     for(pp = path; pp - path <= FILENAME_MAX
  955.     && *template && *template != '\n'; ) {
  956.       if(*template == '@') {
  957.     template++;
  958.     if(*template == '@') {
  959.       template++;
  960.       *pp++ = '@';
  961.     } else {
  962.       char *id = root->xref;
  963.       while(*id && pp - path <= FILENAME_MAX)
  964.         *pp++ = *id++;
  965.     }
  966.       } else {
  967.     *pp++ = *template++;
  968.       }
  969.     }
  970.     *pp = '\0';
  971.     if((incf = fopen(path, "r")) != NULL) {
  972.       char c;
  973.       while((c = fgetc(incf)) != EOF)
  974.     fputc(c, ofile);
  975.       fclose(incf);
  976.     }
  977.   } else {
  978.     output_error("Unrecognized control command");
  979.   }
  980. }
  981.  
  982. /*
  983.  * Pick up the next identifier in the template.
  984.  */
  985.  
  986. #define IDENTIFIER_MAX 63
  987.  
  988. void collect_identifier(char **ret)
  989. {
  990.   static char id[IDENTIFIER_MAX+1];
  991.   char *ip, c;
  992.  
  993.   ip = id;
  994.   while(((c = *template) >= 'A' && c <= 'Z')
  995.     || (c >= 'a' && c <= 'z')) {
  996.     template++;
  997.     if(ip - id < IDENTIFIER_MAX)
  998.       *ip++ = c;
  999.   }
  1000.   *ip = '\0';
  1001.   *ret = id;
  1002. }
  1003.  
  1004. void skip_white_space()
  1005. {
  1006.   while(*template == ' ' || *template == '\t')
  1007.     template++;
  1008. }
  1009.  
  1010. struct binding {
  1011.   char *name;
  1012.   int value;
  1013.   struct binding *next;
  1014. } *environment;
  1015.  
  1016. void set_variable(char *name, int value)
  1017. {
  1018.   struct binding *b;
  1019.   for(b = environment; b != NULL; b = b->next) {
  1020.     if(!strcmp(name, b->name)) {
  1021.       b->value = value;
  1022.       return;
  1023.     }
  1024.   }
  1025.   if((b = malloc(sizeof(struct binding))) == NULL)
  1026.     out_of_memory();
  1027.   b->name = strdup(name);
  1028.   b->value = value;
  1029.   b->next = environment;
  1030.   environment = b;
  1031. }
  1032.  
  1033. int get_variable(char *name)
  1034. {
  1035.   struct binding *b;
  1036.   for(b = environment; b != NULL; b = b->next) {
  1037.     if(!strcmp(name, b->name))
  1038.       return(b->value);
  1039.   }
  1040.   set_variable(name, 0);
  1041.   return(0);
  1042. }
  1043.  
  1044. void output_error(char *msg)
  1045. {
  1046.   char *tp;
  1047.   int line = 1;
  1048.  
  1049.   for(tp = template_start; tp < template; tp++)
  1050.     if(*tp == '\n')
  1051.       line++;
  1052.   fprintf(stderr, "Output error: ");
  1053.   fprintf(stderr, "%s template line %d: %s\n",
  1054.       template_start == individual_template ? "individual" : "index",
  1055.       line, msg);
  1056. }
  1057.  
  1058. void construct_url(char *dest, struct individual_record *indiv)
  1059. {
  1060.   char url[FILENAME_MAX+1];
  1061.  
  1062.   *dest = '\0';
  1063.   if(max_per_directory) {
  1064.     if(!doing_index)
  1065.       sprintf(dest, "../");
  1066.     sprintf(url, "D%07d/", indiv->serial / max_per_directory);
  1067.   } else {
  1068.     sprintf(url, "");
  1069.   }
  1070.   strcat(dest, url);
  1071.   sprintf(url, url_template, indiv->xref);
  1072.   strcat(dest, url);
  1073. }
  1074.