diff options
author | Karl Berry <karl@freefriends.org> | 2013-04-17 23:19:43 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2013-04-17 23:19:43 +0000 |
commit | aaaec2f57c537ee18ea0a59c2cfd7534d5b2b188 (patch) | |
tree | d54aac03a0b78c2e01c9f8ebb68302ebccb3e641 /Master/texmf-dist/doc/latex/songs/songidx/scripidx.c | |
parent | 0c5f06400403c02946d916560fc5cea65e1abe4b (diff) |
songs (per hamlen mail 16 Apr 2013 23:01:06)
git-svn-id: svn://tug.org/texlive/trunk@30005 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/doc/latex/songs/songidx/scripidx.c')
-rw-r--r-- | Master/texmf-dist/doc/latex/songs/songidx/scripidx.c | 1105 |
1 files changed, 1105 insertions, 0 deletions
diff --git a/Master/texmf-dist/doc/latex/songs/songidx/scripidx.c b/Master/texmf-dist/doc/latex/songs/songidx/scripidx.c new file mode 100644 index 00000000000..1a2189ac617 --- /dev/null +++ b/Master/texmf-dist/doc/latex/songs/songidx/scripidx.c @@ -0,0 +1,1105 @@ +/* Copyright (C) 2012 Kevin W. Hamlen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * The latest version of this program can be obtained from + * http://songs.sourceforge.net. + */ + + +/* Create a LaTeX scripture index file from a scripture index .dat file. */ + +#if HAVE_CONFIG_H +# include "config.h" +#else +# include "vsconfig.h" +#endif + +#include "chars.h" +#include "songidx.h" +#include "fileio.h" + +#if HAVE_STRING_H +# include <string.h> +#else +# if HAVE_STRINGS_H +# include <strings.h> +# endif +#endif + +/* The following constant should be a large integer (but not greater than + * INT_MAX) limiting the maximum number of verses in a single chapter of the + * bible. */ +#define MAXVERSES 999 + +/* A BBOOK struct describes a book of the Bible, including: + * name: a \0-separated string of names (the first one is the name used when + * printing the scripture index) + * verses: an array of numbers giving the number of verses in each chapter + * aliases: the number of names in the name field + * chapters: the number of chapters in the book */ +typedef struct +{ + WCHAR *name; + int *verses; + int aliases; + int chapters; +} +BBOOK; + +/* A VERSE struct refers to a verse in a book of the Bible. + * chapter: the integral chapter number of the verse (1 if the book has only + * one chapter) + * verse: the integral verse number */ +typedef struct +{ + int chapter, verse; +} +VERSE; + +/* A SONGLIST is a list of songs + * num: a string denoting the song number (e.g. "3" or "H10") + * link: a string denoting the hyperlink label for the song + * int: an integer key used to order songs when sorting + * next: next pointer */ +typedef struct songliststruct +{ + WCHAR *num; + WCHAR *link; + int key; + struct songliststruct *next; +} +SONGLIST; + +/* A VERSELIST is the list of verses in a particular book of the Bible at which + * the set of songs referencing that verse differs from the set of songs + * referencing the previous verse in the book. Such a list allows us to + * sparsely represent the set of songs referencing each verse in the book + * without having to create a distinct list entry for each verse in the entire + * book. For example, if song "H1" references verses 3:1-10, that would + * represented by a two-element list: + * [ {v={chapter=3, verse=1}, adds=[H1], drops=[]}; + * {v={chapter=3, verse=10}, adds=[], drops=[H1]} ] + * This is more reasonable than creating ten list items to refer to such a + * range. + * v: the verse at which the set of song references is changing + * adds: a list of songs that refer to this verse but not the previous one + * drops: a list of songs that refer to this verse but not the next one + * next: pointer to next verse at which the set of references changes */ +typedef struct verseliststruct +{ + VERSE v; + SONGLIST *adds, *drops; + struct verseliststruct *next; +} +VERSELIST; + +/* The bible global variable stores the array of bible books being used to + * generate the scripture index. The numbooks variable stores the number of + * elements in the bible array. */ +BBOOK *bible = NULL; +int numbooks = 0; + +/* free_bible() + * Free the bible array, set it to NULL, and set numbooks to 0. */ +static void +free_bible() +{ + int i; + + if (!bible) return; + for (i=0; i<numbooks; ++i) + { + if (bible[i].name) free(bible[i].name); + if (bible[i].verses) free(bible[i].verses); + } + free(bible); + bible = NULL; + numbooks = 0; +} + +/* free_songlist(<slist>) + * Free song list <slist>. */ +static void +free_songlist(sl) + SONGLIST *sl; +{ + SONGLIST *next; + + for (; sl; sl=next) + { + if (sl->num) free(sl->num); + if (sl->link) free(sl->link); + next = sl->next; + free(sl); + } +} + +/* free_verselist(<vlist>) + * Free verse list <vlist>. */ +static void +free_verselist(vl) + VERSELIST *vl; +{ + VERSELIST *next; + + for (; vl; vl=next) + { + free_songlist(vl->adds); + free_songlist(vl->drops); + next = vl->next; + free(vl); + } +} + +/* freeidxarray(<array>,<len>) + * Free the array of verselists given by <array>. The array should have + * length <len>. */ +static void +freeidxarray(idx,len) + VERSELIST **idx; + int len; +{ + int i; + + if (!idx) return; + for (i=0; i<len; ++i) free_verselist(idx[i]); + free(idx); +} + +/* readbible(<filename>) + * Read bible data file <filename> into the bible array and set numbooks to + * the number of books read. Return 0 on error or 1 on success. */ +static int +readbible(filename) + const char *filename; +{ + FSTATE fs; + int eof = 0; + WCHAR buf[MAXLINELEN]; + int verses[MAXLINELEN/2+1]; + int arraysize, j; + WCHAR *p1, *p2; + + if (!fileopen(&fs,filename)) return 0; + + bible = NULL; + eof = 0; + for (arraysize=numbooks=0; !eof; ++numbooks) + { + if (!filereadln(&fs,buf,&eof)) + { + free_bible(); + return 0; + } + if (eof) break; + if (*buf == wc_hash) + { + --numbooks; + continue; + } + if (numbooks >= arraysize) + { + BBOOK *temp; + arraysize *= 2; + if (arraysize==0) arraysize=64; + temp = (BBOOK *) realloc(bible,arraysize*sizeof(BBOOK)); + if (!temp) + { + fprintf(stderr, "songidx:%s:%d: too many bible books (out of memory)\n", + fs.filename, fs.lineno); + free_bible(); + return 0; + } + bible = temp; + } + if ((bible[numbooks].name = + (WCHAR *) calloc(ws_strlen(buf)+1,sizeof(WCHAR))) == NULL) + { + fprintf(stderr, "songidx:%s:%d: bible book name too large" + " (out of memory)\n", fs.filename, fs.lineno); + fileclose(&fs); + free_bible(); + return 0; + } + for(j=1, p1=buf, p2=bible[numbooks].name; *p1!=wc_null; ++p1, ++p2) + { + if (*p1 == wc_pipe) + { + ++j; + *p2=wc_null; + } + else *p2=*p1; + } + *p2 = wc_null; + if (j == 0) + { + fprintf(stderr, "songidx:%s:%d: bible book has no name\n", + fs.filename, fs.lineno); + free(bible[numbooks].name); + fileclose(&fs); + free_bible(); + return 0; + } + bible[numbooks].aliases = j; + do + { + if (!filereadln(&fs,buf,&eof)) + { + free_bible(); + return 0; + } + if (eof) + { + fprintf(stderr, "songidx:%s:%d: incomplete bible book entry (book" + " title with no verses)\n", fs.filename, fs.lineno); + free_bible(); + return 0; + } + } + while (*buf == wc_hash); + for (j=0, p1=buf; *p1!=wc_null; ++j) + { + long v = ws_strtol(p1, &p1, 10); + if ((v<=0) || (v>MAXVERSES)) + { + fprintf(stderr, "songidx:%s:%d: illegal verse count for chapter %d\n", + fs.filename, fs.lineno, j+1); + fileclose(&fs); + free_bible(); + return 0; + } + verses[j] = (int) v; + } + if (j == 0) + { + fprintf(stderr, "songidx:%s:%d: book ", fs.filename, fs.lineno); + ws_fputs(bible[numbooks].name, stderr); + fprintf(stderr, " has no chapters\n"); + fileclose(&fs); + free_bible(); + return 0; + } + bible[numbooks].chapters = j; + if ((bible[numbooks].verses = (int *) calloc(j, sizeof(int))) == NULL) + { + fprintf(stderr, "songidx:%s:%d: too many verses (out of memory)\n", + fs.filename, fs.lineno); + fileclose(&fs); + free_bible(); + return 0; + } + for (j=0; j<bible[numbooks].chapters; ++j) + bible[numbooks].verses[j] = verses[j]; + } + + fileclose(&fs); + return 1; +} + +/* ws_stricmp(<string1>,<string2>) + * Case-insensitive, wide-string comparison function. (Some standard C libs + * already have one of these, but not all do; for compatibility I have to + * write my own.) */ +static int +ws_stricmp(p1,p2) + const WCHAR *p1; + const WCHAR *p2; +{ + for (;;) + { + int diff = wc_tolower(*p1) - wc_tolower(*p2); + if (diff != 0) return diff; + if (*p1 == wc_null) return 0; + ++p1; ++p2; + } +} + +/* parseref(<string>,<book>,<verse>) + * Interpret <string> as a scripture reference, and store the NUMBER of the + * book of the Bible it refers to in <book>, and the chapter and verse that + * it refers to in <verse>. Return a pointer into <string> to the suffix of + * <string> that was not parsed. The <book> and <verse> should hold the book + * and verse of the PREVIOUS scripture reference (if any) on entry to the + * function. If book or chapter information is missing, they will be drawn + * from this info. That way, successive calls can correctly parse a run-on + * string like "Philippians 3:1,5; 4:3", inferring that "5" refers to + * "Philippians 3" and "4:3" refers to "Philippians". If the parser + * encounters an error in processing the book name (e.g., a book name was + * specified but not recognized), <book> is set to numbooks. If no chapter + * or no verse is provided (e.g., the reference is just "Philippians" or + * "Philippians 3") then the chapter and/or verse fields of <verse> are set + * to -1. */ +static WCHAR * +parseref(r,book,v) + WCHAR *r; + int *book; + VERSE *v; +{ + WCHAR *p; + WCHAR c; + int i; + const WCHAR *a; + + while (*r==wc_space) ++r; + p = ws_strpbrk(r, ws_lit(",;-")); + if (!p) p=r+ws_strlen(r); + if (p>r) --p; + while ((p>r) && (*p==wc_space)) --p; + while ((p>r) && (wc_isdigit(*p) || (*p==wc_colon))) --p; + if (p>r) + { + if (*p==wc_space) + { + *p++ = wc_null; + for (*book=0; *book<numbooks; ++(*book)) + { + for (i=0, a=bible[*book].name; + i<bible[*book].aliases; + ++i, a+=ws_strlen(a)+1) + if (!ws_stricmp(r,a)) break; + if (i<bible[*book].aliases) break; + } + } + else + { + *book=numbooks; + } + r=p; + v->chapter = v->verse = -1; + } + while (wc_isdigit(*p)) ++p; + if (*p==wc_colon) + { + *p++ = wc_null; + v->chapter = ws_strtol(r,NULL,10); + r=p; + while (wc_isdigit(*p)) ++p; + } + if (r==p) return NULL; + c = *p; + *p = wc_null; + if ((v->chapter < 0) && (*book>=0) && (*book<numbooks) && + (bible[*book].chapters == 1)) + { + /* Special case: This book only has one chapter. */ + v->chapter = 1; + } + if (v->chapter < 0) + v->chapter = ws_strtol(r,NULL,10); + else + v->verse = ws_strtol(r,NULL,10); + *p = c; + while (*p==wc_space) ++p; + if (*p && (*p!=wc_comma) && (*p!=wc_semicolon) && (*p!=wc_hyphen)) + return NULL; + return p; +} + +/* vlt(<v1>,<v2>) + * Return 1 if verse <v1> precedes <v2> and 0 otherwise. */ +static int +vlt(v1,v2) + const VERSE v1; + const VERSE v2; +{ + return ((v1.chapter == v2.chapter) ? (v1.verse < v2.verse) + : (v1.chapter < v2.chapter)); +} + +/* vcpy(<vdest>,<vsrc>) + * Copy the data from verse <vsrc> into verse <vdest>. */ +static void +vcpy(vdest,vsrc) + VERSE *vdest; + const VERSE vsrc; +{ + vdest->chapter = vsrc.chapter; + vdest->verse = vsrc.verse; +} + +/* getvcount(<book>,<chapter>) + * Return the number of verses in chapter <chapter> of book number <book> of + * the Bible. */ +static int +getvcount(book,chapter) + int book; + int chapter; +{ + if ((book<0) || (book>=numbooks)) + { + /* This should never happen. */ + fprintf(stderr, "songidx: Internal error (getvcount):" + " Book %d out of range.\n", book); + return 0; + } + if ((chapter<1) || (chapter > bible[book].chapters)) + return 0; + return bible[book].verses[chapter-1]; +} + +/* vinc(<book>,<verse>) + * Return a reference to the verse immediately following <verse> in book + * number <book> of the Bible. The verse returned is static, so successive + * calls will overwrite it. If <verse> is the last verse in <book>, the + * return value will reference verse 1 of a non-existant chapter. */ +static VERSE +vinc(book,v) + int book; + const VERSE v; +{ + static VERSE vret; + int cnt; + + vret.chapter = v.chapter; + vret.verse = v.verse + 1; + cnt = getvcount(book, vret.chapter); + if (cnt <= 0) + { + /* This should never happen. */ + fprintf(stderr, "songidx: Internal Error (vinc): Book "); + ws_fputs(bible[book].name, stderr); + fprintf(stderr, " has no chapter %d.\n", vret.chapter); + } + else if (vret.verse > cnt) + { + ++vret.chapter; + vret.verse = 1; + } + return vret; +} + +/* vdec(<book>,<verse>) + * Return a reference to the verse immediately preceding <verse> in book + * number <book> of the Bible. The verse returned is static, so successive + * calls will overwrite it. If <verse> is the first verse in <book>, the + * return value will reference chapter -1 and an error message will be + * printed to stderr. */ +static VERSE +vdec(book,v) + int book; + const VERSE v; +{ + static VERSE vret; + vret.chapter = v.chapter; + vret.verse = v.verse - 1; + if (vret.verse < 1) + { + --vret.chapter; + vret.verse = getvcount(book, vret.chapter); + if (vret.verse <= 0) + { + /* This should never happen. */ + fprintf(stderr, "songidx: Internal Error (vdec): Book "); + ws_fputs(bible[book].name, stderr); + fprintf(stderr, " has no chapter %d.\n", vret.chapter); + vret.verse = -1; + } + } + return vret; +} + +/* addref(<reflist>,<song>,<link>,<key>) + * Add a reference to song (<song>,<link>,<key>) to REFLIST <reflist>. The + * <reflist> is assumed to be sorted by <key>. If a reference with the same + * <key> already exists, <reflist> is left unchanged. Both <song> and + * <link> are COPIED into the list. Return 1 on success or 0 on failure. */ +static int +addref(sl,n,l,key) + SONGLIST **sl; + const WCHAR *n; + const WCHAR *l; + int key; +{ + SONGLIST *newsong; + + for (; *sl; sl=&((*sl)->next)) + if ((*sl)->key >= key) break; + if (*sl && ((*sl)->key == key)) return 1; + + if ((newsong = (SONGLIST *) malloc(sizeof(SONGLIST))) == NULL) + { + fprintf(stderr, "songidx: too many scripture references (out of memory)\n"); + return 0; + } + newsong->num = newsong->link = NULL; + if (((newsong->num = (WCHAR *) calloc(ws_strlen(n)+1, + sizeof(WCHAR))) == NULL) || + ((newsong->link = (WCHAR *) calloc(ws_strlen(l)+1, + sizeof(WCHAR))) == NULL)) + { + fprintf(stderr, "songidx: too many scripture references (out of memory)\n"); + if (newsong->num) free(newsong->num); + if (newsong->link) free(newsong->link); + free(newsong); + return 0; + } + ws_strcpy(newsong->num, n); + ws_strcpy(newsong->link, l); + newsong->key = key; + newsong->next = *sl; + *sl = newsong; + return 1; +} + +/* insertref(<type>,<verselist>,<verse>,<song>,<link>,<key>) + * If <type> is INSERT_ADD, then insert song (<song>,<link>,<key>) into the + * set of "adds" for verse <verse> in <verselist>. If <type> is INSERT_DROP, + * then insert the song into the set of "drops" for verse <verse>. In either + * case, create a new entry for <verse> in <verselist> if it doesn't already + * exist. Return 1 on success or 0 on failure. */ +#define INSERT_ADD 1 +#define INSERT_DROP 0 + +static int +insertref(type,vpp,v,n,l,key) + char type; + VERSELIST **vpp; + const VERSE v; + const WCHAR *n; + const WCHAR *l; + int key; +{ + for (;;) + { + if (!*vpp || vlt(v, (*vpp)->v)) + { + VERSELIST *vnew = NULL; + SONGLIST *sl = NULL; + if (((vnew = (VERSELIST *) malloc(sizeof(VERSELIST))) == NULL) || + ((sl = (SONGLIST *) malloc(sizeof(SONGLIST))) == NULL)) + { + fprintf(stderr, "songidx: too many scripture references" + " (out of memory)\n"); + if (vnew) free(vnew); + if (sl) free(sl); + return 0; + } + sl->num = sl->link = NULL; + if (((sl->num = (WCHAR *) calloc(ws_strlen(n)+1, + sizeof(WCHAR))) == NULL) || + ((sl->link = (WCHAR *) calloc(ws_strlen(l)+1, + sizeof(WCHAR))) == NULL)) + { + fprintf(stderr, "songidx: too many scripture references" + " (out of memory)\n"); + if (sl->num) free(sl->num); + if (sl->link) free(sl->link); + free(vnew); + free(sl); + return 0; + } + ws_strcpy(sl->num, n); + ws_strcpy(sl->link, l); + sl->key = key; + sl->next = NULL; + vcpy(&(vnew->v), v); + if (type) + { + vnew->adds = sl; + vnew->drops = NULL; + } + else + { + vnew->adds = NULL; + vnew->drops = sl; + } + vnew->next = *vpp; + *vpp = vnew; + return 1; + } + else if (!vlt((*vpp)->v, v)) + { + return addref(type ? &((*vpp)->adds) : &((*vpp)->drops), n, l, key); + } + vpp=&((*vpp)->next); + } +} + +/* addrefs(<dest>,<src>) + * Take the union of SONGLIST <dest> and SONGLIST <src>, storing the result + * in <dest>. Return 1 on success, or 0 on failure. */ +static int +addrefs(dest,src) + SONGLIST **dest; + const SONGLIST *src; +{ + for (; src; src=src->next) + if (!addref(dest, src->num, src->link, src->key)) return 0; + return 1; +} + +/* removerefs(<dest>,<src>) + * Take the set difference between SONGLIST <dest> and SONGLIST <src>, + * storing the result in <dest>. */ +static void +removerefs(dest,src) + SONGLIST **dest; + const SONGLIST *src; +{ + SONGLIST **sl; + + for (; src; src=src->next) + { + for (sl=dest; *sl; sl=&((*sl)->next)) + if ((*sl)->key >= src->key) break; + if (*sl && ((*sl)->key == src->key)) + { + SONGLIST *temp = (*sl)->next; + free((*sl)->num); + free((*sl)->link); + free(*sl); + *sl = temp; + } + } +} + +/* slcmp(<slist1>,<slist2>) + * Compare SONGLIST's <slist1> and <slist2>, returning 1 if they are + * different and 0 if they are the same. */ +static int +slcmp(s1,s2) + const SONGLIST *s1; + const SONGLIST *s2; +{ + for (; s1 && s2; s1=s1->next, s2=s2->next) + if (s1->key != s2->key) return 1; + return (s1 || s2); +} + +/* print_vrange(<file>,<book>,<verse1>,<verse2>,<lastchapter>) + * Output LaTeX material to file handle <file> for verse range + * <verse1>-<verse2> of book number <book>. Depending on <lastchapter>, + * the outputted material might be the start of a new index entry or the + * continuation of a previous entry. If <lastchapter> is 0, start a new + * index entry. If <lastchapter> is >0, continue the previous entry and + * print the chapter of <verse1> only if it differs from <lastchapter>. + * If <lastchapter> is <0, continue the previous entry and always print the + * chapter number of <verse1>. Return 0 on success or -1 on failure. */ +static int +print_vrange(f,book,v1,v2,lastchapter) + FILE *f; + int book; + const VERSE v1; + const VERSE v2; + int lastchapter; +{ + if (ws_fputs(lastchapter ? ws_lit(",") : ws_lit("\\idxentry{"), f) < 0) + return -1; + if (v1.verse <= 0) + { + if (ws_fprintf1(f, lastchapter ? ws_lit("\\thinspace %d") : ws_lit("%d"), + v1.chapter) < 0) + return -1; + } + else if ((book>=0) && (book<numbooks) && (bible[book].chapters == 1)) + { + /* This book has only one chapter. */ + if (ws_fprintf1(f, lastchapter ? ws_lit("\\thinspace %d") : ws_lit("%d"), + v1.verse) < 0) + return -1; + } + else if ((lastchapter<=0) || (lastchapter!=v1.chapter) || + (v1.chapter!=v2.chapter)) + { + if (ws_fprintf2(f, lastchapter ? ws_lit(" %d:%d") : ws_lit("%d:%d"), + v1.chapter, v1.verse) < 0) + return -1; + } + else + { + if (ws_fprintf1(f, lastchapter ? ws_lit("\\thinspace %d") : ws_lit("%d"), + v1.verse) < 0) + return -1; + } + + if (vlt(v1,v2)) + { + if (v2.verse <= 0) + { + if (ws_fprintf1(f, ws_lit("--%d"), v2.chapter) < 0) return -1; + } + else if (v1.chapter!=v2.chapter) + { + if (ws_fprintf2(f, ws_lit("--%d:%d"), v2.chapter, v2.verse) < 0) + return -1; + } + else + { + if (ws_fprintf1(f, ws_lit("--%d"), v2.verse) < 0) return -1; + } + } + return 0; +} + +/* print_reflist(<file>,<slist>) + * Output the list of song references given by <slist> to file handle <file>. + * Return 0 on success or -1 on failure. */ +static int +print_reflist(f,sp) + FILE *f; + const SONGLIST *sp; +{ + char first; + for (first=1; sp; sp=sp->next) + { + if (!first) + { + if (ws_fputs(ws_lit("\\\\"), f) < 0) return -1; + } + first = 0; + if ((ws_fputs(ws_lit("\\hyperlink{"), f) < 0) || + (ws_fputs(sp->link, f) < 0) || + (ws_fputs(ws_lit("}{"), f) < 0) || + (ws_fputs(sp->num, f) < 0) || + (ws_fputs(ws_lit("}"), f) < 0)) + return -1; + } + return 0; +} + +/* genscriptureindex(<fstate>,<outname>,<biblename>) + * Generate a LaTeX file named <outname> containing material suitable to + * typeset the scripture index data found in input file handle <file>. + * Input Bible data from an ascii file named <biblename>. Return 0 on + * success, 1 if there were warnings, and 2 if there was a fatal error. */ +int +genscriptureindex(fs,outname,biblename) + FSTATE *fs; + const char *outname; + const char *biblename; +{ + FILE *f; + int eof = 0; + VERSELIST **idx; + int book, book2; + WCHAR ref[MAXLINELEN], songnum[MAXLINELEN], linkname[MAXLINELEN]; + int songcount; + WCHAR *p; + VERSE v1, v2; + char hadwarnings = 0; + + fprintf(stderr, "songidx: Parsing scripture index data file %s...\n", + fs->filename); + + /* Read the bible data file into the bible array. */ + if (!readbible(biblename)) return 2; + if ((idx = (VERSELIST **) calloc(numbooks, sizeof(VERSELIST *))) == NULL) + { + fprintf(stderr, "songidx: too many books of the bible (out of memory)\n"); + free_bible(); + return 2; + } + for (book=0; book<numbooks; ++book) idx[book] = NULL; + + /* Walk through the input file and construct a VERSELIST for each book + * of the Bible. Each VERSELIST represents the set of verses in that book + * referred to by songs in the song book. */ + songcount = 0; + book = -1; + v1.chapter = -1; + v1.verse = -1; + for (;;) + { + /* Get the next song data record. */ + if (!filereadln(fs,ref,&eof)) + { + freeidxarray(idx,numbooks); + free_bible(); + return 2; + } + if (eof) break; + if (!filereadln(fs,songnum,&eof) || eof || + !filereadln(fs,linkname,&eof) || eof) + { + if (eof) + { + fprintf(stderr, "songidx:%s:%d: incomplete entry\n", + fs->filename, fs->lineno); + fileclose(fs); + } + freeidxarray(idx,numbooks); + free_bible(); + return 2; + } + + /* Add all scripture references for this song to the VERSELIST array */ + p = ref; + while (*p != wc_null) + { + /* Parse the next reference. */ + p = parseref(p, &book, &v1); + if (!p) + { + fprintf(stderr, "songidx:%s:%d: WARNING: Malformed scripture reference" + " for song ", fs->filename, fs->lineno); + ws_fputs(songnum, stderr); + fprintf(stderr, ". Ignoring it.\n"); + hadwarnings = 1; + break; + } + if (book<0) + { + fprintf(stderr, "songidx:%s:%d: WARNING: Scripture reference for song ", + fs->filename, fs->lineno); + ws_fputs(songnum, stderr); + fprintf(stderr, " doesn't include a book name. Ignoring it.\n"); + hadwarnings = 1; + break; + } + if (book>=numbooks) + { + fprintf(stderr, "songidx:%s:%d: WARNING: Scripture reference for song ", + fs->filename, fs->lineno); + ws_fputs(songnum, stderr); + fprintf(stderr, " references unknown book. Ignoring it.\n"); + hadwarnings = 1; + break; + } + if (v1.chapter < 1) + { + fprintf(stderr, "songidx:%s:%d: WARNING: Scripture reference for song ", + fs->filename, fs->lineno); + ws_fputs(songnum, stderr); + fprintf(stderr, " has no chapter or verse. Ignoring it.\n"); + hadwarnings = 1; + break; + } + /* Unless the reference ended in a "-", it consists of just one verse. */ + v2.chapter = v1.chapter; + v2.verse = v1.verse; + if (*p==wc_hyphen) + { + /* This reference ended in a "-", which means it starts a range. + * Parse the next reference to find the range's end. */ + book2 = book; + p = parseref(++p, &book2, &v2); + if (!p) + { + fprintf(stderr, "songidx:%s:%d: WARNING: Malformed scripture" + " reference for song ", fs->filename, fs->lineno); + ws_fputs(songnum, stderr); + fprintf(stderr, ". Ignoring it.\n"); + hadwarnings = 1; + break; + } + if (book2!=book) + { + fprintf(stderr, "songidx:%s:%d: WARNING: Scripture reference for" + " song ", fs->filename, fs->lineno); + ws_fputs(songnum, stderr); + fprintf(stderr, " appears to span books! Ignoring it.\n"); + hadwarnings = 1; + break; + } + } + if (*p!=wc_null) ++p; + /* If either the start or end references of the range were incomplete + * (e.g., the range was something like "Philippians 3", where the verse + * numbers are not explicit), try to fill in the missing information + * based on how many verses are in the chapter and how many chapters are + * in the book. */ + if (v1.verse <= 0) v1.verse = 1; + if (v2.verse <= 0) + { + v2.verse = getvcount(book,v2.chapter); + if (v2.verse <= 0) + { + fprintf(stderr, "songidx:%s:%d: WARNING: Scripture reference for " + " song ", fs->filename, fs->lineno); + ws_fputs(songnum, stderr); + fprintf(stderr, " refers implicitly to chapter %d of ", v2.chapter); + ws_fputs(bible[book].name, stderr); + fprintf(stderr, ", which doesn't exist. Ignoring it.\n"); + hadwarnings = 1; + break; + } + } + if (v1.verse > getvcount(book,v1.chapter)) + { + fprintf(stderr, "songidx:%s:%d: WARNING: Song ", + fs->filename, fs->lineno); + ws_fputs(songnum, stderr); + fprintf(stderr, " references "); + ws_fputs(bible[book].name, stderr); + fprintf(stderr, " %d:%d, which doesn't exist! Ignoring it.\n", + v1.chapter, v1.verse); + hadwarnings = 1; + break; + } + if (v2.verse > getvcount(book,v2.chapter)) + { + fprintf(stderr, "songidx:%s:%d: WARNING: Song ", + fs->filename, fs->lineno); + ws_fputs(songnum, stderr); + fprintf(stderr, " references "); + ws_fputs(bible[book].name, stderr); + fprintf(stderr, " %d:%d, which doesn't exist! Ignoring it.\n", + v2.chapter, v2.verse); + hadwarnings = 1; + break; + } + + /* Add the range to the SONGLIST array. */ + if (!insertref(INSERT_ADD, &(idx[book]), v1, songnum, linkname, + songcount) || + !insertref(INSERT_DROP, &(idx[book]), v2, songnum, linkname, + songcount)) + { + fileclose(fs); + freeidxarray(idx,numbooks); + free_bible(); + return 2; + } + } + /* finished adding all refs for this song */ + ++songcount; + } + /* finished adding all refs for all songs */ + fileclose(fs); + + /* Now create the index .sbx file */ + fprintf(stderr, "songidx: Generating scripture index TeX file %s...\n", + outname); + if (strcmp(outname,"-")) + { + if ((f = fopen(outname, "w")) == NULL) + { + fprintf(stderr, "songidx: Unable to open %s for writing.\n", outname); + freeidxarray(idx,numbooks); + free_bible(); + return 2; + } + } + else + { + f = stdout; + outname = "stdout"; + } + +#define TRYWRITE(x) \ + if (!(x)) \ + { \ + fprintf(stderr, "songidx:%s: write error\n", outname); \ + if (f == stdout) fflush(f); else fclose(f); \ + freeidxarray(idx,numbooks); \ + free_bible(); \ + return 2; \ + } + + /* For each book of the Bible that has songs that reference it, go through + * its VERSELIST and generate a sequence of index entries. Wherever + * possible, compact adjacent entries that have identical SONGLISTS so that + * we never have two consecutive index entries with identical right-hand + * sides. */ + for (book=0; book<numbooks; ++book) + if (idx[book]) + { + VERSE vrstart; + VERSELIST *vp; + SONGLIST *sl = NULL; + int lastchapter; /* 0=none, -1=force printing of chapter */ + + TRYWRITE((ws_fputs(ws_lit("\\begin{idxblock}{"), f) >=0) + && (ws_fputs(bible[book].name, f) >= 0) + && (ws_fputs(ws_lit("}\n"), f) >=0)) + lastchapter = 0; + vcpy(&vrstart, idx[book]->v); + for (vp=idx[book]; vp; vp=vp->next) + { + if (!addrefs(&sl, vp->adds)) + { + fclose(f); + freeidxarray(idx,numbooks); + free_bible(); + return 2; + } + if (!slcmp(vp->drops, vp->next ? vp->next->adds : NULL)) + { + /* Set of drops here equals set of adds next time. There's at least + * a chance that we can combine this item and the next one into a + * single index entry. */ + if (vp->next && !vlt(vinc(book,vp->v), vp->next->v)) + { + /* If the next item is adjacent to this one, do nothing. Just let + * the range in progress be extended. We'll output a single entry + * for all of these adjacent verses when we reach the end. */ + continue; + } + else if (vp->next && !slcmp(sl, vp->drops)) + { + /* Otherwise, if the next item is not adjacent but all refs are + * dropped here, then print a partial entry to be continued with a + * comma next time. */ + TRYWRITE(print_vrange(f, book, vrstart, vp->v, lastchapter) >= 0) + lastchapter = (vrstart.chapter != vp->v.chapter) ? + -1 : vp->v.chapter; + vcpy(&vrstart, vp->next->v); + continue; + } + } + if (vp->drops) + { + /* Some songs get dropped here, and either the next item is not + * adjacent to this one, or it's adjacent and the set of adds is not + * the same. In either case, that means the set of refs changes at + * this point, so we need to output a full entry (or finish the one + * in progress). */ + TRYWRITE((print_vrange(f, book, vrstart, vp->v, lastchapter) >= 0) && + (ws_fputs(ws_lit("}{"), f) >= 0) && + (print_reflist(f, sl) >= 0) && + (ws_fputs(ws_lit("}\n"), f) >= 0)) + removerefs(&sl, vp->drops); + lastchapter = 0; + vcpy(&vrstart, (!sl && vp->next) ? vp->next->v : vinc(book,vp->v)); + } + if (sl && vp->next && vp->next->adds && vlt(vrstart, vp->next->v)) + { + /* There are verses between this item and the next which have refs, + * but the refs change at the beginning of the next item. Make an + * entry for the intermediate block of verses. */ + VERSE vrend; + vcpy(&vrend, vdec(book,vp->next->v)); + TRYWRITE((print_vrange(f, book, vrstart, vrend, lastchapter) >= 0) && + (ws_fputs(ws_lit("}{"), f) >= 0) && + (print_reflist(f, sl) >= 0) && + (ws_fputs(ws_lit("}\n"), f) >= 0)) + lastchapter = 0; + vcpy(&vrstart, vp->next->v); + } + } + if (sl) + { + fprintf(stderr, "songidx: scripture references in "); + ws_fputs(bible[book].name, stderr); + fprintf(stderr, " appear to extend past the end of the book!" + " Aborting.\n"); + fclose(f); + freeidxarray(idx,numbooks); + free_bible(); + return 2; + } + TRYWRITE(ws_fputs(ws_lit("\\end{idxblock}\n"), f) >= 0) + } + +#undef TRYWRITE + + fclose(f); + freeidxarray(idx,numbooks); + free_bible(); + + return hadwarnings; +} |