aboutsummaryrefslogtreecommitdiff
path: root/internal/bible
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-23 12:55:24 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-23 12:55:24 +0200
commit343a675a5a779cfce5a3dec9a3ad4a6ffb22de75 (patch)
tree744fb2877695f9b02cc8834c098cd3404d25650a /internal/bible
parent0fd209b654b057efa281f86d7bfd5043a12ce407 (diff)
downloadlectio-343a675a5a779cfce5a3dec9a3ad4a6ffb22de75.tar.gz
lectio-343a675a5a779cfce5a3dec9a3ad4a6ffb22de75.zip
bible: Polish citation -> English ref (with psalm versification)
Diffstat (limited to 'internal/bible')
-rw-r--r--internal/bible/convert.go127
-rw-r--r--internal/bible/convert_test.go19
2 files changed, 146 insertions, 0 deletions
diff --git a/internal/bible/convert.go b/internal/bible/convert.go
new file mode 100644
index 0000000..6181dbf
--- /dev/null
+++ b/internal/bible/convert.go
@@ -0,0 +1,127 @@
+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",
+}
+
+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`)
+ 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
new file mode 100644
index 0000000..6f16c89
--- /dev/null
+++ b/internal/bible/convert_test.go
@@ -0,0 +1,19 @@
+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)
+ }
+ }
+}