summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/doc/latex/songs/songidx/scripidx.c
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2013-04-17 23:19:43 +0000
committerKarl Berry <karl@freefriends.org>2013-04-17 23:19:43 +0000
commitaaaec2f57c537ee18ea0a59c2cfd7534d5b2b188 (patch)
treed54aac03a0b78c2e01c9f8ebb68302ebccb3e641 /Master/texmf-dist/doc/latex/songs/songidx/scripidx.c
parent0c5f06400403c02946d916560fc5cea65e1abe4b (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.c1105
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;
+}