Files
spsays-training/scripts/nod_extract.py
T
2026-04-17 22:51:58 +05:30

804 lines
27 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 The Nectar of Devotion (1970 Edition) from other.html.
The book has 3 intro segments (Front Flap, Preface, Introduction),
51 chapters organized in 4 parts, and a Concluding Words section.
Output:
lib/jsons/books/nod/intro.json — Book metadata
lib/jsons/books/nod/intro/*.json — Intro segments
lib/jsons/books/nod/chapters/*.json — Chapters + concluding words
Usage:
python3 scripts/nod_extract.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:
"""Replace ScaGoudy-encoded characters with proper IAST Unicode."""
for src, dst in SCAGOUDY_TO_IAST.items():
text = text.replace(src, dst)
return text
def decode_html_content(tag: Tag) -> str:
"""Extract inner HTML from a tag, decode ScaGoudy, preserve formatting tags."""
inner = "".join(str(child) for child in tag.children)
inner = decode_scagoudy(inner)
# Remove span tags with CSS classes (RTF conversion artifacts)
inner = re.sub(
r'<span[^>]*class="[^"]*"[^>]*>(.*?)</span>', r"\1", inner, flags=re.DOTALL
)
# Clean up Apple-tab-span leftovers (tab characters)
inner = inner.replace("\t", " ")
# Normalize whitespace
inner = re.sub(r"\s+", " ", inner).strip()
return inner
def decode_text(tag: Tag) -> str:
"""Get plain text from a tag with ScaGoudy decoding."""
return decode_scagoudy(tag.get_text().strip())
def get_css_class(tag: Tag) -> str:
"""Get the first CSS class of a tag, or empty string."""
classes = tag.get("class", [])
return classes[0] if classes else ""
def is_empty(tag: Tag) -> bool:
"""Check if a tag is empty or whitespace-only."""
text = tag.get_text().strip()
return not text or text == "\xa0"
# ---------------------------------------------------------------------------
# CSS class categorization for NoD
# ---------------------------------------------------------------------------
# Bookmark class — used for section boundary detection
BOOKMARK_CLASS = "p13"
# Book title header class
BOOK_HEADER_CLASS = "p12"
# Body paragraph classes
BODY_CLASSES = frozenset(["p6", "p35", "p30"])
# Section heading (bold) — "Chapter One", "Preface", etc.
SECTION_HEADING_CLASS = "p31"
# Chapter title (bold) — "Characteristics of Pure Devotional Service", etc.
CHAPTER_TITLE_CLASS = "p27"
# Part header class — "PART ONE", "PART TWO", etc.
PART_HEADER_CLASS = "p34"
# Sub-section heading (bold) within chapters
SUB_HEADING_CLASS = "p36"
# Italic sub-headings
ITALIC_SUB_HEADING_CLASSES = frozenset(["p37", "p38"])
# Title page / copyright text classes (in intro area)
TITLE_PAGE_CLASS = "p3"
COPYRIGHT_CLASS = "p32"
# Sanskrit verse (italic with br tags)
SANSKRIT_VERSE_CLASS = "p33"
# Classes to skip entirely (empty/decorative)
SKIP_CLASSES = frozenset(["p9", "p15"])
# ---------------------------------------------------------------------------
# Intro segment definitions
# ---------------------------------------------------------------------------
INTRO_SEGMENTS = [
{
"slug": "front-flap",
"title": "Front Flap",
"order": 1,
"bookmark_pattern": "NoD-1970: Front Flap",
},
{
"slug": "preface",
"title": "Preface",
"order": 2,
"bookmark_pattern": "NoD-1970: Preface",
},
{
"slug": "introduction",
"title": "Introduction",
"order": 3,
"bookmark_pattern": "NoD-1970: Introduction",
},
]
# ---------------------------------------------------------------------------
# Extraction helpers
# ---------------------------------------------------------------------------
def find_nod_boundaries(all_paragraphs: list) -> tuple[int, int]:
"""Find the start and end paragraph indices of the NoD content.
Start: <p class="p12"> containing "Nectar of Devotion"
End: next <p class="p12"> after start, or end of file
"""
start_idx = None
end_idx = len(all_paragraphs)
for i, p in enumerate(all_paragraphs):
cls = get_css_class(p)
if cls != BOOK_HEADER_CLASS:
continue
text = decode_text(p)
if "Nectar of Devotion" in text:
start_idx = i
elif start_idx is not None:
# Next p12 after NoD start = end of NoD
end_idx = i
break
if start_idx is None:
print("ERROR: Could not find 'The Nectar of Devotion' header in other.html!")
sys.exit(1)
return start_idx, end_idx
def find_all_bookmarks(all_paragraphs: list, start_idx: int, end_idx: int) -> list:
"""Find all p13 bookmark tags in the NoD range.
Returns list of (paragraph_index, bookmark_text).
"""
bookmarks = []
for i in range(start_idx, end_idx):
p = all_paragraphs[i]
cls = get_css_class(p)
if cls == BOOKMARK_CLASS:
text = decode_text(p)
if "NoD" in text:
bookmarks.append((i, text))
return bookmarks
def parse_chapter_bookmark(text: str) -> tuple[int, str] | None:
"""Parse a chapter bookmark like 'NoD 1-1970: Characteristics of Pure Devotional Service'.
Returns (chapter_number, title) or None if not a chapter bookmark.
"""
m = re.match(r"NoD\s+(\d+)-1970:\s*(.+)", text.strip())
if m:
return int(m.group(1)), m.group(2).strip()
return None
def parse_intro_bookmark(text: str) -> str | None:
"""Parse an intro bookmark like 'NoD-1970: Preface'.
Returns the segment name or None if not an intro bookmark.
"""
m = re.match(r"NoD-1970:\s*(.+)", text.strip())
if m:
return m.group(1).strip()
return None
def extract_paragraphs(
all_paragraphs: list,
start_idx: int,
end_idx: int,
is_intro: bool = False,
) -> list[str]:
"""Extract paragraphs for a section (chapter or intro segment).
Handles all CSS classes within the NoD content.
"""
result: list[str] = []
for i in range(start_idx, end_idx):
p = all_paragraphs[i]
cls = get_css_class(p)
# Skip empty paragraphs
if is_empty(p):
continue
# Skip noise classes
if cls in SKIP_CLASSES:
continue
# Skip bookmark tags
if cls == BOOKMARK_CLASS:
continue
# Skip book title header
if cls == BOOK_HEADER_CLASS:
continue
# Part headers — include as bold paragraph
if cls == PART_HEADER_CLASS:
html = decode_html_content(p)
if html:
result.append(html)
continue
# Section heading (p31) — include as paragraph
if cls == SECTION_HEADING_CLASS:
html = decode_html_content(p)
if html:
result.append(html)
continue
# Chapter title (p27) — include as bold paragraph
if cls == CHAPTER_TITLE_CLASS:
html = decode_html_content(p)
if html:
result.append(html)
continue
# Sub-section headings within chapters (p36, bold)
if cls == SUB_HEADING_CLASS:
html = decode_html_content(p)
if html:
result.append(html)
continue
# Italic sub-headings (p37, p38)
if cls in ITALIC_SUB_HEADING_CLASSES:
html = decode_html_content(p)
if html:
result.append(html)
continue
# Title page / copyright text (in intro area)
if cls == TITLE_PAGE_CLASS:
html = decode_html_content(p)
if html:
result.append(html)
continue
# Copyright text
if cls == COPYRIGHT_CLASS:
html = decode_html_content(p)
if html:
result.append(html)
continue
# Sanskrit verse (italic with br tags) — format as centered italic div
# Strip all inner <i>/<\/i> tags before re-wrapping to avoid nesting
if cls == SANSKRIT_VERSE_CLASS:
html = decode_html_content(p)
if html:
stripped = re.sub(r"</?i>", "", html)
result.append(f'<div style="text-align:center"><i>{stripped}</i></div>')
continue
# Body paragraphs (p6, p35, p30)
if cls in BODY_CLASSES:
html = decode_html_content(p)
if html:
result.append(html)
continue
# Any other class — try to extract content (be permissive)
html = decode_html_content(p)
if html:
result.append(html)
return result
# ---------------------------------------------------------------------------
# Main extraction logic
# ---------------------------------------------------------------------------
def extract_nod(soup: BeautifulSoup) -> dict:
"""Extract the entire Nectar of Devotion from the parsed HTML.
Returns a dict with:
"intro_segments": list of intro segment dicts
"chapters": list of chapter dicts
"concluding_words": concluding words dict
"parts": list of part assignment dicts
"chapter_listing": list of {number, title} for intro.json
"""
all_paragraphs = list(soup.find_all("p"))
# Step 1: Find NoD boundaries
nod_start, nod_end = find_nod_boundaries(all_paragraphs)
print(f" NoD content: paragraphs {nod_start} to {nod_end}")
print(f" Total paragraphs in range: {nod_end - nod_start}")
# Step 2: Find all bookmarks
bookmarks = find_all_bookmarks(all_paragraphs, nod_start, nod_end)
print(f" Found {len(bookmarks)} NoD bookmarks")
# Step 3: Categorize bookmarks into intro vs chapter
intro_bookmarks = [] # (para_idx, segment_name)
chapter_bookmarks = [] # (para_idx, chapter_num, title)
for pos, text in bookmarks:
ch = parse_chapter_bookmark(text)
if ch:
chapter_bookmarks.append((pos, ch[0], ch[1]))
else:
intro_name = parse_intro_bookmark(text)
if intro_name:
intro_bookmarks.append((pos, intro_name))
print(f" Intro bookmarks: {len(intro_bookmarks)}")
print(f" Chapter bookmarks: {len(chapter_bookmarks)}")
# Step 4: Track PART assignments by scanning for p34 tags
# We need to know which chapters belong to which part.
part_assignments = {} # chapter_num -> part_num
parts_data = [] # list of {number, title, chapters}
# Walk through all paragraphs in NoD range and track parts
# We process this before extracting chapters so we know part assignments
part_positions = [] # (para_idx, part_title)
for i in range(nod_start, nod_end):
p = all_paragraphs[i]
cls = get_css_class(p)
if cls == PART_HEADER_CLASS:
text = decode_text(p)
if text:
part_positions.append((i, text))
print(f" Part headers found: {len(part_positions)}")
for pos, title in part_positions:
print(f" Line ~{pos}: {title}")
# Assign parts to chapters based on position
# A chapter belongs to the most recent PART header before its bookmark
part_number_map = {}
for pos, title in part_positions:
# Extract part number from title like "PART ONE", "PART TWO", etc.
part_num = None
title_upper = title.upper()
if "ONE" in title_upper or "I" == title_upper.replace("PART", "").strip():
part_num = 1
elif "TWO" in title_upper or "II" == title_upper.replace("PART", "").strip():
part_num = 2
elif "THREE" in title_upper or "III" == title_upper.replace("PART", "").strip():
part_num = 3
elif "FOUR" in title_upper or "IV" == title_upper.replace("PART", "").strip():
part_num = 4
if part_num:
part_number_map[pos] = part_num
# Now assign each chapter to a part
sorted_part_positions = sorted(part_number_map.keys())
for ch_pos, ch_num, ch_title in chapter_bookmarks:
# Find the most recent PART header before this chapter's bookmark
assigned_part = None
for pp in sorted_part_positions:
if pp < ch_pos:
assigned_part = part_number_map[pp]
else:
break
if assigned_part:
part_assignments[ch_num] = assigned_part
# Build parts_data
for part_num in sorted(set(part_assignments.values())):
chs = sorted([ch for ch, pn in part_assignments.items() if pn == part_num])
part_title = f"Part {'One' if part_num == 1 else 'Two' if part_num == 2 else 'Three' if part_num == 3 else 'Four'}"
parts_data.append(
{
"number": part_num,
"title": part_title,
"chapters": chs,
}
)
# Step 5: Extract intro segments
intro_segments = []
for seg_def in INTRO_SEGMENTS:
# Find matching bookmark
bookmark_pos = None
for pos, name in intro_bookmarks:
if (
seg_def["bookmark_pattern"].endswith(name)
or name == seg_def["bookmark_pattern"].split(": ", 1)[-1]
):
bookmark_pos = pos
break
if bookmark_pos is None:
print(f" WARNING: Could not find bookmark for '{seg_def['slug']}'")
continue
# Content starts after the bookmark
content_start = bookmark_pos + 1
# Content ends at the next bookmark (intro or chapter) or PART header
content_end = nod_end
all_section_starts = sorted(
[pos for pos, _ in intro_bookmarks]
+ [pos for pos, _, _ in chapter_bookmarks]
)
for s in all_section_starts:
if s > bookmark_pos:
content_end = s
break
paragraphs = extract_paragraphs(
all_paragraphs, content_start, content_end, is_intro=True
)
seg = {
"slug": seg_def["slug"],
"title": seg_def["title"],
"type": "intro-segment",
"book": "nod",
"order": seg_def["order"],
"paragraphs": paragraphs,
}
intro_segments.append(seg)
# Step 6: Extract chapters
chapters = []
for idx, (ch_pos, ch_num, ch_title) in enumerate(chapter_bookmarks):
content_start = ch_pos + 1
# Content ends at next chapter bookmark or "Concluding Words" p27 or nod_end
if idx + 1 < len(chapter_bookmarks):
content_end = chapter_bookmarks[idx + 1][0]
else:
# Last chapter (51) — content ends at "Concluding Words" p27 heading
# or at nod_end. We need to find where "Concluding Words" starts.
# Look for p27 containing "Concluding Words" after the last chapter bookmark.
concluding_pos = None
for i in range(ch_pos + 1, nod_end):
p = all_paragraphs[i]
cls = get_css_class(p)
if cls == CHAPTER_TITLE_CLASS:
text = decode_text(p)
if "Concluding Words" in text:
concluding_pos = i
break
if concluding_pos:
content_end = concluding_pos
else:
content_end = nod_end
# For the PART header: if a PART header appears right before this
# chapter's bookmark, include it in this chapter's content range.
# The PART header might be 1-3 paragraphs before the bookmark (with
# possible p15 empty lines in between).
actual_start = content_start
for pp in sorted_part_positions:
# Check if this PART header is between the previous section end and
# this chapter's bookmark
if pp < ch_pos and pp >= content_start - 5:
# Check that there's no other bookmark between pp and ch_pos
has_other_bookmark = False
for bpos, _ in bookmarks:
if bpos > pp and bpos < ch_pos:
has_other_bookmark = True
break
if not has_other_bookmark:
actual_start = pp
paragraphs = extract_paragraphs(all_paragraphs, actual_start, content_end)
ch = {
"book": "nod",
"chapter": ch_num,
"title": ch_title,
"type": "chapter",
"paragraphs": paragraphs,
}
chapters.append(ch)
# Step 7: Extract "Concluding Words"
concluding_words = None
# Find the "Concluding Words" p27 heading after chapter 51
last_ch_pos = chapter_bookmarks[-1][0] if chapter_bookmarks else nod_start
for i in range(last_ch_pos, nod_end):
p = all_paragraphs[i]
cls = get_css_class(p)
if cls == CHAPTER_TITLE_CLASS:
text = decode_text(p)
if "Concluding Words" in text:
# Extract from this heading to nod_end
paragraphs = extract_paragraphs(all_paragraphs, i, nod_end)
concluding_words = {
"book": "nod",
"chapter": None,
"title": "Concluding Words",
"type": "conclusion",
"slug": "concluding-words",
"paragraphs": paragraphs,
}
break
# Step 8: Build chapter listing for intro.json
chapter_listing = [
{"number": ch["chapter"], "title": ch["title"]} for ch in chapters
]
return {
"intro_segments": intro_segments,
"chapters": chapters,
"concluding_words": concluding_words,
"parts": parts_data,
"chapter_listing": chapter_listing,
}
# ---------------------------------------------------------------------------
# Output
# ---------------------------------------------------------------------------
def write_json(data, filepath):
"""Write data as formatted JSON to filepath, creating directories as needed."""
os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
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_base = os.path.join(base_dir, "lib", "jsons", "books", "nod")
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")
print("Extracting The Nectar of Devotion (1970 Edition)...\n")
result = extract_nod(soup)
intro_segments = result["intro_segments"]
chapters = result["chapters"]
concluding_words = result["concluding_words"]
parts = result["parts"]
chapter_listing = result["chapter_listing"]
# --- Write intro.json ---
intro_meta = {
"book": "nod",
"title": "The Nectar of Devotion",
"subtitle": "A Summary Study of Śrīla Rūpa Gosvāmī's Bhakti-rasāmṛta-sindhu",
"author": "His Divine Grace A.C. Bhaktivedanta Swami Prabhupāda",
"year": 1970,
"description": "The complete science of bhakti-yoga, or devotional service to Lord Kṛṣṇa.",
"totalChapters": 51,
"parts": parts,
"chapters": chapter_listing,
}
intro_path = os.path.join(output_base, "intro.json")
write_json(intro_meta, intro_path)
print(f"\n Written: {intro_path}")
# --- Write intro segments ---
print(f"\n--- Intro Segments ---")
for seg in intro_segments:
filepath = os.path.join(output_base, "intro", f"{seg['slug']}.json")
write_json(seg, filepath)
print(
f" {seg['order']}. {seg['title']}: {len(seg['paragraphs'])} paragraphs → {filepath}"
)
# --- Write chapters ---
print(f"\n--- Chapters ---")
for ch in chapters:
ch_num_str = str(ch["chapter"]).zfill(2)
filepath = os.path.join(output_base, "chapters", f"{ch_num_str}.json")
write_json(ch, filepath)
part_info = ""
for p in parts:
if ch["chapter"] in p["chapters"]:
part_info = f" [Part {p['number']}]"
break
print(
f" Ch {ch['chapter']:>2}: {len(ch['paragraphs']):>3} paragraphs — "
f"{ch['title']}{part_info}"
)
# --- Write concluding words ---
if concluding_words:
filepath = os.path.join(output_base, "chapters", "concluding-words.json")
write_json(concluding_words, filepath)
print(
f"\n Concluding Words: {len(concluding_words['paragraphs'])} paragraphs → {filepath}"
)
# --- Summary ---
total_intro_paras = sum(len(s["paragraphs"]) for s in intro_segments)
total_chapter_paras = sum(len(ch["paragraphs"]) for ch in chapters)
total_concluding_paras = (
len(concluding_words["paragraphs"]) if concluding_words else 0
)
total_paras = total_intro_paras + total_chapter_paras + total_concluding_paras
print(f"\n{'=' * 60}")
print(f" EXTRACTION SUMMARY")
print(f"{'=' * 60}")
print(f" Intro segments: {len(intro_segments)}")
print(f" Chapters: {len(chapters)}")
print(f" Concluding Words: {'Yes' if concluding_words else 'No'}")
print(f" Parts: {len(parts)}")
for p in parts:
print(
f" Part {p['number']} ({p['title']}): chapters {p['chapters'][0]}-{p['chapters'][-1]} ({len(p['chapters'])} chapters)"
)
print(f" Total paragraphs: {total_paras}")
print(f" Intro: {total_intro_paras}")
print(f" Chapters: {total_chapter_paras}")
print(f" Concluding: {total_concluding_paras}")
print(f" Avg paras/chapter: {total_chapter_paras / len(chapters):.1f}")
print(f" Output directory: {output_base}/")
# --- Samples ---
print(f"\n--- Sample: Chapter 1 ---")
if chapters:
ch1 = chapters[0]
for i, p in enumerate(ch1["paragraphs"][:4]):
preview = re.sub(r"<[^>]+>", "", p)[:100]
print(f" [{i}] {preview}...")
print(f"\n--- Sample: Chapter {chapters[-1]['chapter']} (last) ---")
if chapters:
last = chapters[-1]
for i, p in enumerate(last["paragraphs"][:3]):
preview = re.sub(r"<[^>]+>", "", p)[:100]
print(f" [{i}] {preview}...")
if concluding_words:
print(f"\n--- Sample: Concluding Words ---")
for i, p in enumerate(concluding_words["paragraphs"][:3]):
preview = re.sub(r"<[^>]+>", "", p)[:100]
print(f" [{i}] {preview}...")
# --- Quality Checks ---
print(f"\n--- Quality Checks ---")
# Check all 51 chapters present
expected_chapters = set(range(1, 52))
actual_chapters = set(ch["chapter"] for ch in chapters)
missing = expected_chapters - actual_chapters
extra = actual_chapters - expected_chapters
if missing:
print(f" ⚠ Missing chapters: {sorted(missing)}")
if extra:
print(f" ⚠ Extra chapters: {sorted(extra)}")
if not missing and not extra:
print(f" ✅ All 51 chapters present")
# Check all 3 intro segments present
expected_intros = {"front-flap", "preface", "introduction"}
actual_intros = {s["slug"] for s in intro_segments}
missing_intros = expected_intros - actual_intros
if missing_intros:
print(f" ⚠ Missing intro segments: {missing_intros}")
else:
print(f" ✅ All 3 intro segments present")
# Check "Thus ends" markers in chapters
chapters_with_end = 0
for ch in chapters:
has_end = any("Thus end" in p for p in ch["paragraphs"])
if has_end:
chapters_with_end += 1
print(f" ℹ {chapters_with_end}/{len(chapters)} chapters have 'Thus ends' markers")
# Check empty chapters
empty_chapters = [ch["chapter"] for ch in chapters if len(ch["paragraphs"]) == 0]
if empty_chapters:
print(f" ⚠ Empty chapters (0 paragraphs): {empty_chapters}")
else:
print(f" ✅ No empty chapters")
# Check ScaGoudy decoding — look for common encoded characters that should have been decoded
# Note: ñ is excluded because it's a valid IAST output character (ï→ñ mapping)
encoded_chars = set("äåöëòçüéàùè")
suspect_count = 0
for ch in chapters:
for p in ch["paragraphs"]:
# Only flag if the encoded chars appear outside HTML tags
plain = re.sub(r"<[^>]+>", "", p)
for c in encoded_chars:
if c in plain:
# Many of these are legitimate uses (e.g., German loanwords)
# but in this context they almost always indicate ScaGoudy encoding
suspect_count += 1
break
if suspect_count == 0:
print(f" ✅ No suspected undecoded ScaGoudy characters found")
else:
print(
f" ℹ {suspect_count} paragraphs contain characters that may be undecoded ScaGoudy"
)
# Check part assignments
all_part_chapters = set()
for p in parts:
all_part_chapters.update(p["chapters"])
unassigned = [
ch["chapter"] for ch in chapters if ch["chapter"] not in all_part_chapters
]
if unassigned:
print(f" ⚠ Chapters not assigned to any part: {unassigned}")
else:
print(f" ✅ All chapters assigned to parts")
# Verify ScaGoudy decoding with a spot check on known text
print(f"\n--- ScaGoudy Decoding Spot Check ---")
# Chapter 1 should contain "Kṛṣṇa" (encoded as "Kåñëa")
if chapters:
ch1_text = " ".join(chapters[0]["paragraphs"][:5])
if "Kṛṣṇa" in ch1_text:
print(f" ✅ 'Kṛṣṇa' correctly decoded in Chapter 1")
elif "Kåñëa" in ch1_text:
print(f" ⚠ 'Kåñëa' still encoded — ScaGoudy decoding may have failed!")
else:
print(
f" ℹ 'Kṛṣṇa' not found in Chapter 1 first 5 paragraphs (may be normal)"
)
# Check for "Śrīmad-Bhāgavatam" (encoded as "Çrémad-Bhägavatam")
if "Śrīmad-Bhāgavatam" in ch1_text:
print(f" ✅ 'Śrīmad-Bhāgavatam' correctly decoded")
elif "Çrémad-Bhägavatam" in ch1_text:
print(f" ⚠ ScaGoudy decoding incomplete for 'Çrémad-Bhägavatam'")
print(f"\nDone! ✨")
if __name__ == "__main__":
main()