1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
/* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}
|