Preserve layout in embedded Bible data

This commit is contained in:
2026-07-05 20:49:18 -05:00
parent dbc39480f7
commit 98253920ca
4 changed files with 276 additions and 64 deletions
+117 -32
View File
@@ -2,11 +2,27 @@ import std/[strutils, tables]
import ./reference_parser
type BibleIndex = object
verses: Table[string, string]
lastVerseByChapter: Table[string, int]
lastChapterByBook: Table[string, int]
translationName: string
type
SegmentBreak = enum
sbSame = "same"
sbLine = "line"
sbParagraph = "paragraph"
BibleSegment = object
code: string
chapter: int
verse: int
segment: int
breakKind: SegmentBreak
indent: int
text: string
BibleIndex = object
segments: seq[BibleSegment]
verses: Table[string, bool]
lastVerseByChapter: Table[string, int]
lastChapterByBook: Table[string, int]
translationName: string
proc verseKey(code: string, chapter, verse: int): string =
code & "\t" & $chapter & "\t" & $verse
@@ -14,6 +30,27 @@ proc verseKey(code: string, chapter, verse: int): string =
proc chapterKey(code: string, chapter: int): string =
code & "\t" & $chapter
proc parseSegmentBreak(s: string): SegmentBreak =
case s
of "same": sbSame
of "line": sbLine
of "paragraph": sbParagraph
else:
raise newException(ValueError, "invalid embedded Bible segment break: " & s)
proc addSegment(index: var BibleIndex, segment: BibleSegment) =
index.segments.add(segment)
index.verses[verseKey(segment.code, segment.chapter, segment.verse)] = true
let cKey = chapterKey(segment.code, segment.chapter)
if not index.lastVerseByChapter.hasKey(cKey) or
segment.verse > index.lastVerseByChapter[cKey]:
index.lastVerseByChapter[cKey] = segment.verse
if not index.lastChapterByBook.hasKey(segment.code) or
segment.chapter > index.lastChapterByBook[segment.code]:
index.lastChapterByBook[segment.code] = segment.chapter
proc loadBibleIndex(rows, translationName: string): BibleIndex =
result.translationName = translationName
@@ -21,26 +58,29 @@ proc loadBibleIndex(rows, translationName: string): BibleIndex =
if line.strip.len == 0:
continue
let parts = line.split('\t', maxsplit = 3)
if parts.len != 4:
let parts = line.split('\t', maxsplit = 6)
if parts.len != 4 and parts.len != 7:
raise newException(ValueError,
"invalid embedded " & translationName & " row: " & line)
let code = parts[0]
let chapter = parseInt(parts[1])
let verse = parseInt(parts[2])
let text = parts[3]
result.verses[verseKey(code, chapter, verse)] = text
let cKey = chapterKey(code, chapter)
if not result.lastVerseByChapter.hasKey(cKey) or
verse > result.lastVerseByChapter[cKey]:
result.lastVerseByChapter[cKey] = verse
if not result.lastChapterByBook.hasKey(code) or
chapter > result.lastChapterByBook[code]:
result.lastChapterByBook[code] = chapter
if parts.len == 4:
result.addSegment(BibleSegment(
code: parts[0],
chapter: parseInt(parts[1]),
verse: parseInt(parts[2]),
segment: 0,
breakKind: sbLine,
indent: 0,
text: parts[3]))
else:
result.addSegment(BibleSegment(
code: parts[0],
chapter: parseInt(parts[1]),
verse: parseInt(parts[2]),
segment: parseInt(parts[3]),
breakKind: parseSegmentBreak(parts[4]),
indent: parseInt(parts[5]),
text: parts[6]))
proc requireLastChapter(index: BibleIndex, code: string): int =
if not index.lastChapterByBook.hasKey(code):
@@ -62,13 +102,10 @@ proc requireVerse(index: BibleIndex, code: string, chapter, verse: int): string
raise newException(ValueError,
"no embedded " & index.translationName & " data for " &
bookInfo(code).name & " " & $chapter & ":" & $verse)
index.verses[vKey]
""
proc addVerseLines(
lines: var seq[string],
index: BibleIndex,
reference: PassageReference,
range: RefRange) =
proc selectedVerses(index: BibleIndex, reference: PassageReference, range: RefRange):
Table[string, bool] =
let code = reference.book.code
discard index.requireLastChapter(code)
@@ -90,16 +127,64 @@ proc addVerseLines(
raise newException(ValueError, "reference range starts after it ends")
for verse in startVerse .. endVerse:
lines.add(" [" & $verse & "] " & index.requireVerse(code, chapter, verse))
discard index.requireVerse(code, chapter, verse)
result[verseKey(code, chapter, verse)] = true
proc addFormattedSegment(
lines: var seq[string],
segment: BibleSegment,
emittedVerses: var Table[string, bool]) =
let vKey = verseKey(segment.code, segment.chapter, segment.verse)
let marker =
if emittedVerses.hasKey(vKey): ""
else:
emittedVerses[vKey] = true
"[" & $segment.verse & "] "
let prefix = repeat(" ", segment.indent + 1)
let content = marker & segment.text
let hasPassageLine = lines.len > 1 and lines[^1].len > 0
if segment.breakKind == sbSame and hasPassageLine:
if lines[^1].len > 0 and not lines[^1][^1].isSpaceAscii:
lines[^1].add(" ")
lines[^1].add(content)
else:
if segment.breakKind == sbParagraph and hasPassageLine:
lines.add("")
lines.add(prefix & content)
proc addVerseLines(
lines: var seq[string],
index: BibleIndex,
reference: PassageReference,
range: RefRange) =
let code = reference.book.code
let selected = index.selectedVerses(reference, range)
var emittedVerses: Table[string, bool]
var firstSelected = true
for segment in index.segments:
if segment.code == code and
selected.hasKey(verseKey(segment.code, segment.chapter, segment.verse)):
var adjusted = segment
if firstSelected and adjusted.breakKind == sbSame:
adjusted.breakKind = sbLine
lines.addFormattedSegment(adjusted, emittedVerses)
firstSelected = false
proc fetchReference(index: BibleIndex, reference: PassageReference): string =
var lines = @[$reference]
let code = reference.book.code
if reference.ranges.len == 0:
for chapter in 1 .. index.requireLastChapter(code):
for verse in 1 .. index.requireLastVerse(code, chapter):
lines.add(" [" & $verse & "] " & index.requireVerse(code, chapter, verse))
var emittedVerses: Table[string, bool]
discard index.requireLastChapter(code)
for segment in index.segments:
if segment.code == code:
lines.addFormattedSegment(segment, emittedVerses)
else:
for range in reference.ranges:
lines.addVerseLines(index, reference, range)