/* Copyright (C) 2009 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 title index file from a title .sxd file. */ #ifdef HAVE_CONFIG_H # include "config.h" #else # include "vsconfig.h" #endif #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_STDIO_H # include #endif #ifdef HAVE_STRING_H # include #else # ifdef HAVE_STRINGS_H # include # endif #endif #include "chars.h" #include "songidx.h" #include "fileio.h" /* By convention, some word prefixes always get moved to the end of a title before it is indexed. For * example, in English "The Song Title" becomes "Song Title, The". The following structure stores the * list of these prefix words. The default is "The" and "A". */ typedef struct prefix { struct prefix *next; WCHAR *s; } PREFIX; PREFIX pre_A = { NULL, ws_lit("A") }; PREFIX pre_defaults = { &pre_A, ws_lit("The") }; /* freeprefixes() * Free prefix list . */ static void freeprefixes(pl) PREFIX *pl; { if (pl == &pre_defaults) return; while (pl) { PREFIX *nxt = pl->next; free(pl->s); free(pl); pl = nxt; } } /* rotate(, <prelist>) * If the first word of <title> is any word in list <prelist>, then <title> is modified in-place so * that the word is shifted to the end of the string and preceded by a comma and a space. So for * example, rotate("The Title",<prelist>) where <prelist> is the default results in "Title, The". * Words in <prelist> are matched case-insensitively. If <title> begins with the marker character '*', * that character is ignored and left unchanged. Note that <title> MUST be allocated with ONE EXTRA * WCHAR OF STORAGE in order to leave room for the extra comma. * Return Value: 1 if <title> was modified and 0 otherwise. */ static int rotate(s,pl) WCHAR *s; PREFIX *pl; { const WCHAR *s2, *w2; w2 = NULL; if (*s==wc_asterisk) ++s; for (; pl; pl=pl->next) { for (s2=s, w2=pl->s; *w2; ++s2, ++w2) if (wc_tolower(*s2)!=wc_tolower(*w2)) break; if ((*w2 == wc_null) && (*s2 != wc_null) && !wc_isalpha(*s2)) break; } if (pl) { WCHAR buf[MAXLINELEN]; size_t slen = ws_strlen(s); size_t wlen = w2 - pl->s; ws_strncpy(buf, s, wlen); buf[wlen] = wc_null; ws_memmove(s, s+wlen+1, slen-wlen-1); *s = wc_toupper(*s); *(s+slen-wlen-1) = wc_comma; *(s+slen-wlen) = wc_space; ws_strcpy(s+slen-wlen+1, buf); return 1; } return 0; } /* getstartchar(<string>,<default>) * Returns the first non-escaped, alphabetic character of <string>. If <string> has no non-escaped, * alphabetic characters, <default> is returned instead. */ static WCHAR getstartchar(s,def) const WCHAR *s; WCHAR def; { if (!s) return def; skipesc(&s); while ((*s!=wc_null) && !wc_isalpha(*s)) { ++s; skipesc(&s); } return (*s!=wc_null) ? wc_toupper(*s) : def; } /* freesongarray(<array>,<len>) * Free song array <array>, which should have length <len>. */ static void freesongarray(songs,len) SONGENTRY **songs; int len; { int i; if (!songs) return; for (i=0; i<len; ++i) { if (songs[i]) { if (songs[i]->title) free(songs[i]->title); if (songs[i]->num) free(songs[i]->num); if (songs[i]->linkname) free(songs[i]->linkname); free(songs[i]); } } free(songs); } /* gentitleindex(<file>,<outname>) * Reads a title index data file from file handle <file> and generates a new file named <outfile> * containing a LaTeX title index. * Return Value: 0 on success, 1 on warnings, or 2 on failure */ int gentitleindex(fs,outname) FSTATE *fs; const char *outname; { FILE *f; int eof = 0; int numsongs, arraysize, i; PREFIX *prelist = &pre_defaults; SONGENTRY **songs; WCHAR buf[MAXLINELEN], *bp; WCHAR startchar; fprintf(stderr, "songidx: Parsing title index data file %s...\n", fs->filename); songs = NULL; eof = 0; for (arraysize=numsongs=0; !eof; ++numsongs) { if (!filereadln(fs,buf,&eof)) { freesongarray(songs,numsongs); return 2; } if (eof) break; if (buf[0] == wc_percent) { if (!ws_strncmp(buf, wc_lit("%prefix "), 8)) { PREFIX *temp = (prelist==&pre_defaults) ? NULL : prelist; prelist = (PREFIX *) malloc(sizeof(PREFIX)); prelist->next = temp; prelist->s = (WCHAR *) calloc(ws_strlen(buf)-7, sizeof(WCHAR)); ws_strcpy(prelist->s, buf+8); } --numsongs; continue; } if (numsongs >= arraysize) { SONGENTRY **temp; arraysize *= 2; if (arraysize==0) arraysize=64; temp = (SONGENTRY **) realloc(songs,arraysize*sizeof(SONGENTRY *)); if (!temp) { fprintf(stderr, "songidx:%s:%d: too many songs (out of memory)\n", fs->filename, fs->lineno); freeprefixes(prelist); freesongarray(songs,numsongs); return 2; } songs = temp; } if ((songs[numsongs] = (SONGENTRY *) malloc(sizeof(SONGENTRY))) == NULL) { fprintf(stderr, "songidx:%s:%d: too many songs (out of memory)\n", fs->filename, fs->lineno); freeprefixes(prelist); freesongarray(songs,numsongs); return 2; } songs[numsongs]->title = songs[numsongs]->num = songs[numsongs]->linkname = NULL; for (bp=buf+ws_strlen(buf)-1; (bp > buf) && wc_isspace(*bp); --bp) ; if (wc_isspace(*bp)) *bp = wc_null; for (bp=buf; wc_isspace(*bp); ++bp) ; /* The following allocates one extra char of storage because we might add a comma later. */ if ((songs[numsongs]->title = (WCHAR *) calloc(ws_strlen(bp)+2, sizeof(WCHAR))) == NULL) { fprintf(stderr, "songidx:%s:%d: too many songs (out of memory)\n", fs->filename, fs->lineno); freeprefixes(prelist); freesongarray(songs,numsongs+1); return 2; } ws_strcpy(songs[numsongs]->title, bp); rotate(songs[numsongs]->title, prelist); if (!filereadln(fs,buf,&eof)) { freeprefixes(prelist); freesongarray(songs,numsongs+1); return 2; } if (eof) { fprintf(stderr, "songidx:%s:%d: incomplete song entry (orphan title)\n", fs->filename, fs->lineno); freeprefixes(prelist); freesongarray(songs,numsongs+1); return 2; } if ((songs[numsongs]->num = (WCHAR *) calloc(ws_strlen(buf)+1, sizeof(WCHAR))) == NULL) { fprintf(stderr, "songidx:%s:%d: too many songs (out of memory)\n", fs->filename, fs->lineno); freeprefixes(prelist); freesongarray(songs,numsongs+1); return 2; } ws_strcpy(songs[numsongs]->num, buf); if (!filereadln(fs,buf,&eof)) { freeprefixes(prelist); freesongarray(songs,numsongs+1); return 2; } if (eof) { fprintf(stderr, "songidx:%s:%d: incomplete song entry (missing hyperlink)\n", fs->filename, fs->lineno); freeprefixes(prelist); freesongarray(songs,numsongs+1); return 2; } if ((songs[numsongs]->linkname = (WCHAR *) calloc(ws_strlen(buf)+1, sizeof(WCHAR))) == NULL) { fprintf(stderr, "songidx:%s:%d: too many songs (out of memory)\n", fs->filename, fs->lineno); freeprefixes(prelist); freesongarray(songs,numsongs); return 2; } ws_strcpy(songs[numsongs]->linkname, buf); songs[numsongs]->idx = numsongs; } fileclose(fs); freeprefixes(prelist); /* Sort the song array */ qsort(songs, numsongs, sizeof(*songs), songcmp); /* Write the sorted data out to the output file. */ fprintf(stderr, "songidx: Generating title 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); 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); \ freesongarray(songs,numsongs); \ return 2; \ } startchar = 'A'; if (numsongs>0) { startchar = getstartchar(songs[0]->title, wc_capA); TRYWRITE((ws_fputs(ws_lit("\\begin{idxblock}{"), f) >= 0) && (ws_fputc(startchar, f)==startchar)) } for (i=0; i<numsongs; ++i) { WCHAR c = getstartchar(songs[i]->title, startchar); if ((i>0) && !ws_strcmp(songs[i]->title, songs[i-1]->title)) { TRYWRITE((ws_fputs(ws_lit("\\\\\\hyperlink{"), f) >= 0) && (ws_fputs(songs[i]->linkname, f) >= 0) && (ws_fputs(ws_lit("}{"), f) >= 0) && (ws_fputs(songs[i]->num, f) >= 0) && (ws_fputs(ws_lit("}"), f) >= 0)) continue; } else { TRYWRITE(ws_fputs(ws_lit("}\n"), f) >= 0) } if (startchar != c) { startchar = c; TRYWRITE((ws_fputs(ws_lit("\\end{idxblock}\n\\begin{idxblock}{"), f) >= 0) && (ws_fputc(startchar, f)==startchar) && (ws_fputs(ws_lit("}\n"), f) >= 0)) } if (songs[i]->title[0] == wc_asterisk) { TRYWRITE((ws_fputs(ws_lit("\\idxaltentry{"), f) >= 0) && (ws_fputs(songs[i]->title+1, f) >= 0) && (ws_fputs(ws_lit("}{\\hyperlink{"), f) >= 0) && (ws_fputs(songs[i]->linkname, f) >= 0) && (ws_fputs(ws_lit("}{"), f) >= 0) && (ws_fputs(songs[i]->num, f) >= 0) && (ws_fputs(ws_lit("}"), f) >= 0)) } else { TRYWRITE((ws_fputs(ws_lit("\\idxentry{"), f) >= 0) && (ws_fputs(songs[i]->title, f) >= 0) && (ws_fputs(ws_lit("}{\\hyperlink{"), f) >= 0) && (ws_fputs(songs[i]->linkname, f) >= 0) && (ws_fputs(ws_lit("}{"), f) >= 0) && (ws_fputs(songs[i]->num, f) >= 0) && (ws_fputs(ws_lit("}"), f) >= 0)) } } TRYWRITE(ws_fputs(ws_lit("}\n\\end{idxblock}\n"), f) >= 0) #undef TRYWRITE if (f == stdout) fflush(f); else fclose(f); freesongarray(songs,numsongs); return 0; }