summaryrefslogtreecommitdiff
path: root/internal/bible
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-28 12:52:13 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-28 12:52:13 +0200
commit44472bbe31475a3c672ae003d928d7caffb4b50a (patch)
treee74bae03c61b62479ef4f68b046e3345618933c8 /internal/bible
parent626f2275438a37e5cd2efb5964281b9bef5ce8cc (diff)
parent7b220084cf3951c8cde0582efdfcf628afc64336 (diff)
downloadlectio-44472bbe31475a3c672ae003d928d7caffb4b50a.tar.gz
lectio-44472bbe31475a3c672ae003d928d7caffb4b50a.zip
Merge branch 'of-offline-migration': offline daily view, remove scrapers
Diffstat (limited to 'internal/bible')
-rw-r--r--internal/bible/convert.go123
-rw-r--r--internal/bible/convert_test.go19
-rw-r--r--internal/bible/ofref.go71
-rw-r--r--internal/bible/ofref_test.go50
4 files changed, 126 insertions, 137 deletions
diff --git a/internal/bible/convert.go b/internal/bible/convert.go
index e7d5944..6b9a663 100644
--- a/internal/bible/convert.go
+++ b/internal/bible/convert.go
@@ -1,128 +1,15 @@
package bible
-import (
- "fmt"
- "regexp"
- "sort"
- "strconv"
- "strings"
-
- "github.com/lukaszkasprzak/lectio/internal/psalter"
-)
-
-// polishToEn maps Polish Biblical abbreviations to the book name accepted by
-// the kjv-family tools. Gospels use the short forms (Mat/Mark/Luke/John);
-// other books use full names that the tools resolve by unique prefix.
-// Ported verbatim from ewangelia.py POLISH_TO_EN.
-var polishToEn = map[string]string{
- // Old Testament
- "Rdz": "Genesis", "Wj": "Exodus", "Kpł": "Leviticus", "Lb": "Numbers",
- "Pwt": "Deuteronomy", "Joz": "Joshua", "Sdz": "Judges", "Rt": "Ruth",
- "1 Sm": "1 Samuel", "2 Sm": "2 Samuel", "1 Krl": "1 Kings", "2 Krl": "2 Kings",
- "1 Krn": "1 Chronicles", "2 Krn": "2 Chronicles", "Ezd": "Ezra",
- "Ne": "Nehemiah", "Tb": "Tobit", "Jdt": "Judith", "Est": "Esther",
- "1 Mch": "1 Maccabees", "2 Mch": "2 Maccabees", "Hi": "Job", "Ps": "Psalms",
- "Prz": "Proverbs", "Koh": "Ecclesiastes", "Pnp": "Song of Solomon",
- "Mdr": "Wisdom", "Syr": "Sirach", "Iz": "Isaiah", "Jr": "Jeremiah",
- "Lm": "Lamentations", "Ba": "Baruch", "Ez": "Ezekiel", "Dn": "Daniel",
- "Oz": "Hosea", "Jl": "Joel", "Am": "Amos", "Ab": "Obadiah", "Jon": "Jonah",
- "Mi": "Micah", "Na": "Nahum", "Ha": "Habakkuk", "So": "Zephaniah",
- "Ag": "Haggai", "Za": "Zechariah", "Ml": "Malachi",
- // New Testament
- "Mt": "Mat", "Mk": "Mark", "Łk": "Luke", "J": "John", "Dz": "Acts",
- "Rz": "Romans", "1 Kor": "1 Corinthians", "2 Kor": "2 Corinthians",
- "Ga": "Galatians", "Ef": "Ephesians", "Flp": "Philippians",
- "Kol": "Colossians", "1 Tes": "1 Thessalonians", "2 Tes": "2 Thessalonians",
- "1 Tm": "1 Timothy", "2 Tm": "2 Timothy", "Tt": "Titus", "Flm": "Philemon",
- "Hbr": "Hebrews", "Jk": "James", "1 P": "1 Peter", "2 P": "2 Peter",
- "1 J": "1 John", "2 J": "2 John", "3 J": "3 John", "Jud": "Jude",
- "Ap": "Revelation",
-}
+import "regexp"
+// Shared citation-normalisation regexes used by the book table (normalizeSigla)
+// and the Ordinary Form reference renumbering (OFRef). The niedziela.pl Polish
+// citation converter that once lived here (ToEnglishRef) was removed with the
+// scraper: readings are now authored in English-canonical form.
var (
- porRe = regexp.MustCompile(`(?i)^\s*por\.\s*`)
- refrainRe = regexp.MustCompile(`\s*\(R\.:.*$`)
- wsRe = regexp.MustCompile(`\s+`)
iRe = regexp.MustCompile(`\s+i\s+`)
dashRe = regexp.MustCompile(`\s*[-–—]\s*`)
verseLetterRe = regexp.MustCompile(`(\d)[a-c]\b`)
bookDotRe = regexp.MustCompile(`(\p{L})\.`) // abbreviation dot after a letter ("Cor." -> "Cor")
- psalmRestRe = regexp.MustCompile(`(\d+)(?:\s*\((\d+)\))?(.*)$`)
digitsRe = regexp.MustCompile(`\d+`)
)
-
-// polishBookKeys holds the POLISH_TO_EN keys sorted longest-first, matching
-// the Python `sorted(POLISH_TO_EN, key=len, reverse=True)` traversal order.
-var polishBookKeys = sortedPolishBookKeys()
-
-func sortedPolishBookKeys() []string {
- keys := make([]string, 0, len(polishToEn))
- for k := range polishToEn {
- keys = append(keys, k)
- }
- sort.SliceStable(keys, func(i, j int) bool { return len(keys[i]) > len(keys[j]) })
- return keys
-}
-
-// psalmRef maps the psalm citation body ("63 (62), 2. 3-4. ...") to the target Psalter.
-func psalmRef(rest, system string) string {
- m := psalmRestRe.FindStringSubmatch(rest)
- if m == nil {
- return rest
- }
- heb, _ := strconv.Atoi(m[1])
- tail := m[3]
- if system == "vulgate" {
- ch := m[1]
- if m[2] != "" {
- ch = m[2]
- }
- return ch + tail
- }
- if system == "drb" {
- tail = digitsRe.ReplaceAllStringFunc(tail, func(s string) string {
- n, _ := strconv.Atoi(s)
- return strconv.Itoa(psalter.DrbVerse(heb, n))
- })
- }
- return strconv.Itoa(heb) + tail
-}
-
-// ToEnglishRef converts a Polish citation to kjv-tool style: "Mt 7, 1-5" -> "Mat 7:1-5".
-//
-// Handles the extra apparatus of non-gospel readings: a leading "por." (compare)
-// marker, a trailing "(R.: ...)" responsorial refrain, and psalm versification.
-// system selects the target Psalter: "vulgate" (vul/grb/wuj) uses the citation's
-// Vulgate chapter (62 of "63 (62)"); "drb" uses the Hebrew chapter (63) with the
-// DRB title-fold verse shift; other values use the Hebrew chapter unshifted.
-func ToEnglishRef(plRef, system string) (string, error) {
- plRef = porRe.ReplaceAllString(plRef, "") // 'compare' marker
- plRef = refrainRe.ReplaceAllString(plRef, "") // responsorial refrain
- plRef = wsRe.ReplaceAllString(strings.TrimSpace(plRef), " ")
-
- var book string
- found := false
- for _, k := range polishBookKeys {
- if plRef == k || strings.HasPrefix(plRef, k+" ") {
- book = k
- found = true
- break
- }
- }
- if !found {
- return "", fmt.Errorf("unknown book in reference: %q", plRef)
- }
-
- rest := strings.TrimSpace(plRef[len(book):])
- if polishToEn[book] == "Psalms" {
- rest = psalmRef(rest, system)
- }
- rest = strings.ReplaceAll(rest, ", ", ":") // chapter/verse separator
- rest = strings.ReplaceAll(rest, ". ", ",") // disjoint verse groups
- rest = iRe.ReplaceAllString(rest, ",") // Polish 'and'
- rest = dashRe.ReplaceAllString(rest, "-") // normalise ranges
- rest = verseLetterRe.ReplaceAllString(rest, "$1") // drop verse-part letters (15a -> 15)
- rest = strings.ReplaceAll(rest, " ", "")
-
- return strings.TrimSpace(fmt.Sprintf("%s %s", polishToEn[book], rest)), nil
-}
diff --git a/internal/bible/convert_test.go b/internal/bible/convert_test.go
deleted file mode 100644
index 6f16c89..0000000
--- a/internal/bible/convert_test.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package bible
-
-import "testing"
-
-func TestToEnglishRef(t *testing.T) {
- cases := []struct{ in, system, want string }{
- {"Mt 7, 1-5", "vulgate", "Mat 7:1-5"},
- {"J 20, 1. 11-18", "vulgate", "John 20:1,11-18"},
- {"por. J 20, 11", "vulgate", "John 20:11"},
- {"Ps 63 (62), 2. 3-4. 5-6. 8-9 (R.: por. 2ab)", "vulgate", "Psalms 62:2,3-4,5-6,8-9"},
- {"Ps 63 (62), 2. 3-4. 5-6. 8-9 (R.: por. 2ab)", "drb", "Psalms 63:1,2-3,4-5,7-8"},
- }
- for _, c := range cases {
- got, err := ToEnglishRef(c.in, c.system)
- if err != nil || got != c.want {
- t.Errorf("ToEnglishRef(%q,%q)=%q,%v want %q", c.in, c.system, got, err, c.want)
- }
- }
-}
diff --git a/internal/bible/ofref.go b/internal/bible/ofref.go
new file mode 100644
index 0000000..8a72342
--- /dev/null
+++ b/internal/bible/ofref.go
@@ -0,0 +1,71 @@
+package bible
+
+import (
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/lukaszkasprzak/lectio/internal/psalter"
+)
+
+var (
+ // ofCiteRe splits an English-canonical citation into book, chapter and the
+ // verse tail: "2 Samuel 6:12b-15,17-19" -> ("2 Samuel", "6", "12b-15,17-19").
+ // The book is non-greedy so a leading ordinal ("2 Samuel") stays with it.
+ ofCiteRe = regexp.MustCompile(`^(.*?)\s+(\d+):(.+)$`)
+ // ofVerseLetterRe drops sub-verse part letters so lookups resolve at whole
+ // verses: "12b" -> "12", "3ab" -> "3", "3cd" -> "3".
+ ofVerseLetterRe = regexp.MustCompile(`(\d)[a-z]+`)
+)
+
+// normalizeOFVerses rewrites a citation's verse tail into the shape
+// bible.Lookup parses: cross-chapter semicolons become commas, ranges use a
+// plain hyphen, sub-verse letters are dropped, and spaces are removed.
+func normalizeOFVerses(tail string) string {
+ tail = strings.ReplaceAll(tail, ";", ",")
+ tail = dashRe.ReplaceAllString(tail, "-")
+ tail = ofVerseLetterRe.ReplaceAllString(tail, "$1")
+ tail = strings.ReplaceAll(tail, " ", "")
+ return tail
+}
+
+// OFRef turns an English-canonical Ordinary Form citation (lectio's authored
+// form) into a bible.Lookup-ready reference for the target corpus's Psalter.
+//
+// Every citation's verse tail is normalized (see normalizeOFVerses). Only the
+// Psalms differ between Psalters, so their chapter/verse is additionally
+// renumbered:
+//
+// - "vulgate" (vul/grb/wuj): shift the Hebrew chapter to its Vulgate
+// equivalent (Ps 27 -> 26, Ps 147 -> 146). Verse numbers are kept, so the
+// rare intra-psalm verse-boundary shifts are not corrected here.
+// - "drb": the Douay-Rheims corpus keeps modern (Hebrew) chapter numbers, so
+// the chapter is kept; only the DRB title-fold verse shift applies (see
+// psalter.DrbVerse).
+// - "hebrew" / anything else: already in the corpus's numbering.
+//
+// A citation OFRef cannot parse (no "chapter:verse") is returned unchanged for
+// bible.Lookup to accept or reject.
+func OFRef(citation, system string) string {
+ m := ofCiteRe.FindStringSubmatch(citation)
+ if m == nil {
+ return citation
+ }
+ book, chap, verses := m[1], m[2], normalizeOFVerses(m[3])
+
+ if book == "Psalms" {
+ heb, err := strconv.Atoi(chap)
+ if err == nil {
+ switch system {
+ case "vulgate":
+ chap = strconv.Itoa(psalter.HebrewToVulgateChapter(heb))
+ case "drb":
+ verses = digitsRe.ReplaceAllStringFunc(verses, func(s string) string {
+ n, _ := strconv.Atoi(s)
+ return strconv.Itoa(psalter.DrbVerse(heb, n))
+ })
+ }
+ }
+ }
+ return book + " " + chap + ":" + verses
+}
diff --git a/internal/bible/ofref_test.go b/internal/bible/ofref_test.go
new file mode 100644
index 0000000..4b9cb1e
--- /dev/null
+++ b/internal/bible/ofref_test.go
@@ -0,0 +1,50 @@
+package bible
+
+import "testing"
+
+func TestOFRef(t *testing.T) {
+ cases := []struct {
+ name string
+ citation string
+ system string
+ want string
+ }{
+ // Non-psalms pass through unchanged in every system.
+ {"gospel-vulgate", "Matthew 4:12-23", "vulgate", "Matthew 4:12-23"},
+ {"gospel-drb", "Matthew 4:12-23", "drb", "Matthew 4:12-23"},
+ {"epistle-hebrew", "1 Corinthians 1:10-13,17", "hebrew", "1 Corinthians 1:10-13,17"},
+
+ // Psalm chapter shift for the Vulgate family (Hebrew -> Vulgate).
+ {"ps27-vulgate", "Psalms 27:1,4,13-14", "vulgate", "Psalms 26:1,4,13-14"},
+ {"ps27-drb", "Psalms 27:1,4,13-14", "drb", "Psalms 27:1,4,13-14"}, // Ps 27 has no DRB title fold
+ {"ps27-hebrew", "Psalms 27:1,4,13-14", "hebrew", "Psalms 27:1,4,13-14"},
+
+ // Boundary cases of HebrewToVulgateChapter.
+ {"ps8-vulgate", "Psalms 8:2", "vulgate", "Psalms 8:2"}, // <=8 unchanged
+ {"ps9-vulgate", "Psalms 9:2", "vulgate", "Psalms 9:2"}, // 9,10 -> 9
+ {"ps10-vulgate", "Psalms 10:1", "vulgate", "Psalms 9:1"}, // 10 -> 9
+ {"ps11-vulgate", "Psalms 11:1", "vulgate", "Psalms 10:1"}, // 11..113 -> h-1
+ {"ps116-vulgate", "Psalms 116:12", "vulgate", "Psalms 114:12"},
+ {"ps147-vulgate", "Psalms 147:12", "vulgate", "Psalms 146:12"},
+ {"ps148-vulgate", "Psalms 148:1", "vulgate", "Psalms 148:1"}, // >=148 unchanged
+
+ // DRB title-fold verse shift: Ps 51 folds 2 title verses into verse 1.
+ {"ps51-drb", "Psalms 51:3-4,12-13", "drb", "Psalms 51:1-2,10-11"},
+ {"ps51-vulgate", "Psalms 51:3-4", "vulgate", "Psalms 50:3-4"},
+ // Ps 63 folds 1 title verse.
+ {"ps63-drb", "Psalms 63:2,3-4", "drb", "Psalms 63:1,2-3"},
+
+ // Tail normalization: sub-verse letters dropped, ";" -> ",".
+ {"verseletter-2sam", "2 Samuel 6:12b-15,17-19", "vulgate", "2 Samuel 6:12-15,17-19"},
+ {"semicolon-acts", "The Acts 11:21b-26;13:1-3", "drb", "The Acts 11:21-26,13:1-3"},
+ {"multiletter-psalm-drb", "Psalms 98:1,2-3ab,3cd-4,5-6", "drb", "Psalms 98:1,2-3,3-4,5-6"},
+ {"multiletter-psalm-vulgate", "Psalms 98:1,2-3ab,3cd-4,5-6", "vulgate", "Psalms 97:1,2-3,3-4,5-6"},
+ }
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ if got := OFRef(c.citation, c.system); got != c.want {
+ t.Errorf("OFRef(%q, %q) = %q, want %q", c.citation, c.system, got, c.want)
+ }
+ })
+ }
+}