`)
}
// htmlToLines turns an HTML fragment into a list of non-empty text lines.
//
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, `
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
}
// 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
}