/* mktext -- resolve a lectionary's verse keys against a scripture corpus and * write the LZSS-packed text clectio compiles in. This is what makes clectio * bring-your-own-Bible: point it at any corpus formatted like ours (a 6-column * TSV: Book, Abbrev, BookNum, Chapter, Verse, Text) and it produces the text for * exactly the verses the calendar cites -- nothing more. The corpus must use the * same (Vulgate) chapter/verse numbering as the keys. * * usage: mktext verses.keys corpus.tsv out.lz * out.lz = [uint32 LE rawlen][LZSS-packed "chap:verse text\n" lines]. */ #define LZ_PACK #include "lz.h" #include #include #include typedef struct { char *key; /* "Book\tChap\tVerse" */ char *cv; /* "Chap:Verse" */ char *text; /* verse text */ } Rec; static Rec *recs; static size_t nrec; static int cmp(const void *a, const void *b) { return strcmp(((const Rec *)a)->key, ((const Rec *)b)->key); } static char *slurp(const char *path, size_t *len) { FILE *f = fopen(path, "rb"); if (!f) { fprintf(stderr, "mktext: cannot open %s\n", path); exit(1); } fseek(f, 0, SEEK_END); long n = ftell(f); fseek(f, 0, SEEK_SET); char *buf = malloc((size_t)n + 1); if (fread(buf, 1, (size_t)n, f) != (size_t)n) { fprintf(stderr, "mktext: read error\n"); exit(1); } buf[n] = 0; fclose(f); *len = (size_t)n; return buf; } /* split a TSV line into fields in place; return field count (max 6). */ static int fields(char *line, char *f[6]) { int n = 0; f[n++] = line; for (char *p = line; *p && n < 6; p++) if (*p == '\t') { *p = 0; f[n++] = p + 1; } return n; } int main(int argc, char **argv) { if (argc != 4) { fprintf(stderr, "usage: mktext verses.keys corpus.tsv out.lz\n"); return 2; } size_t klen, tlen; char *keys = slurp(argv[1], &klen); char *tsv = slurp(argv[2], &tlen); /* index the corpus by (Book\tChap\tVerse) */ size_t cap = 4096; recs = malloc(cap * sizeof(Rec)); for (char *line = strtok(tsv, "\n"); line; line = strtok(NULL, "\n")) { char *fl[6]; if (fields(line, fl) < 6) continue; if (nrec == cap) { cap *= 2; recs = realloc(recs, cap * sizeof(Rec)); } size_t kl = strlen(fl[0]) + strlen(fl[3]) + strlen(fl[4]) + 3; char *key = malloc(kl); snprintf(key, kl, "%s\t%s\t%s", fl[0], fl[3], fl[4]); size_t cl = strlen(fl[3]) + strlen(fl[4]) + 2; char *cv = malloc(cl); snprintf(cv, cl, "%s:%s", fl[3], fl[4]); recs[nrec].key = key; recs[nrec].cv = cv; recs[nrec].text = fl[5]; nrec++; } qsort(recs, nrec, sizeof(Rec), cmp); /* resolve each key line to "chap:verse text\n" */ size_t obuf = 1 << 20, olen = 0; char *out = malloc(obuf); size_t missing = 0, nkeys = 0; for (char *line = strtok(keys, "\n"); line; line = strtok(NULL, "\n")) { nkeys++; Rec probe; probe.key = line; Rec *r = bsearch(&probe, recs, nrec, sizeof(Rec), cmp); const char *cv = "0:0", *text = ""; if (r) { cv = r->cv; text = r->text; } else { missing++; /* keep a placeholder so indices stay aligned */ } size_t need = strlen(cv) + strlen(text) + 3; while (olen + need > obuf) { obuf *= 2; out = realloc(out, obuf); } olen += (size_t)snprintf(out + olen, need, "%s %s", cv, text); out[olen++] = '\n'; } if (missing) fprintf(stderr, "mktext: warning: %zu of %zu verses not in corpus (blank)\n", missing, nkeys); size_t plen; unsigned char *packed = lz_pack((unsigned char *)out, olen, &plen); FILE *of = fopen(argv[3], "wb"); if (!of) { fprintf(stderr, "mktext: cannot write %s\n", argv[3]); return 1; } unsigned char hdr[4] = { (unsigned char)olen, (unsigned char)(olen >> 8), (unsigned char)(olen >> 16), (unsigned char)(olen >> 24) }; fwrite(hdr, 1, 4, of); fwrite(packed, 1, plen, of); fclose(of); fprintf(stderr, "mktext: %zu verses, %zu -> %zu bytes (%.1fx)\n", nkeys, olen, plen, olen ? (double)olen / (double)plen : 0); return 0; }