home *** CD-ROM | disk | FTP | other *** search
/ DP Tool Club 24 / CD_ASCQ_24_0995.iso / vrac / ged2ht23.zip / OUTPUT.C < prev    next >
Text File  |  1995-07-14  |  43KB  |  1,648 lines

  1. /*
  2.  * Copyright (c) 1995 Eugene W. Stark
  3.  * All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  * 1. Redistributions of source code must retain the above copyright
  9.  *    notice, this list of conditions and the following disclaimer.
  10.  * 2. Redistributions in binary form must reproduce the above copyright
  11.  *    notice, this list of conditions and the following disclaimer in the
  12.  *    documentation and/or other materials provided with the distribution.
  13.  * 3. All advertising materials mentioning features or use of this software
  14.  *    must display the following acknowledgement:
  15.  *    This product includes software developed by Eugene W. Stark.
  16.  * 4. The name of the author may not be used to endorse or promote products
  17.  *    derived from this software without specific prior written permission.
  18.  * 5. No copying or redistribution in any form for commercial purposes is
  19.  *    permitted without specific prior written permission.
  20.  *
  21.  * THIS SOFTWARE IS PROVIDED BY EUGENE W. STARK (THE AUTHOR) ``AS IS'' AND
  22.  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  23.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  24.  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
  25.  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  26.  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  27.  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  28.  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  29.  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  30.  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  31.  * SUCH DAMAGE.
  32.  */
  33.  
  34. /*
  35.  * HTML Output Routines
  36.  */
  37.  
  38. #include <stdlib.h>
  39. #include <stdio.h>
  40. #include <string.h>
  41. #include <ctype.h>
  42.  
  43. #ifdef MSDOS
  44. #include <direct.h>
  45. #else
  46. #include <sys/stat.h>
  47. #endif
  48.  
  49. #ifndef HAVE_STRDUP
  50. char *strdup(const char *s);
  51. #endif
  52.  
  53. #include "tags.h"
  54. #include "node.h"
  55. #include "database.h"
  56. #include "output.h"
  57.  
  58. #ifndef FILENAME_MAX
  59. #define FILENAME_MAX 1024
  60. #endif
  61.  
  62.  
  63. #ifdef MSDOS
  64. char *file_template="%s.htm";
  65. int files_per_directory = 100;
  66. int indivs_per_directory = 1000;
  67. int indivs_per_file = 10;
  68. #else
  69. char *file_template="%s.html";
  70. int files_per_directory = 100;
  71. int indivs_per_directory = 100;
  72. int indivs_per_file = 0;
  73. #endif
  74.  
  75. int index_levels = 0;
  76. int pedigree_depth = 2;
  77.  
  78. /*
  79.  * The templates are split into several strings, because the
  80.  * "stone knives and bearskins" Microsoft compiler has a limit on
  81.  * the length of a string literal.
  82.  *
  83.  * The splitting is now used as a convenient way to substitute certain
  84.  * option-dependent portions of the template, such as "../INDEX.html" versus
  85.  * "INDEX.html"
  86.  */
  87.  
  88. char *individual_template_base[] =
  89. {
  90. /* 0 */
  91.   "<HTML>\n" \
  92.   "<HEAD>\n" \
  93.   "!IF @.TITLE\n" \
  94.     "<TITLE>${@.XREF}: ${@.NAME} (${@.TITLE})</TITLE>\n" \
  95.   "!ELSE\n" \
  96.     "<TITLE>${@.XREF}: ${@.NAME}</TITLE>\n" \
  97.   "!ENDIF\n" \
  98.   "</HEAD>\n" \
  99.   "<BODY>\n"
  100.   "!IF @.TITLE\n" \
  101.     "<H1><A NAME=\"${@.XREF}\">${@.NAME} (${@.TITLE})</A></H1>\n" \
  102.   "!ELSE\n" \
  103.     "<H1><A NAME=\"${@.XREF}\">${@.NAME}</A></H1>\n" \
  104.   "!ENDIF\n" \
  105.   "!INCLUDE @.img\n" \
  106.   "!IF @.EVENT\n" \
  107.     "<UL>\n" \
  108.     "!RESET i\n" \
  109.     "!WHILE @.EVENT[i]\n" \
  110.       "!IF @.EVENT[i].DATE\n" \
  111.         "!IF @.EVENT[i].PLACE\n" \
  112.           "<LI><EM>${@.EVENT[i].TAG}</EM>: ${@.EVENT[i].DATE}, ${@.EVENT[i].PLACE.NAME}\n" \
  113.         "!ELSE\n" \
  114.           "<LI><EM>${@.EVENT[i].TAG}</EM>: ${@.EVENT[i].DATE}\n" \
  115.         "!ENDIF\n" \
  116.       "!ELSE\n" \
  117.         "<LI><EM>${@.EVENT[i].TAG}</EM>: ${@.EVENT[i].PLACE.NAME}\n" \
  118.       "!ENDIF\n" \
  119.       "!INCREMENT i\n" \
  120.     "!END\n" \
  121.     "</UL>\n" \
  122.    "!ENDIF\n" \
  123.    "!IF @.FATHER\n" \
  124.      "<EM>Father:</EM> <A HREF=\"${@.FATHER.&}\">${@.FATHER.NAME}</A><BR>\n" \
  125.   "!ENDIF\n" \
  126.   "!IF @.MOTHER\n" \
  127.     "<EM>Mother:</EM> <A HREF=\"${@.MOTHER.&}\">${@.MOTHER.NAME}</A><BR>\n" \
  128.   "!ENDIF\n" \
  129.   "<BR>\n" \
  130.   "!RESET i\n" \
  131.   "!WHILE @.FAMS[i]\n" \
  132.     "<EM>Family ${i}</EM>:\n" \
  133.     "!IF @.ISMALE\n" \
  134.       "!IF @.FAMS[i].FAMILY.WIFE\n" \
  135.         "<A HREF=\"${@.FAMS[i].FAMILY.WIFE.&}\">${@.FAMS[i].FAMILY.WIFE.NAME}</A>\n" \
  136.       "!ENDIF\n" \
  137.     "!ENDIF\n"
  138.     "!IF @.ISFEMALE\n" \
  139.       "!IF @.FAMS[i].FAMILY.HUSBAND\n" \
  140.         "<A HREF=\"${@.FAMS[i].FAMILY.HUSBAND.&}\">${@.FAMS[i].FAMILY.HUSBAND.NAME}</A>\n" \
  141.       "!ENDIF\n" \
  142.     "!ENDIF\n" \
  143.     "!IF @.FAMS[i].FAMILY.EVENT\n" \
  144.       "<UL>\n" \
  145.       "!RESET j\n" \
  146.       "!WHILE @.FAMS[i].FAMILY.EVENT[j]\n" \
  147.     "!IF @.FAMS[i].FAMILY.EVENT[j].DATE\n" \
  148.       "!IF @.FAMS[i].FAMILY.EVENT[j].PLACE\n" \
  149.             "<LI><EM>${@.FAMS[i].FAMILY.EVENT[j].TAG}</EM>: ${@.FAMS[i].FAMILY.EVENT[j].DATE}, ${@.FAMS[i].FAMILY.EVENT[j].PLACE.NAME}\n" \
  150.           "!ELSE\n" \
  151.             "<LI><EM>${@.FAMS[i].FAMILY.EVENT[j].TAG}</EM>: ${@.FAMS[i].FAMILY.EVENT[j].DATE}\n" \
  152.           "!ENDIF\n" \
  153.         "!ELSE\n" \
  154.            "<LI><EM>${@.FAMS[i].FAMILY.EVENT[j].TAG}</EM>: ${@.FAMS[i].FAMILY.EVENT[j].PLACE.NAME}\n" \
  155.         "!ENDIF\n" \
  156.         "!INCREMENT j\n" \
  157.       "!END\n" \
  158.       "</UL>\n" \
  159.     "!ELSE\n" \
  160.       "<BR>\n" \
  161.     "!ENDIF\n",
  162.  
  163. /* 1 */
  164.     "!IF @.FAMS[i].FAMILY.CHILDREN\n" \
  165.       "<OL>\n" \
  166.       "!RESET j\n" \
  167.       "!WHILE @.FAMS[i].FAMILY.CHILDREN[j]\n" \
  168.         "<LI><A HREF=\"${@.FAMS[i].FAMILY.CHILDREN[j].INDIV.&}\">" \
  169.         "${@.FAMS[i].FAMILY.CHILDREN[j].INDIV.NAME}</A>\n" \
  170.         "!INCREMENT j\n" \
  171.       "!END\n" \
  172.       "</OL>\n" \
  173.     "!ELSE\n" \
  174.       "<BR>\n" \
  175.     "!ENDIF\n" \
  176.     "!INCREMENT i\n" \
  177.   "!END\n",
  178.  
  179. /* 2 */  /* Pedigree diagram, if available */
  180.   "!PEDIGREE @\n",
  181.  
  182. /* 3 */  /* This should be the value of INDEX_ANCHOR_POS in output.h */
  183.   INDEX_ANCHOR_NOSUBDIR \
  184.   "<P>\n",
  185.   
  186. /* 4 */
  187.   "!IF @.NOTE\n" \
  188.     "<H2>Notes</H2>\n" \
  189.     "!RESET i\n" \
  190.     "!WHILE @.NOTE[i]\n" \
  191.       "${@.NOTE[i].TEXT}\n" \
  192.       "!RESET j\n" \
  193.       "!WHILE @.NOTE[i].CONT[j]\n" \
  194.         "${@.NOTE[i].CONT[j].TEXT}\n" \
  195.         "!INCREMENT j\n" \
  196.       "!END\n" \
  197.       "<P>\n" \
  198.       "!INCREMENT i\n" \
  199.     "!END\n" \
  200.   "!ENDIF\n" \
  201.   "<BR>\n" \
  202.   "!IF @.SOURCE\n" \
  203.     "<H2>Sources</H2>\n" \
  204.     "!RESET i\n" \
  205.     "!WHILE @.SOURCE[i]\n" \
  206.       "${@.SOURCE[i].SOURCE.TEXT}\n" \
  207.       "!RESET j\n" \
  208.       "!WHILE @.SOURCE[i].SOURCE.CONT[j]\n" \
  209.         "${@.SOURCE[i].SOURCE.CONT[j].TEXT}\n" \
  210.         "!INCREMENT j\n" \
  211.       "!END\n" \
  212.       "<P>\n" \
  213.       "!INCREMENT i\n" \
  214.     "!END\n" \
  215.   "!ENDIF\n" \
  216.   "!INCLUDE @.inc\n" \
  217.   "</BODY>\n" \
  218.   "</HTML>\n"
  219.   };
  220.  
  221. int individual_template_base_size =
  222.   sizeof(individual_template_base)/sizeof(char *);
  223.  
  224. char *individual_template = NULL;
  225.  
  226. char *index_template =
  227.   "<HTML>\n<HEAD>\n<TITLE>" \
  228.   "(${@.PARENT.FIRST.NAME} - ${@.PARENT.LAST.NAME})" \
  229.   "</TITLE>\n</HEAD>\n" \
  230.   "<BODY>\n<H2>Index of Persons</H2>\n" \
  231.   "!WHILE @\n" \
  232.     "!IF @.CHILDREN\n" \
  233.        "<A HREF=\"${@.&}\">\n" \
  234.        "${@.FIRST.NAME} - ${@.LAST.NAME}\n" \
  235.        "</A><BR>\n" \
  236.     "!ELSE\n" \
  237.        "!IF @.FIRST.TITLE\n" \
  238.      "<A NAME=\"${@.FIRST.XREF}\"></A>\n" \
  239.          "<A HREF=\"${@.FIRST.&}\">" \
  240.          "${@.FIRST.NAME} (${@.FIRST.TITLE})</A>\n" \
  241.        "!ELSE\n" \
  242.      "<A NAME=\"${@.FIRST.XREF}\"></A>\n" \
  243.          "<A HREF=\"${@.FIRST.&}\">" \
  244.      "${@.FIRST.NAME}</A>\n" \
  245.        "!ENDIF\n" \
  246.        " (${@.FIRST.BIRTH.DATE}\n" \
  247. /*       "!IF @.FIRST.BIRTH.PLACE.NAME\n" \
  248.          ", ${@.FIRST.BIRTH.PLACE.NAME}\n" \
  249.        "!ENDIF\n" \ */
  250.        " - ${@.FIRST.DEATH.DATE}\n" \
  251. /*       "!IF @.LAST.BIRTH.PLACE.NAME\n" \
  252.          ", ${@.LAST.BIRTH.PLACE.NAME}\n" \
  253.        "!ENDIF\n" \ */
  254.        ")<BR>\n" \
  255.     "!ENDIF\n" \
  256.     "!IF @.NEXT\n" \
  257.     "!ELSE\n" \
  258.       "</P>\n" \
  259.       "!IF @.PARENT\n" \
  260.         "!IF @.PARENT.PARENT\n" \
  261.           "<A HREF=\"${@.PARENT.PARENT.&}\">UP</A> " \
  262.           "(${@.PARENT.PARENT.FIRST.NAME} - ${@.PARENT.PARENT.LAST.NAME}\n" \
  263.           "</A>)<BR>\n" \
  264.         "!ENDIF\n" \
  265.         "!IF @.PARENT.PREV\n" \
  266.           "<A HREF=\"${@.PARENT.PREV.&}\">BACK</A> " \
  267.           "(${@.PARENT.PREV.FIRST.NAME} - ${@.PARENT.PREV.LAST.NAME}\n" \
  268.           "</A>)<BR>\n" \
  269.         "!ENDIF\n" \
  270.         "!IF @.PARENT.NEXT\n" \
  271.           "<A HREF=\"${@.PARENT.NEXT.&}\">NEXT</A> " \
  272.           "(${@.PARENT.NEXT.FIRST.NAME} - ${@.PARENT.NEXT.LAST.NAME}\n" \
  273.           "</A>)<BR>\n" \
  274.         "!ENDIF\n" \
  275.       "!ENDIF\n" \
  276.     "!ENDIF\n" \
  277.     "!NEXT\n" \
  278.   "!END\n" \
  279.   "<P>\n"
  280. #ifdef MSDOS
  281.   "<A HREF=\"surnames.htm\">SURNAMES</A>\n"
  282. #else
  283.   "<A HREF=\"SURNAMES.html\">SURNAMES</A>\n"
  284. #endif
  285.   "</BODY>\n</HTML>\n";
  286.  
  287. char *surname_template =
  288.   "<HTML>\n<HEAD>\n<TITLE>" \
  289.   "Index of Surnames" \
  290.   "</TITLE>\n</HEAD>\n" \
  291.   "<BODY>\n<H2>Index of Surnames</H2>\n" \
  292.   "!WHILE @\n" \
  293.     "!IF @.NEXT\n" \
  294.       "<A HREF=\"${@.PARENT.&}#${@.FIRST.XREF}\">${@.FIRST.SURNAME}</A>,\n" \
  295.     "!ELSE\n" \
  296.       "<A HREF=\"${@.PARENT.&}#${@.FIRST.XREF}\">${@.FIRST.SURNAME}</A>\n" \
  297.     "!ENDIF\n" \
  298.     "!NEXT\n" \
  299.   "!END\n" \
  300.   "<P>\n" \
  301.   INDEX_ANCHOR_NOSUBDIR \
  302.   "</BODY>\n</HTML>\n";
  303.  
  304. /*
  305.  * Record types
  306.  */
  307.  
  308. typedef enum {
  309.   T_INTEGER, T_STRING, T_PLACE, T_NOTE, T_XREF, T_SOURCE,
  310.   T_FAMEVENT, T_EVENT, T_INDIV, T_FAMILY, T_CONT, T_URL, T_INDEX
  311. } record_type;
  312.  
  313. /*
  314.  * Interpreter state
  315.  */
  316.  
  317. int skipping;
  318.  
  319. #define CONTROL_STACK_SIZE 100
  320. char *while_stack[CONTROL_STACK_SIZE];
  321. int while_stack_top;
  322. int if_stack[CONTROL_STACK_SIZE];
  323. int if_stack_top;
  324.  
  325. char *template;
  326. char *template_start;
  327. int current_index;
  328. record_type current_type = T_INTEGER;
  329. union value {
  330.   int integer;
  331.   char *string;
  332.   struct place_structure *place;
  333.   struct note_structure *note;
  334.   struct xref *xref;
  335.   struct event_structure *event;
  336.   struct individual_record *indiv;
  337.   struct family_record *family;
  338.   struct continuation *cont;
  339.   struct source_record *source;
  340.   struct index_node *index;
  341.   char *url;
  342. } current_value;
  343.  
  344. union value root;
  345. record_type root_type;
  346.  
  347. char current_url[FILENAME_MAX+1];
  348. int doing_index = 0;
  349.  
  350. void interpret(FILE *ofile);
  351. void variable(FILE *ofile);
  352. void command(FILE *ofile);
  353. void collect_identifier(char **ret);
  354. void skip_white_space();
  355. void output_error(char *msg);
  356.  
  357. void set_variable(char *name, int value);
  358. int get_variable(char *name);
  359.  
  360. void family_select(char *field);
  361. void indiv_select(char *field);
  362. void event_select(char *field);
  363. void note_select(char *field);
  364. void source_select(char *field);
  365. void cont_select(char *field);
  366. void place_select(char *field);
  367. void xref_select(char *field);
  368. void index_select(char *field);
  369.  
  370. void construct_url(char *dest);
  371.  
  372. void output_pedigree(FILE *ofile, struct individual_record *rt, int depth);
  373. void compute_pedigree_widths(struct individual_record *rt, int depth,
  374.                  int *widths);
  375. void output_pedigree_info(FILE *ofile, struct individual_record *rt,
  376.               int maxdepth, int *widths, int curdepth,
  377.               unsigned int parities);
  378. void output_pedigree_name(FILE *ofile, struct individual_record *indiv,
  379.               int width, char *hdr);
  380. void pedigree_indent(FILE *ofile, int *widths, int curdepth,
  381.              unsigned int parities);
  382.  
  383. void output_individual(struct individual_record *rt)
  384. {
  385.   FILE *ofile;
  386.   char path[FILENAME_MAX+1];
  387.   char url[FILENAME_MAX+1];
  388.   char id[9];
  389.  
  390.   if(indivs_per_directory) {
  391.     sprintf(path, "D%04d",
  392.         (rt->serial / indivs_per_directory) + 1);
  393. #ifdef MSDOS
  394.     _mkdir(path);
  395. #else
  396.     mkdir(path, 0777);
  397. #endif
  398.     strcat(path, "/");
  399.   } else {
  400.     sprintf(path, "%s", "");
  401.   }
  402.   if(indivs_per_file) {
  403.     sprintf(id, "S%07d", ((rt->serial - 1) / indivs_per_file) + 1);
  404.     sprintf(url, file_template, id);
  405.   } else {
  406.     sprintf(url, file_template, rt->xref);
  407.   }
  408.   strcat(path, url);
  409.   if(indivs_per_file && rt->serial%indivs_per_file != 1) {
  410.     ofile = fopen(path, "a");
  411.   } else {
  412. #ifdef MSDOS
  413.     fprintf(stderr, "Creating %s\n", path);
  414. #endif
  415.     ofile = fopen(path, "w");
  416.   }
  417.   if(ofile == NULL) {
  418.     fprintf(stderr, "Failed to create individual file %s\n", path);
  419.     return;
  420.   }
  421.   template = template_start = individual_template;
  422.   root.indiv = rt;
  423.   root_type = T_INDIV;
  424.   interpret(ofile);
  425.   fclose(ofile);
  426. }
  427.  
  428. /*
  429.  * Recursive routine to create hierarchical index.
  430.  * Assumes that inp is the root of a tree of index nodes.
  431.  * Leaves are identifiable as those with inp->children == NULL.
  432.  * In this case, inp->first == inp->last, a pointer to the
  433.  * single individual at this leaf.
  434.  * The index template expects the head of a list of index nodes as the root.
  435.  * For each node in the list, it determines whether that node is an
  436.  * internal node or a leaf.  If a leaf, then it outputs a leaf entry for
  437.  * the one individual under that node.  If an internal node, it outputs
  438.  * a range entry for that node.
  439.  */
  440.  
  441. void output_index(struct index_node *inp)
  442. {
  443.   FILE *ofile;
  444.   struct index_node *inc;
  445.   char path[FILENAME_MAX+1];
  446.   char base[8];
  447.  
  448.   if(inp->children == NULL)
  449.       return;
  450.   if(inp->id == 0)
  451.       sprintf(path, file_template, "INDEX");
  452.   else {
  453.       sprintf(base, "IND%04d", inp->id);
  454.       sprintf(path, file_template, base);
  455.   }
  456. #ifdef MSDOS
  457.     fprintf(stderr, "Creating %s\n", path);
  458. #endif
  459.   if((ofile = fopen(path, "w")) == NULL) {
  460.       fprintf(stderr, "Failed to create index file %s\n", path);
  461.       return;
  462.   }
  463.   template = template_start = index_template;
  464.   root.index = inp->children;
  465.   root_type = T_INDEX;
  466.   doing_index = 1;
  467.   interpret(ofile);
  468.   doing_index = 0;
  469.   fclose(ofile);
  470.   for(inc = inp->children; inc != NULL; inc = inc->next)
  471.       output_index(inc);
  472. }
  473.  
  474. /*
  475.  * Output surname index with links to level 1 index nodes.
  476.  * The argument is the head of a list of index nodes, one for each surname.
  477.  * Each index node is linked (via FIRST and LAST) to the first and last
  478.  * individuals with that surname, and via PARENT to the level 1 index node
  479.  * containing the first index entry for that surname.
  480.  */
  481.  
  482. void output_surnames(struct index_node *inp)
  483. {
  484.   FILE *ofile;
  485.   char path[FILENAME_MAX+1];
  486.  
  487.   sprintf(path, file_template, "SURNAMES");
  488. #ifdef MSDOS
  489.     fprintf(stderr, "Creating %s\n", path);
  490. #endif
  491.   if((ofile = fopen(path, "w")) == NULL) {
  492.       fprintf(stderr, "Failed to create surnames file %s\n", path);
  493.       return;
  494.   }
  495.   template = template_start = surname_template;
  496.   root.index = inp;
  497.   root_type = T_INDEX;
  498.   doing_index = 1;
  499.   interpret(ofile);
  500.   doing_index = 0;
  501.   fclose(ofile);
  502. }
  503.  
  504. /*
  505.  * Output index file for use by GenWeb automatic indexers.
  506.  *
  507.  * First version produces a format suitable for AWK/SORT processing:
  508.  * each individual is on a single line, with fields terminated by '|'.
  509.  */
  510.  
  511. void output_gendex(struct individual_record *rt)
  512. {
  513.   FILE *ofile;
  514.   char path[FILENAME_MAX+1], *cp;
  515.   struct event_structure *ep;
  516.   int found;
  517.  
  518.   sprintf(path, "%s", "GENDEX.txt");
  519. #ifdef MSDOS
  520.     fprintf(stderr, "Creating %s\n", path);
  521. #endif
  522.   if((ofile = fopen(path, "w")) == NULL) {
  523.       fprintf(stderr, "Failed to create index file %s\n", path);
  524.       return;
  525.   }
  526.   for( ; rt != NULL; rt = rt->next) {
  527.       /*
  528.        * First field is the filename/URL
  529.        */
  530.       current_type = T_INDIV;
  531.       current_value.indiv = rt;
  532.       doing_index = 1;
  533.       construct_url(current_url);
  534.       doing_index = 0;
  535.       fprintf(ofile, "%s|", current_url);
  536.       /*
  537.        * Second field is the surname
  538.        */
  539.       fprintf(ofile, "%s|", rt->personal_name && rt->personal_name->surname ?
  540.           rt->personal_name->surname : "");
  541.       /*
  542.        * Third field is the full name as from a GEDCOM file
  543.        */
  544.       if(rt->personal_name && rt->personal_name->name) {
  545.       for(cp = rt->personal_name->name; *cp != '\0'; cp++) {
  546.           if(cp - rt->personal_name->name
  547.               == rt->personal_name->surname_start
  548.          || cp - rt->personal_name->name
  549.               == rt->personal_name->surname_end)
  550.           fputc('/', ofile);
  551.           else
  552.           fputc(*cp, ofile);
  553.       }
  554.       }
  555.       fprintf(ofile, "|");
  556.       /*
  557.        * Fourth and fifth fields are the birth date and place
  558.        */
  559.       found = 0;
  560.       for(ep = rt->events; ep != NULL; ep = ep->next) {
  561.       if(ep->tag && ep->tag->value == BIRT) {
  562.           found++;
  563.           if(ep->date)
  564.           fprintf(ofile, "%s", ep->date);
  565.           fprintf(ofile, "|");
  566.           if(ep->place)
  567.           fprintf(ofile, "%s", ep->place->name);
  568.           fprintf(ofile, "|");
  569.       }
  570.       }
  571.       if(found == 0)
  572.       fprintf(ofile, "||");
  573.       /*
  574.        * Sixth and seventh fields are the death date and place
  575.        */
  576.       found = 0;
  577.       for(ep = rt->events; ep != NULL; ep = ep->next) {
  578.       if(ep->tag && ep->tag->value == DEAT) {
  579.           found++;
  580.           if(ep->date)
  581.           fprintf(ofile, "%s", ep->date);
  582.           fprintf(ofile, "|");
  583.           if(ep->place)
  584.           fprintf(ofile, "%s", ep->place->name);
  585.           fprintf(ofile, "|");
  586.       }
  587.       }
  588.       if(found == 0)
  589.       fprintf(ofile, "||");
  590.       fprintf(ofile, "\n");
  591.   }
  592.   fclose(ofile);  
  593. }
  594.  
  595. /*
  596.  * This version outputs GEDCOM
  597.  */
  598. /*
  599. void output_gendex(struct individual_record *rt)
  600. {
  601.   FILE *ofile;
  602.   char path[FILENAME_MAX+1], *cp;
  603.   struct event_structure *ep;
  604.  
  605.   sprintf(path, "%s", "GENDEX.ged");
  606. #ifdef MSDOS
  607.     fprintf(stderr, "Creating %s\n", path);
  608. #endif
  609.   if((ofile = fopen(path, "w")) == NULL) {
  610.       fprintf(stderr, "Failed to create index file %s\n", path);
  611.       return;
  612.   }
  613.   fprintf(ofile, "0 HEAD\n");
  614.   fprintf(ofile, "1 SOUR GED2HTML\n");
  615.   fprintf(ofile, "1 CHAR ANSEL\n");
  616.   fprintf(ofile, "0 NOTE This limited-information GEDCOM file was automatically\n");
  617.   fprintf(ofile, "1 CONT produced by 'GED2HTML' for use by automatic indexers.\n");
  618.   fprintf(ofile, "1 CONT It contains individual's names, birth and death dates and\n");
  619.   fprintf(ofile, "1 CONT places, and the URL of the HTML individual data page,\n");
  620.   fprintf(ofile, "1 CONT relative to the location of this file.\n");
  621.   for( ; rt != NULL; rt = rt->next) {
  622.       fprintf(ofile, "0 @%s@ INDI\n", rt->xref ? rt->xref : "");
  623.       if(rt->personal_name && rt->personal_name->name) {
  624.       fprintf(ofile, "1 NAME ");
  625.       for(cp = rt->personal_name->name; *cp != '\0'; cp++) {
  626.           if(cp - rt->personal_name->name
  627.               == rt->personal_name->surname_start
  628.          || cp - rt->personal_name->name
  629.               == rt->personal_name->surname_end)
  630.           fputc('/', ofile);
  631.           else
  632.           fputc(*cp, ofile);
  633.       }
  634.       fprintf(ofile, "\n");
  635.       }
  636.       for(ep = rt->events; ep != NULL; ep = ep->next) {
  637.       if(ep->tag) {
  638.           switch(ep->tag->value) {
  639.           case BIRT:
  640.           fprintf(ofile, "1 BIRT\n");
  641.           if(ep->date)
  642.               fprintf(ofile, "2 DATE %s\n", ep->date);
  643.           if(ep->place)
  644.               fprintf(ofile, "2 PLAC %s\n", ep->place->name);
  645.           break;
  646.           case DEAT:
  647.           fprintf(ofile, "1 DEAT\n");
  648.           if(ep->date)
  649.               fprintf(ofile, "2 DATE %s\n", ep->date);
  650.           if(ep->place)
  651.               fprintf(ofile, "2 PLAC %s\n", ep->place->name);
  652.           break;
  653.           default:
  654.           break;
  655.           }
  656.       }
  657.       }
  658.       current_type = T_INDIV;
  659.       current_value.indiv = rt;
  660.       doing_index = 1;
  661.       construct_url(current_url);
  662.       doing_index = 0;
  663.       fprintf(ofile, "1 NOTE URL: %s\n", current_url);
  664.       if(rt->personal_name && rt->personal_name->surname)
  665.       fprintf(ofile, "1 NOTE SURNAME: %s\n", rt->personal_name->surname);
  666.   }
  667.   fprintf(ofile, "0 TRLR\n");
  668.   fclose(ofile);  
  669. }
  670. */
  671.  
  672. /*
  673.  * Construction of pedigree charts
  674.  */
  675.  
  676. #define PEDIGREE_MINW 2
  677.  
  678. void output_pedigree(FILE *ofile, struct individual_record *rt, int depth)
  679. {
  680.   int *widths, i;
  681.  
  682.   if((widths = malloc((depth+1) * sizeof(int))) == NULL)
  683.     out_of_memory();
  684.   for(i = 0; i <= depth; i++)
  685.       widths[i] = PEDIGREE_MINW;
  686.   compute_pedigree_widths(rt, depth, widths);
  687.   fprintf(ofile, "<HR>\n<PRE>\n");
  688.   output_pedigree_info(ofile, rt, depth, widths, 0, 0);
  689.   fprintf(ofile, "</PRE>\n<HR>\n");
  690.   free(widths);
  691. }
  692.  
  693. void compute_pedigree_widths(struct individual_record *rt, int depth,
  694.                  int *widths)
  695. {
  696.   char *cp;
  697.   int i, space = 0;
  698.  
  699.   if(rt->personal_name) {
  700.       for(cp = rt->personal_name->name, i = 0; *cp != '\0'; cp++) {
  701.       if(!space || *cp != ' ')
  702.           i++;
  703.       if(*cp == ' ')
  704.           space = 1;
  705.       else
  706.           space = 0;
  707.       }
  708.       i += 2;
  709.       if(i > *widths)
  710.       *widths = i;
  711.   }
  712.   if(depth && rt->famc && rt->famc->pointer.family) {
  713.     if(rt->famc->pointer.family->husband
  714.     && rt->famc->pointer.family->husband->pointer.individual)
  715.       compute_pedigree_widths(rt->famc->pointer.family->
  716.                   husband->pointer.individual,
  717.                   depth-1, widths+1);
  718.     if(rt->famc->pointer.family->wife
  719.     && rt->famc->pointer.family->wife->pointer.individual)
  720.       compute_pedigree_widths(rt->famc->pointer.family->
  721.                   wife->pointer.individual,
  722.                   depth-1, widths+1);
  723.   }
  724. }
  725.  
  726. void output_pedigree_info(FILE *ofile, struct individual_record *rt,
  727.               int maxdepth, int *widths, int curdepth,
  728.               unsigned int parities)
  729. {
  730.     struct family_record *family = NULL;
  731.     struct individual_record *husband = NULL, *wife = NULL;
  732.  
  733.     if(curdepth > maxdepth)
  734.     return;
  735.     if(rt && rt->famc) {
  736.     family = rt->famc->pointer.family;
  737.     if(family && family->husband)
  738.         husband = family->husband->pointer.individual;
  739.     if(family && family->wife)
  740.         wife = family->wife->pointer.individual;
  741.     }
  742.     if(curdepth == 0) {
  743.     output_pedigree_info(ofile, husband, maxdepth,
  744.                  widths, 1, parities);
  745.     fprintf(ofile, "|\n");
  746.     fprintf(ofile, "|--");
  747.  
  748.     output_pedigree_name(ofile, rt, 0, "");
  749.     fprintf(ofile, "\n");
  750.     fprintf(ofile, "|\n");
  751.     output_pedigree_info(ofile, wife, maxdepth,
  752.                  widths, 1, parities | 1);
  753.     } else {
  754.     output_pedigree_info(ofile, husband, maxdepth,
  755.                  widths, curdepth+1, parities);
  756.     pedigree_indent(ofile, widths, curdepth, parities);
  757.     output_pedigree_name(ofile, rt, widths[curdepth], "_");
  758.     if(curdepth < maxdepth)
  759.         fprintf(ofile, "|");
  760.     fprintf(ofile, "\n");
  761.     output_pedigree_info(ofile, wife, maxdepth, widths,
  762.                  curdepth+1, parities | (1 << curdepth));
  763.     }
  764. }
  765.  
  766. void output_pedigree_name(FILE *ofile, struct individual_record *indiv,
  767.               int width, char *hdr)
  768. {
  769.     char *np;
  770.     int space = 0;
  771.  
  772.     if(indiv) {
  773.     current_value.indiv = indiv;
  774.     current_type = T_INDIV;
  775.     construct_url(current_url);
  776.     } else
  777.     *current_url = '\0';
  778.     fprintf(ofile, "<A HREF=\"%s\">", current_url);
  779.     for(np = hdr; *np != '\0'; np++, width--)
  780.     fputc(*np, ofile);
  781.     if(indiv && indiv->personal_name)
  782.     np = indiv->personal_name->name;
  783.     else
  784.     np = "";
  785.     for( ; *np != '\0'; np++) {
  786.     if(!space || *np != ' ') {
  787.       fputc(*np, ofile);
  788.       width--;
  789.     }
  790.     if(*np == ' ')
  791.         space = 1;
  792.     else
  793.         space = 0;
  794.     }
  795.     while(width-- > 0)
  796.     fputc('_', ofile);
  797.     fprintf(ofile, "</A>");
  798. }
  799.  
  800. void pedigree_indent(FILE *ofile, int *widths, int curdepth,
  801.              unsigned int parities)
  802. {
  803.     int i, j, p, q;
  804.  
  805.     for(i = 1; i < curdepth; i++) {
  806.     /*
  807.      * Determine whether the line being printed is in the middle
  808.      * two quarters of the subtree headed at depth i.
  809.      */
  810.     p = parities & (1<<(i-1)) ? (~0) : 0;
  811.     q = parities & (1<<i) ? (~0) : 0;
  812.     if(p ^ q)
  813.         fprintf(ofile, "|");
  814.     else
  815.         fprintf(ofile, " ");
  816.     for(j = 0; j < widths[i]; j++)
  817.         fprintf(ofile, " ");
  818.     }
  819.     if(parities & (1<<(curdepth-1)))
  820.     fprintf(ofile, "|");
  821.     else
  822.     fprintf(ofile, " ");
  823. }
  824.  
  825. /*
  826.  * Interpret the template stored in "template", outputting results
  827.  * to "ofile".  The value "root" is the starting point for
  828.  * the interpretation of variables.  Its type is given by "root_type".
  829.  */
  830.  
  831. void interpret(FILE *ofile)
  832. {
  833.   char c;
  834.   int start_of_line = 1;
  835.  
  836.   while((c = *template++) != '\0') {
  837.     switch(c) {
  838.     case '\n':
  839.       start_of_line = 1;
  840.       /* fall through intentional */
  841.     case ' ':
  842.     case '\t':
  843.       if(!skipping)
  844.     fputc(c, ofile);
  845.       continue;
  846.     case '!':
  847.       if(!start_of_line) {
  848.     if(!skipping)
  849.       fputc(c, ofile);
  850.     continue;
  851.       }
  852.       template--;
  853.       command(ofile);
  854.       continue;
  855.     case '$':
  856.       start_of_line = 0;
  857.       variable(ofile);
  858.       if(!skipping) {
  859.     if(current_type == T_STRING) {
  860.       fprintf(ofile, "%s", current_value.string);
  861.     } else if(current_type == T_URL) {
  862.       fprintf(ofile, current_value.url);
  863.     } else if(current_type == T_INTEGER) {
  864.       /* Integer variables start from 1 */
  865.       fprintf(ofile, "%d", current_value.integer + 1);
  866.     } else {
  867.       output_error("Attempt to output something not an integer or string");
  868.     }
  869.       }
  870.       continue;
  871.     default:
  872.       start_of_line = 0;
  873.       if(!skipping)
  874.     fputc(c, ofile);
  875.       continue;
  876.     }
  877.   }
  878. }
  879.  
  880. /*
  881.  * After having seen the initial $, interpret a simple or compound variable. 
  882.  */
  883.  
  884. void variable(FILE *ofile)
  885. {
  886.   char c, *selector;
  887.   int braces = 0;
  888.   int first = 1;
  889.   /*
  890.    * $$ means output a single $
  891.    */
  892.   if(*template == '$') {
  893.     if(!skipping)
  894.       fputc(*template++, ofile);
  895.     return;
  896.   }
  897.   while((c = *template) != '\0') {
  898.     switch(c) {
  899.       /*
  900.        * An '@' indicates the current individual
  901.        */
  902.     case '@':
  903.       first = 0;
  904.       template++;
  905.       if(!skipping) {
  906.     current_value = root;
  907.     current_type = root_type;
  908.       }
  909.       if(*template == '.') {
  910.     template++;
  911.     continue;
  912.       } else if(*template == '[') 
  913.     continue;
  914.       else
  915.     return;
  916.       /*
  917.        * Braces in variables simply serve as delimiters
  918.        */
  919.     case '{':
  920.       template++;
  921.       braces++;
  922.       continue;
  923.     case '}':
  924.       template++;
  925.       if(braces) {
  926.     braces--;
  927.     if(braces)
  928.       continue;
  929.     else
  930.       return;
  931.       } else {
  932.     if(!skipping)
  933.       fputc(c, ofile);
  934.     return;
  935.       }
  936.       /*
  937.        * Brackets indicate integer subscripts
  938.        */
  939.     case '[':
  940.       first = 0;
  941.       template++;
  942.       /*
  943.        * Integer constant
  944.        */
  945.       if(*template >= '0' && *template <= '9') {
  946.     /*
  947.      * Convert integer value and leave on top of stack
  948.      * (if not skipping)
  949.      */
  950.       }
  951.       /*
  952.        * Variable subscript
  953.        */
  954.       else {
  955.     record_type previous_type;
  956.     union value previous_value;
  957.     
  958.     previous_type = current_type;
  959.     previous_value = current_value;
  960.     variable(ofile);
  961.     if(!skipping && current_type != T_INTEGER)
  962.       output_error("Subscript is not an integer variable");
  963.     else {
  964.       current_index = current_value.integer;
  965.       current_type = previous_type;
  966.       current_value = previous_value;
  967.     }
  968.     if(*template != ']') {
  969.       output_error("Subscript fails to end with ']'");
  970.     } else {
  971.       template++;
  972.     }
  973.       }
  974.       if(!skipping) {
  975.     switch(current_type) {
  976.     case T_INTEGER:
  977.     case T_STRING:
  978.       output_error("Can't apply subscript to an integer or string");
  979.       break;
  980.     case T_PLACE:
  981.       while(current_index--) place_select("NEXT");
  982.       break;
  983.     case T_NOTE:
  984.       while(current_index--) note_select("NEXT");
  985.       break;
  986.     case T_SOURCE:
  987.       while(current_index--) source_select("NEXT");
  988.       break;
  989.     case T_CONT:
  990.       while(current_index--) cont_select("NEXT");
  991.       break;
  992.     case T_EVENT:
  993.       while(current_index--) event_select("NEXT");
  994.       break;
  995.     case T_INDIV:
  996.       while(current_index--) indiv_select("NEXT");
  997.       break;
  998.     case T_FAMILY:
  999.       while(current_index--) family_select("NEXT");
  1000.       break;
  1001.     case T_XREF:
  1002.       while(current_index--) xref_select("NEXT");
  1003.       break;
  1004.     case T_INDEX:
  1005.       while(current_index--) index_select("NEXT");
  1006.       break;
  1007.     default:
  1008.       break;
  1009.     }
  1010.       }
  1011.       if(*template == '.') {
  1012.     template++;
  1013.     continue;
  1014.       } else if(*template == '[' || *template == '}') 
  1015.     continue;
  1016.       else
  1017.     return;
  1018.       /*
  1019.        * An ampersand means turn the current individual or index into a URL
  1020.        */
  1021.     case '&':
  1022.       template++;
  1023.       if(!skipping) {
  1024.       construct_url(current_url);
  1025.       current_type = T_URL;
  1026.       current_value.url = current_url;
  1027.       }
  1028.       if(*template == '}')
  1029.     continue;
  1030.       else
  1031.     return;
  1032.       /*
  1033.        * Alphabetic characters indicate selector name.
  1034.        * Anything else is a delimiter.
  1035.        */
  1036.     default:
  1037.       collect_identifier(&selector);
  1038.       if(*selector == '\0')
  1039.     return;
  1040.       if(first) {
  1041.     if(!skipping) {
  1042.       current_value.integer = get_variable(selector);
  1043.       current_type = T_INTEGER;
  1044.     }
  1045.     if(*template == '}')
  1046.       continue;
  1047.     else
  1048.       return;
  1049.       }
  1050.       if(!skipping) {
  1051.     switch(current_type) {
  1052.     case T_INTEGER:
  1053.     case T_STRING:
  1054.       output_error("Can't apply selector to an integer or string");
  1055.       break;
  1056.     case T_PLACE:
  1057.       place_select(selector);
  1058.       break;
  1059.     case T_NOTE:
  1060.       note_select(selector);
  1061.       break;
  1062.     case T_SOURCE:
  1063.       source_select(selector);
  1064.       break;
  1065.     case T_CONT:
  1066.       cont_select(selector);
  1067.       break;
  1068.     case T_EVENT:
  1069.       event_select(selector);
  1070.       break;
  1071.     case T_INDIV:
  1072.       indiv_select(selector);
  1073.       break;
  1074.     case T_FAMILY:
  1075.       family_select(selector);
  1076.       break;
  1077.     case T_XREF:
  1078.       xref_select(selector);
  1079.       break;
  1080.     case T_INDEX:
  1081.       index_select(selector);
  1082.       break;
  1083.     default:
  1084.       break;
  1085.     }
  1086.       }
  1087.       if(*template == '.') {
  1088.     template++;
  1089.     continue;
  1090.       } else if(*template == '[' || *template == '}') 
  1091.     continue;
  1092.       else
  1093.     return;
  1094.     }
  1095.   }
  1096. }
  1097.  
  1098. /*
  1099.  * Record field selection operations
  1100.  */
  1101.  
  1102. void family_select(char *field)
  1103. {
  1104.   struct family_record *r = current_value.family;
  1105.   if(!strcmp(field, "XREF")) {
  1106.     current_type = T_STRING;
  1107.     current_value.string = (r && r->xref) ? r->xref: "";
  1108.   } else if(!strcmp(field, "REFN")) {
  1109.     current_type = T_STRING;
  1110.     current_value.string = (r && r->refn) ? r->refn: "";
  1111.   } else if(!strcmp(field, "HUSBAND")) {
  1112.     current_type = T_INDIV;
  1113.     current_value.indiv =
  1114.       (r && r->husband) ? r->husband->pointer.individual : NULL;
  1115.   } else if(!strcmp(field, "WIFE")) {
  1116.     current_type = T_INDIV;
  1117.     current_value.indiv =
  1118.       (r && r->wife) ? r->wife->pointer.individual: NULL;
  1119.   } else if(!strcmp(field, "CHILDREN")) {
  1120.     current_type = T_XREF;
  1121.     current_value.xref =
  1122.       (r && r->children) ? r->children: NULL;
  1123.   } else if(!strcmp(field, "NOTE")) {
  1124.     current_type = T_NOTE;
  1125.     current_value.note = r ? r->notes: NULL;
  1126.   } else if(!strcmp(field, "EVENT")) {
  1127.     current_type = T_EVENT;
  1128.     current_value.event = r ? r->events: NULL;
  1129.   } else if(!strcmp(field, "NEXT")) {
  1130.     current_value.family = r ? r->next: NULL;
  1131.   } else {
  1132.     output_error("Unrecognized selector applied to family record");
  1133.   }
  1134. }
  1135.  
  1136. void indiv_select(char *field)
  1137. {
  1138.   struct individual_record *r = current_value.indiv;
  1139.   if(!strcmp(field, "XREF")) {
  1140.     current_type = T_STRING;
  1141.     current_value.string = (r && r->xref) ? r->xref: "";
  1142.   } else if(!strcmp(field, "NAME")) {
  1143.     current_type = T_STRING;
  1144.     current_value.string = (r && r->personal_name) ?
  1145.       r->personal_name->name: "???";
  1146.   } else if(!strcmp(field, "SURNAME")) {
  1147.     current_type = T_STRING;
  1148.     current_value.string = (r && r->personal_name
  1149.                 && r->personal_name->surname) ?
  1150.       r->personal_name->surname: "???";
  1151.   } else if(!strcmp(field, "TITLE")) {
  1152.     current_type = T_STRING;
  1153.     current_value.string = (r && r->title) ? r->title: "";
  1154.   } else if(!strcmp(field, "ISMALE")) {
  1155.     current_type = T_INTEGER;
  1156.     current_value.integer = (r && r->sex == 'M');
  1157.   } else if(!strcmp(field, "ISFEMALE")) {
  1158.     current_type = T_INTEGER;
  1159.     current_value.integer = (r && r->sex == 'F');
  1160.   } else if(!strcmp(field, "REFN")) {
  1161.     current_type = T_STRING;
  1162.     current_value.string = (r && r->refn) ? r->refn: "";
  1163.   } else if(!strcmp(field, "RFN")) {
  1164.     current_type = T_STRING;
  1165.     current_value.string = (r && r->rfn) ? r->rfn: "";
  1166.   } else if(!strcmp(field, "AFN")) {
  1167.     current_type = T_STRING;
  1168.     current_value.string = (r && r->afn) ? r->afn: "";
  1169.   } else if(!strcmp(field, "FAMC")) {
  1170.     current_type = T_XREF;
  1171.     current_value.xref = r ? r->famc: NULL;
  1172.   } else if(!strcmp(field, "FAMS")) {
  1173.     current_type = T_XREF;
  1174.     current_value.xref = r ? r->fams: NULL;
  1175.   } else if(!strcmp(field, "FATHER")) {
  1176.     current_value.indiv =
  1177.       (r && r->famc && r->famc->pointer.family
  1178.        && r->famc->pointer.family->husband)
  1179.     ? r->famc->pointer.family->husband->pointer.individual: NULL;
  1180.   } else if(!strcmp(field, "MOTHER")) {
  1181.     current_value.indiv =
  1182.       (r && r->famc && r->famc->pointer.family
  1183.        && r->famc->pointer.family->wife)
  1184.     ? r->famc->pointer.family->wife->pointer.individual: NULL;
  1185.   } else if(!strcmp(field, "NOTE")) {
  1186.     current_type = T_NOTE;
  1187.     current_value.note = r ? r->notes: NULL;
  1188.   } else if(!strcmp(field, "SOURCE")) {
  1189.     current_type = T_XREF;
  1190.     current_value.xref =
  1191.       (r && r->sources) ? r->sources: NULL;
  1192.   } else if(!strcmp(field, "EVENT")) {
  1193.     current_type = T_EVENT;
  1194.     current_value.event = r ? r->events: NULL;
  1195.   } else if(!strcmp(field, "BIRTH")) {
  1196.     struct event_structure *ep;
  1197.     current_type = T_EVENT;
  1198.     current_value.event = NULL;
  1199.     for(ep = r->events; ep != NULL; ep = ep->next) {
  1200.       if(ep->tag->value == BIRT)
  1201.     current_value.event = ep;
  1202.     }
  1203.   } else if(!strcmp(field, "DEATH")) {
  1204.     struct event_structure *ep;
  1205.     current_type = T_EVENT;
  1206.     current_value.event = NULL;
  1207.     for(ep = r->events; ep != NULL; ep = ep->next) {
  1208.       if(ep->tag->value == DEAT)
  1209.     current_value.event = ep;
  1210.     }
  1211.   } else if(!strcmp(field, "NEXT")) {
  1212.     current_value.indiv = r ? r->next: NULL;
  1213.   } else {
  1214.     output_error("Unrecognized selector applied to individual record");
  1215.   }
  1216. }
  1217.  
  1218. void event_select(char *field)
  1219. {
  1220.   struct event_structure *r = current_value.event;
  1221.   if(!strcmp(field, "TAG")) {
  1222.     current_type = T_STRING;
  1223.     current_value.string = (r && r->tag) ? r->tag->pname[default_language]: "";
  1224.   } else if(!strcmp(field, "DATE")) {
  1225.     current_type = T_STRING;
  1226.     current_value.string = (r && r->date) ? r->date: "";
  1227.   } else if(!strcmp(field, "PLACE")) {
  1228.     current_type = T_PLACE;
  1229.     current_value.place = r ? r->place: NULL;
  1230.   } else if(!strcmp(field, "NEXT")) {
  1231.     current_value.event = r ? r->next: NULL;
  1232.   } else {
  1233.     output_error("Unrecognized selector applied to event structure");
  1234.   }
  1235. }
  1236.  
  1237. void note_select(char *field)
  1238. {
  1239.   struct note_structure *r = current_value.note;
  1240.  
  1241.   if(!strcmp(field, "XREF")) {
  1242.     current_type = T_STRING;
  1243.     current_value.string = (r && r->xref) ? r->xref: "";
  1244.   } else if(!strcmp(field, "TEXT")) {
  1245.     current_type = T_STRING;
  1246.     current_value.string = (r && r->text) ? r->text: "";
  1247.   } else if(!strcmp(field, "NEXT")) {
  1248.     current_value.note = r ? r->next : NULL;
  1249.   } else if(!strcmp(field, "CONT")) {
  1250.     current_type = T_CONT;
  1251.     current_value.cont = r ? r->cont: NULL;
  1252.   } else {
  1253.     output_error("Unrecognized selector applied to note structure");
  1254.   }
  1255. }
  1256.  
  1257. void source_select(char *field)
  1258. {
  1259.   struct source_record *r = current_value.source;
  1260.  
  1261.   if(!strcmp(field, "XREF")) {
  1262.     current_type = T_STRING;
  1263.     current_value.string = (r && r->xref) ? r->xref: "";
  1264.   } else if(!strcmp(field, "TEXT")) {
  1265.     current_type = T_STRING;
  1266.     current_value.string = (r && r->text) ? r->text: "";
  1267.   } else if(!strcmp(field, "CONT")) {
  1268.     current_type = T_CONT;
  1269.     current_value.cont = r ? r->cont: NULL;
  1270.   } else {
  1271.     output_error("Unrecognized selector applied to source record");
  1272.   }
  1273. }
  1274.  
  1275. void cont_select(char *field)
  1276. {
  1277.   struct continuation *c = current_value.cont;
  1278.  
  1279.   if(!strcmp(field, "TEXT")) {
  1280.     current_type = T_STRING;
  1281.     current_value.string = (c && c->text) ? c->text: "";
  1282.   } else if(!strcmp(field, "NEXT")) {
  1283.     current_value.cont = c ? c->next: NULL;
  1284.   } else {
  1285.     output_error("Unrecognized selector applied to continuation structure");
  1286.   }
  1287. }
  1288.  
  1289. void place_select(char *field)
  1290. {
  1291.   struct place_structure *r = current_value.place;
  1292.   if(!strcmp(field, "NAME")) {
  1293.     current_type = T_STRING;
  1294.     current_value.string = (r && r->name) ? r->name: "";
  1295.   } else if(!strcmp(field, "NOTE")) {
  1296.     current_type = T_NOTE;
  1297.     current_value.note = r ? r->notes: NULL;
  1298.   } else {
  1299.     output_error("Unrecognized selector applied to place structure");
  1300.   }
  1301. }
  1302.  
  1303. void xref_select(char *field)
  1304. {
  1305.   struct xref *r = current_value.xref;
  1306.   if(!strcmp(field, "INDIV")) {
  1307.     current_type = T_INDIV;
  1308.     current_value.indiv = r ? r->pointer.individual: NULL;
  1309.   } else if(!strcmp(field, "FAMILY")) {
  1310.     current_type = T_FAMILY;
  1311.     current_value.family = r ? r->pointer.family: NULL;
  1312.   } else if(!strcmp(field, "SOURCE")) {
  1313.     current_type = T_SOURCE;
  1314.     current_value.source = r ? r->pointer.source: NULL;
  1315.   } else if(!strcmp(field, "NEXT")) {
  1316.     current_value.xref = r ? r->next: NULL;
  1317.   } else {
  1318.     output_error("Unrecognized selector applied to cross-reference");
  1319.   }
  1320. }
  1321.  
  1322. void index_select(char *field)
  1323. {
  1324.   struct index_node *r = current_value.index;
  1325.   if(!strcmp(field, "FIRST")) {
  1326.     current_type = T_INDIV;
  1327.     current_value.indiv = r ? r->first: NULL;
  1328.   } else if(!strcmp(field, "LAST")) {
  1329.     current_type = T_INDIV;
  1330.     current_value.indiv = r ? r->last: NULL;
  1331.   } else if(!strcmp(field, "PARENT")) {
  1332.     current_value.index = r ? r->parent: NULL;
  1333.   } else if(!strcmp(field, "CHILDREN")) {
  1334.     current_value.index = r ? r->children: NULL;
  1335.   } else if(!strcmp(field, "NEXT")) {
  1336.     current_value.index = r ? r->next: NULL;
  1337.   } else if(!strcmp(field, "PREV")) {
  1338.     current_value.index = r ? r->prev: NULL;
  1339.   } else {
  1340.     output_error("Unrecognized selector applied to cross-reference");
  1341.   }
  1342. }
  1343.  
  1344. /*
  1345.  * Perform a control command.
  1346.  */
  1347.  
  1348. void command(FILE *ofile)
  1349. {
  1350.   char *buf;
  1351.   char *start = template++; 
  1352.  
  1353.   collect_identifier(&buf);
  1354.   skip_white_space();
  1355.   if(!strcmp(buf, "RESET")) {
  1356.     collect_identifier(&buf);
  1357.     if(*template == '\n')
  1358.       template++;
  1359.     else {
  1360.       output_error("Newline expected");
  1361.     }
  1362.     set_variable(buf, 0);
  1363.   } else if(!strcmp(buf, "INCREMENT")) {
  1364.     collect_identifier(&buf);
  1365.     if(*template == '\n')
  1366.       template++;
  1367.     else {
  1368.       output_error("Newline expected");
  1369.     }
  1370.     set_variable(buf, get_variable(buf)+1);
  1371.   } else if(!strcmp(buf, "IF")) {
  1372.     variable(ofile);
  1373.     if(*template == '\n') {
  1374.       template++;
  1375.       if(current_type == T_STRING) {
  1376.     if(!strcmp(current_value.string, "")) {
  1377.       skipping++;
  1378.       if_stack[if_stack_top++] = 1;
  1379.     } else {
  1380.       if_stack[if_stack_top++] = 0;
  1381.     }
  1382.       } else {
  1383.     if(!current_value.integer) {
  1384.       skipping++;
  1385.       if_stack[if_stack_top++] = 1;
  1386.     } else {
  1387.       if_stack[if_stack_top++] = 0;
  1388.     }
  1389.       }
  1390.     } else {
  1391.       output_error("Newline expected");
  1392.     }
  1393.   } else if(!strcmp(buf, "ELSE")) {
  1394.     if(*template == '\n') {
  1395.       template++;
  1396.       if(if_stack[if_stack_top-1]) {
  1397.     skipping--;
  1398.     if_stack[if_stack_top-1] = 0;
  1399.       } else {
  1400.     skipping++;
  1401.     if_stack[if_stack_top-1] = 1;
  1402.       }
  1403.     } else {
  1404.       output_error("Newline expected");
  1405.     }
  1406.   } else if(!strcmp(buf, "ENDIF")) {
  1407.     if(*template == '\n') {
  1408.       template++;
  1409.       if(if_stack[--if_stack_top])
  1410.     skipping--;
  1411.     } else {
  1412.       output_error("Newline expected");
  1413.     }
  1414.   } else if(!strcmp(buf, "WHILE")) {
  1415.     variable(ofile);
  1416.     if(*template == '\n') {
  1417.       template++;
  1418.       while_stack[while_stack_top++] = start;
  1419.       if(!skipping) {
  1420.     if(current_type == T_STRING) {
  1421.       if(!strcmp(current_value.string, ""))
  1422.         skipping++;
  1423.     } else {
  1424.       if(!current_value.integer)
  1425.         skipping++;
  1426.     }
  1427.       } else {
  1428.     skipping++;
  1429.       }
  1430.     } else {
  1431.       output_error("Newline expected");
  1432.     }
  1433.   } else if(!strcmp(buf, "END")) {
  1434.     if(*template == '\n') {
  1435.       template++;
  1436.       if(skipping) {
  1437.     skipping--;
  1438.         if(while_stack_top)
  1439.       while_stack_top--;
  1440.       } else {
  1441.         if(while_stack_top)
  1442.       template = while_stack[--while_stack_top];
  1443.       }
  1444.     } else {
  1445.       output_error("Newline expected");
  1446.     }
  1447.   } else if(!strcmp(buf, "NEXT")) {
  1448.     if(*template == '\n') {
  1449.       template++;
  1450.       if(!skipping) {
  1451.       if(root_type == T_INDIV && root.indiv)
  1452.           root.indiv = root.indiv->next;
  1453.       else if(root_type == T_INDEX && root.index)
  1454.           root.index = root.index->next;
  1455.       else
  1456.           output_error("'NEXT' applied to something not an individual or index\n");
  1457.       }
  1458.     } else {
  1459.     output_error("Newline expected");
  1460.     }
  1461.   } else if(!strcmp(buf, "PEDIGREE")) {
  1462.     variable(ofile);
  1463.     if(*template == '\n') {
  1464.       template++;
  1465.       if(!skipping) {
  1466.       if(current_type == T_INDIV) {
  1467.           if(pedigree_depth)
  1468.           output_pedigree(ofile, current_value.indiv, pedigree_depth);
  1469.       } else {
  1470.           output_error("Attempt to output pedigree for non-individual\n");
  1471.       }
  1472.       }
  1473.     } else {
  1474.     output_error("Newline expected");
  1475.     }
  1476.   } else if(!strcmp(buf, "INCLUDE")) {
  1477.     char path[FILENAME_MAX+1], *pp;
  1478.     FILE *incf;
  1479.  
  1480.     skip_white_space();
  1481.     for(pp = path; pp - path <= FILENAME_MAX
  1482.     && *template && *template != '\n'; ) {
  1483.       if(*template == '@') {
  1484.     template++;
  1485.     if(*template == '@') {
  1486.       template++;
  1487.       *pp++ = '@';
  1488.     } else {
  1489.       char *id;
  1490.       if(root_type == T_INDIV)
  1491.           id = root.indiv->xref;
  1492.       else
  1493.           output_error("Use of '!INCLUDE @' on non-individual\n");
  1494.       while(*id && pp - path <= FILENAME_MAX)
  1495.         *pp++ = *id++;
  1496.     }
  1497.       } else {
  1498.     *pp++ = *template++;
  1499.       }
  1500.     }
  1501.     *pp = '\0';
  1502.     if((incf = fopen(path, "r")) != NULL) {
  1503.       char c;
  1504.       while((c = fgetc(incf)) != EOF)
  1505.     fputc(c, ofile);
  1506.       fclose(incf);
  1507.     }
  1508.   } else {
  1509.     output_error("Unrecognized control command");
  1510.   }
  1511. }
  1512.  
  1513. /*
  1514.  * Pick up the next identifier in the template.
  1515.  */
  1516.  
  1517. #define IDENTIFIER_MAX 63
  1518.  
  1519. void collect_identifier(char **ret)
  1520. {
  1521.   static char id[IDENTIFIER_MAX+1];
  1522.   char *ip, c;
  1523.  
  1524.   ip = id;
  1525.   while(((c = *template) >= 'A' && c <= 'Z')
  1526.     || (c >= 'a' && c <= 'z')) {
  1527.     template++;
  1528.     if(ip - id < IDENTIFIER_MAX)
  1529.       *ip++ = c;
  1530.   }
  1531.   *ip = '\0';
  1532.   *ret = id;
  1533. }
  1534.  
  1535. void skip_white_space()
  1536. {
  1537.   while(*template == ' ' || *template == '\t')
  1538.     template++;
  1539. }
  1540.  
  1541. struct binding {
  1542.   char *name;
  1543.   int value;
  1544.   struct binding *next;
  1545. } *environment;
  1546.  
  1547. void set_variable(char *name, int value)
  1548. {
  1549.   struct binding *b;
  1550.   for(b = environment; b != NULL; b = b->next) {
  1551.     if(!strcmp(name, b->name)) {
  1552.       b->value = value;
  1553.       return;
  1554.     }
  1555.   }
  1556.   if((b = malloc(sizeof(struct binding))) == NULL)
  1557.     out_of_memory();
  1558.   if((b->name = strdup(name)) == NULL)
  1559.     out_of_memory();
  1560.   b->value = value;
  1561.   b->next = environment;
  1562.   environment = b;
  1563. }
  1564.  
  1565. int get_variable(char *name)
  1566. {
  1567.   struct binding *b;
  1568.   for(b = environment; b != NULL; b = b->next) {
  1569.     if(!strcmp(name, b->name))
  1570.       return(b->value);
  1571.   }
  1572.   set_variable(name, 0);
  1573.   return(0);
  1574. }
  1575.  
  1576. void output_error(char *msg)
  1577. {
  1578.   char *tp;
  1579.   int line = 1;
  1580.  
  1581.   for(tp = template_start; tp < template; tp++)
  1582.     if(*tp == '\n')
  1583.       line++;
  1584.   fprintf(stderr, "Output error: ");
  1585.   fprintf(stderr, "%s template line %d: %s\n",
  1586.       template_start == individual_template ? "individual" :
  1587.       template_start == index_template ? "index" : "surname",
  1588.       line, msg);
  1589. }
  1590.  
  1591. void construct_url(char *dest)
  1592. {
  1593.   char url[FILENAME_MAX+1];
  1594.   char id[9];
  1595.  
  1596.   *dest = '\0';
  1597.   if(current_type == T_INDIV) {
  1598.       if(indivs_per_directory) {
  1599.       if(!doing_index)
  1600.           sprintf(dest, "../");
  1601.       sprintf(url, "D%04d/",
  1602.           current_value.indiv ?
  1603.           (current_value.indiv->serial / indivs_per_directory) + 1 :
  1604.           0);
  1605.       } else {
  1606.       sprintf(url, "%s", "");
  1607.       }
  1608.       strcat(dest, url);
  1609.       if(indivs_per_file) {
  1610.       sprintf(id, "S%07d",
  1611.           current_value.indiv ?
  1612.           ((current_value.indiv->serial - 1) / indivs_per_file) + 1: 
  1613.           0);
  1614.       sprintf(url, file_template, id);
  1615.       strcat(dest, url);
  1616.       sprintf(url, "#%s",
  1617.           current_value.indiv ? current_value.indiv->xref : "");
  1618.       strcat(dest, url);
  1619.       } else {
  1620.       sprintf(url, file_template,
  1621.           current_value.indiv ? current_value.indiv->xref : "");
  1622.       strcat(dest, url);
  1623.       }
  1624.   } else if(current_type == T_INDEX) {
  1625.       /*
  1626.        * I build the indexes myself, so I assume that current_value.index
  1627.        * is nonnull if we arrive here.
  1628.        */
  1629.       if(current_value.index->id == 0)
  1630.       sprintf(dest, file_template, "INDEX");
  1631.       else {
  1632.       sprintf(id, "IND%04d", current_value.index->id);
  1633.       sprintf(dest, file_template, id);
  1634.       }
  1635.   } else {
  1636.       output_error("Can only make a URL from an individual or index\n");
  1637.   }
  1638. #ifdef MSDOS
  1639.   {
  1640.     char *cp;
  1641.     for(cp = dest; *cp != '\0' && *cp != '#'; cp++) {
  1642.     if(isupper(*cp))
  1643.         *cp = tolower(*cp);
  1644.     }
  1645.   }
  1646. #endif
  1647. }
  1648.