aboutsummaryrefslogtreecommitdiff
path: root/internal/liturgy/parse.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-28 12:50:31 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-28 12:50:31 +0200
commit7b220084cf3951c8cde0582efdfcf628afc64336 (patch)
treee74bae03c61b62479ef4f68b046e3345618933c8 /internal/liturgy/parse.go
parentfeede51697be870ae183a95b513ef64f031dbd0f (diff)
downloadlectio-7b220084cf3951c8cde0582efdfcf628afc64336.tar.gz
lectio-7b220084cf3951c8cde0582efdfcf628afc64336.zip
refactor: remove the niedziela/missalemeum scrapers, bt, traditional_lang
The daily view now computes entirely offline (previous commit), so retire the network path and everything that served it: - Delete internal/tradlit (missalemeum) and the niedziela scraper from internal/liturgy (fetch/store/parse + fixtures); keep Section, DayInfo and ExtractCitation. - Remove the "bt" version everywhere (render gatherBT + branches, config, i18n, web form, TUI) and bible.ToEnglishRef (Polish citation converter). A legacy config carrying "bt" migrates to "wuj" on load (config.migrateBT). - Remove the traditional_lang config field and the -g/--lang flag from all three binaries. - Drop the now-dead flags -R/--refresh, -o/--offline, -u/--update, -C/--clean and the harvest/clean commands. - New defaults: versions = wuj,vul,grb,drb; default_version = vul. Update the README (offline-by-design, no harvest/update), help text, and stale niedziela/bt doc comments. Tests updated for the offline reality; go test ./... and go vet ./... are clean, all three binaries build and run offline (OF + EF, compare, web).
Diffstat (limited to 'internal/liturgy/parse.go')
-rw-r--r--internal/liturgy/parse.go209
1 files changed, 0 insertions, 209 deletions
diff --git a/internal/liturgy/parse.go b/internal/liturgy/parse.go
deleted file mode 100644
index 4c1a935..0000000
--- a/internal/liturgy/parse.go
+++ /dev/null
@@ -1,209 +0,0 @@
-package liturgy
-
-import (
- "fmt"
- "html"
- "regexp"
- "strings"
-)
-
-// lectionaryTabs lists the two reading sets carried on the page: the
-// lectionary in force since Advent 2015 ("nowy") and the one it replaced
-// ("stary"). They are not interchangeable -- the acclamation can cite a
-// different book entirely, and the older set contains malformed citations.
-// Pick by id, never by position in the markup, or a reordering silently
-// switches edition.
-var lectionaryTabs = []string{"tabnowy0all", "tabstary0all"}
-
-var (
- brRe = regexp.MustCompile(`(?i)<br\s*/?>`)
- tagRe = regexp.MustCompile(`<[^>]+>`)
- wsRe = regexp.MustCompile(`[ \t]+`)
- h2Re = regexp.MustCompile(`(?s)<h2>(.*?)</h2>`)
- h4Re = regexp.MustCompile(`(?s)<h4>(.*?)</h4>`)
- pRe = regexp.MustCompile(`(?s)<p>(.*?)</p>`)
- citationRe = regexp.MustCompile(`\((.+)\)\s*$`)
-
- // dayNamePRe matches every classed <p><em>...</em></p> on the page; only
- // the one whose class carries both "fw-bold" and a "color-" role (see
- // dayNameParaMatches) is the day's celebration name -- the page also
- // carries a plain fw-bold (no color-) lookalike higher up that must not
- // win instead.
- dayNamePRe = regexp.MustCompile(`(?s)<p class="([^"]*)">\s*<em>(.*?)</em>\s*</p>`)
-
- // dayColourRe matches niedziela.pl's "Kolor szat: <word>" vestment-colour
- // line, tolerating the <span>/<strong> markup wrapped around the colour
- // word on the page (see internal/liturgy/testdata/2026-07-22.html).
- dayColourRe = regexp.MustCompile(`Kolor szat:\s*(?:<[^>]+>\s*)*([\p{L}]+)`)
-)
-
-// modernColours maps niedziela.pl's Polish vestment-colour words to
-// DayInfo's normalized colour names; anything not listed here (including a
-// multi-option line like "zielony albo biały albo czerwony", which matches
-// only its first word) is left for the caller to treat as "" if absent.
-var modernColours = map[string]string{
- "biały": "white",
- "zielony": "green",
- "fioletowy": "violet",
- "czerwony": "red",
- "różowy": "rose",
-}
-
-// panePattern matches the opening tag of the tab-pane div carrying the given
-// tab id, e.g. `<div class="tab-pane fade " id="tabnowy0all">`.
-func panePattern(tab string) *regexp.Regexp {
- return regexp.MustCompile(`<div class="tab-pane[^"]*"\s+id="` + regexp.QuoteMeta(tab) + `">`)
-}
-
-// htmlToLines turns an HTML fragment into a list of non-empty text lines.
-// <br> marks a verse line break (used in psalms/acclamations); other tags
-// are dropped, entities decoded, and intra-line whitespace collapsed.
-func htmlToLines(fragment string) []string {
- fragment = brRe.ReplaceAllString(fragment, "\n")
- fragment = tagRe.ReplaceAllString(fragment, "")
- text := html.UnescapeString(fragment)
- var lines []string
- for _, ln := range strings.Split(text, "\n") {
- ln = strings.TrimSpace(wsRe.ReplaceAllString(ln, " "))
- if ln != "" {
- lines = append(lines, ln)
- }
- }
- return lines
-}
-
-// Parse extracts every reading section from the page's preferred lectionary
-// tab (falling back to the superseded one), erroring loudly if neither tab is
-// present on the page, or the tab is found but carries no sections -- a
-// layout change should never be mistaken for a quiet day with no readings.
-func Parse(pageHTML string) ([]Section, error) {
- var loc []int
- for _, tab := range lectionaryTabs {
- if m := panePattern(tab).FindStringIndex(pageHTML); m != nil {
- loc = m
- break
- }
- }
- if loc == nil {
- if strings.Contains(pageHTML, "Przykro nam") {
- return nil, fmt.Errorf("no reading published for this date yet")
- }
- return nil, fmt.Errorf(
- "no reading tab (%s) found on page -- the site layout may have changed",
- strings.Join(lectionaryTabs, "/"),
- )
- }
-
- rest := pageHTML[loc[1]:]
- block := rest
- if nxt := strings.Index(rest, `<div class="tab-pane`); nxt != -1 {
- block = rest[:nxt]
- }
-
- heads := h2Re.FindAllStringSubmatchIndex(block, -1)
- var sections []Section
- czytanieCount := 0
- for i, h := range heads {
- bodyEnd := len(block)
- if i+1 < len(heads) {
- bodyEnd = heads[i+1][0]
- }
- body := block[h[1]:bodyEnd]
-
- heading := strings.Join(htmlToLines(block[h[2]:h[3]]), " ")
-
- subtitle := ""
- if sub := h4Re.FindStringSubmatch(body); sub != nil {
- subtitle = strings.Join(htmlToLines(sub[1]), " ")
- }
-
- var paragraphs [][]string
- for _, p := range pRe.FindAllStringSubmatch(body, -1) {
- if lines := htmlToLines(p[1]); len(lines) > 0 {
- paragraphs = append(paragraphs, lines)
- }
- }
-
- // A heading is expected to always carry a parenthetical citation;
- // if one is somehow missing, leave Citation empty rather than
- // failing the whole parse over one section.
- citation, _ := ExtractCitation(heading)
-
- sections = append(sections, Section{
- Heading: heading,
- Subtitle: subtitle,
- Citation: citation,
- PartID: partID(heading, &czytanieCount),
- Paragraphs: paragraphs,
- })
- }
-
- // A layout change can leave the tab findable but empty; say so rather
- // than returning nothing and looking like a quiet day.
- if len(sections) == 0 {
- return nil, fmt.Errorf("reading tab found but no sections in it -- the site layout may have changed")
- }
- return sections, nil
-}
-
-// ParseDayInfo extracts the day's celebration name and liturgical colour
-// from a niedziela.pl page: Name is the inner text of the <p class="...
-// fw-bold color-XXX"><em>NAME</em></p> paragraph (there is also an earlier,
-// plain fw-bold-but-no-color- lookalike on the page -- see dayNamePRe --
-// which must not match instead), and Colour comes from the page's "Kolor
-// szat: <word>" line, mapped via modernColours (case-insensitive; unknown
-// word -> ""). Season is always "" -- the modern lectionary folds its
-// temporal context into Name on temporal days rather than carrying it
-// separately. A page whose markup doesn't match either pattern (a layout
-// change, or a fixture with neither) yields a zero DayInfo, never an error:
-// the readings are the load-bearing content, the header is a nice-to-have.
-func ParseDayInfo(pageHTML string) DayInfo {
- var info DayInfo
-
- for _, m := range dayNamePRe.FindAllStringSubmatch(pageHTML, -1) {
- class := m[1]
- if strings.Contains(class, "fw-bold") && strings.Contains(class, "color-") {
- info.Name = strings.Join(htmlToLines(m[2]), " ")
- break
- }
- }
-
- if m := dayColourRe.FindStringSubmatch(pageHTML); m != nil {
- info.Colour = modernColours[strings.ToLower(m[1])]
- }
-
- return info
-}
-
-// partID assigns the stable liturgical-part identifier for a section heading.
-// A second "1. czytanie" heading on the same day (a split feast offering two
-// alternative first readings) becomes "drugie_czytanie" instead of colliding
-// with the first ("pierwsze_czytanie"). Anything unrecognised is "".
-func partID(heading string, czytanieCount *int) string {
- switch {
- case strings.HasPrefix(heading, "1. czytanie"):
- *czytanieCount++
- if *czytanieCount == 1 {
- return "pierwsze_czytanie"
- }
- return "drugie_czytanie"
- case strings.HasPrefix(heading, "Psalm"):
- return "psalm"
- case strings.HasPrefix(heading, "Aklamacja"):
- return "aklamacja"
- case strings.HasPrefix(heading, "Ewangelia"):
- return "ewangelia"
- default:
- return ""
- }
-}
-
-// ExtractCitation pulls the citation from a section heading:
-// "Ewangelia (Mt 7, 1-5)" -> "Mt 7, 1-5".
-func ExtractCitation(heading string) (string, error) {
- m := citationRe.FindStringSubmatch(heading)
- if m == nil {
- return "", fmt.Errorf("no reference found in heading: %q", heading)
- }
- return strings.TrimSpace(m[1]), nil
-}