aboutsummaryrefslogtreecommitdiff
path: root/mktext.c
blob: 55af074a94431174b777c95a532f0738078c7d0d (plain) (blame)
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;
}