Files
spsays-training/scripts/kb_extract_intro.py
T
2026-04-16 00:48:48 +05:30

310 lines
9.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Extract Kṛṣṇa Book introduction and build intro.json with chapter listing.
Reads public/html/other.html to extract:
1. The introduction/preface text (lines ~713–771)
2. The full chapter listing from p13 bookmark tags (KB 1970-V-C / Title)
3. Chapter end markers ("Thus ends the Bhaktivedanta purport of the Nth Chapter...")
Output: lib/jsons/books/kb/intro.json
Usage:
python3 scripts/kb_extract_intro.py
"""
import json
import os
import re
import sys
from bs4 import BeautifulSoup, Tag
# ---------------------------------------------------------------------------
# ScaGoudy → IAST Unicode mapping
# ---------------------------------------------------------------------------
SCAGOUDY_TO_IAST = {
"ä": "ā",
"Ä": "Ā",
"å": "ṛ",
"Å": "Ṛ",
"ñ": "ṣ",
"Ñ": "Ṣ",
"ö": "ṭ",
"Ö": "Ṭ",
"ë": "ṇ",
"Ë": "Ṇ",
"ò": "ḍ",
"Ò": "Ḍ",
"ç": "ś",
"Ç": "Ś",
"ì": "ṅ",
"ï": "ñ",
"ü": "ū",
"Ü": "Ū",
"é": "ī",
"É": "Ī",
"à": "ṁ",
"ù": "ḥ",
"è": "ṝ",
"û": "ṁl",
"ÿ": "ḷ",
}
def decode_scagoudy(text: str) -> str:
for src, dst in SCAGOUDY_TO_IAST.items():
text = text.replace(src, dst)
return text
def decode_html_content(tag: Tag) -> str:
inner = "".join(str(child) for child in tag.children)
inner = decode_scagoudy(inner)
inner = re.sub(r'<span[^>]*class="[^"]*"[^>]*>(.*?)</span>', r"\1", inner)
inner = re.sub(r"\s+", " ", inner).strip()
return inner
def decode_text(tag: Tag) -> str:
return decode_scagoudy(tag.get_text().strip())
def get_css_class(tag: Tag) -> str:
classes = tag.get("class", [])
return classes[0] if classes else ""
# ---------------------------------------------------------------------------
# Extract introduction paragraphs (lines ~713–771)
# ---------------------------------------------------------------------------
# CSS classes used for Sanskrit verse pādas in the introduction
VERSE_PADA_CLASSES = frozenset(["p4", "p5", "p10"])
def extract_introduction(soup: BeautifulSoup) -> list[str]:
"""Extract the Kṛṣṇa Book introduction/preface paragraphs.
Starts at the 'KṚṢṆA BOOK-Introduction' header and ends at the
'February 26th, 1970' date line (first occurrence — the undated
typed document version).
Sanskrit verse lines (p4/p5 classes) are grouped into a single
center-aligned italic block.
"""
paragraphs = soup.find_all("p")
intro_paragraphs: list[str] = []
in_intro = False
found_date = False
verse_accumulator: list[str] = []
def flush_verse():
"""Join accumulated Sanskrit verse pādas into one formatted block."""
if verse_accumulator:
joined = "<br>".join(verse_accumulator)
intro_paragraphs.append(
f'<div style="text-align:center"><i>{joined}</i></div>'
)
verse_accumulator.clear()
for p in paragraphs:
cls = get_css_class(p)
text = p.get_text().strip()
# Start marker
if not in_intro:
if "BOOK-Introduction" in text or "BOOK\u2011Introduction" in text:
in_intro = True
continue
# Also match the ScaGoudy-encoded version
if "BOOK" in text and "Introduction" in text and "K" in text:
decoded = decode_text(p)
if "KṚṢṆA" in decoded and "Introduction" in decoded:
in_intro = True
continue
continue
# Skip the "Preface" subheader
if text == "Preface" or text.startswith("*****"):
continue
# End marker — "February 26th, 1970" (first occurrence)
if "February 26th, 1970" in text:
flush_verse()
found_date = True
break
# Skip empty paragraphs
if not text or text == "\xa0":
continue
# Sanskrit verse pādas (p4, p5, p10) — accumulate into a single block
# Use decode_html_content to preserve <br> tags within multi-line verse elements
if cls in VERSE_PADA_CLASSES:
html = decode_html_content(p)
if html:
verse_accumulator.append(html)
continue
# If we were accumulating verse lines and hit a non-verse paragraph,
# flush the verse block first
flush_verse()
# Collect the paragraph
html = decode_html_content(p)
if html:
intro_paragraphs.append(html)
if not found_date:
print(" WARNING: Did not find the end marker 'February 26th, 1970'")
return intro_paragraphs
# ---------------------------------------------------------------------------
# Extract chapter listing from p13 bookmarks
# ---------------------------------------------------------------------------
def extract_chapters(soup: BeautifulSoup) -> list[dict]:
"""Extract the full chapter listing from p13 bookmark tags.
Format: 'KB 1970-V-C / Title' where V=volume, C=chapter within volume.
We produce a flat chapter numbering (1–89) across both volumes.
Also finds chapter end markers to count paragraphs per chapter
(useful for later extraction).
"""
chapters: list[dict] = []
chapter_num = 0
# Also extract chapter end markers for titles
end_titles: dict[int, str] = {}
for p in soup.find_all("p"):
cls = get_css_class(p)
text = decode_text(p)
# "Thus ends the Bhaktivedanta purport of the First Chapter of Kṛṣṇa, "Title""
if "Thus ends" in text and "Chapter of" in text and "Kṛṣṇa" in text:
# Extract chapter ordinal and title
m = re.search(
r"(?:the\s+)?(\w+)\s+Chapter\s+of\s+Kṛṣṇa,\s*[\"\"\"]*(.+?)[\"\"\"]*\.?\s*$",
text,
)
if m:
# We don't need the ordinal — just the title for cross-referencing
title_from_end = m.group(2).strip().rstrip(".")
# Store by the chapter count we've seen so far (rough approximation)
# Extract from p13 bookmarks
for p in soup.find_all("p"):
cls = get_css_class(p)
if cls != "p13":
continue
text = decode_text(p)
# Match: KB 1970-V-C / Title
m = re.match(r"KB\s+1970-(\d+)-(\d+)\s*/\s*(.+)", text)
if not m:
continue
volume = int(m.group(1))
chapter_in_vol = int(m.group(2))
title = m.group(3).strip()
chapter_num += 1
chapters.append(
{
"number": chapter_num,
"volume": volume,
"chapterInVolume": chapter_in_vol,
"title": title,
}
)
return chapters
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
html_path = os.path.join(base_dir, "public", "html", "other.html")
output_path = os.path.join(base_dir, "lib", "jsons", "books", "kb", "intro.json")
if not os.path.exists(html_path):
print(f"ERROR: {html_path} not found!")
sys.exit(1)
print(f"Reading {html_path}...")
with open(html_path, "r", encoding="utf-8") as f:
soup = BeautifulSoup(f, "html.parser")
# Extract introduction
print("Extracting introduction...")
intro_paragraphs = extract_introduction(soup)
print(f" Found {len(intro_paragraphs)} introduction paragraphs")
# Extract chapter listing
print("Extracting chapter listing...")
chapters = extract_chapters(soup)
print(f" Found {len(chapters)} chapters")
# Count per volume
vol1 = [c for c in chapters if c["volume"] == 1]
vol2 = [c for c in chapters if c["volume"] == 2]
print(f" Volume 1: {len(vol1)} chapters")
print(f" Volume 2: {len(vol2)} chapters")
# Build intro.json
# Strip volume/chapterInVolume from the chapter entries for the public JSON
chapters_public = [{"number": c["number"], "title": c["title"]} for c in chapters]
intro = {
"book": "kb",
"title": "Kṛṣṇa, The Supreme Personality of Godhead",
"author": "His Divine Grace A.C. Bhaktivedanta Swami Prabhupāda",
"description": "A summary study of the Tenth Canto of Śrīmad-Bhāgavatam. Originally published in two volumes in 1970.",
"totalChapters": len(chapters),
"volumes": [
{"number": 1, "totalChapters": len(vol1)},
{"number": 2, "totalChapters": len(vol2)},
],
"chapters": chapters_public,
"introduction": intro_paragraphs,
}
# Write
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, "w", encoding="utf-8") as f:
json.dump(intro, f, ensure_ascii=False, indent=2)
print(f"\nWritten: {output_path}")
# Summary
print(f"\n--- Summary ---")
print(f"Title: {intro['title']}")
print(f"Chapters: {intro['totalChapters']}")
print(f"Introduction paragraphs: {len(intro_paragraphs)}")
# Show first few intro paragraphs
print(f"\n--- Introduction (first 3 paragraphs) ---")
for i, para in enumerate(intro_paragraphs[:3]):
print(f" [{i + 1}] {para[:120]}...")
# Show first and last chapters
print(f"\n--- Chapters ---")
for c in chapters[:5]:
print(f" Ch {c['number']:>2} (Vol {c['volume']}): {c['title']}")
print(f" ...")
for c in chapters[-3:]:
print(f" Ch {c['number']:>2} (Vol {c['volume']}): {c['title']}")
if __name__ == "__main__":
main()