公開前の確認用サイト
日常訳NICHIJOYAKUEnglish ↗

OPEN SOURCE

software/src/utils/validation.py

このサイトで読み、必要なファイルを保存できます。

日常訳はAIを用いて作成された翻訳です。人間の聖書学・原語の専門家による全訳の検証は確認されていません。既存の翻訳や原語資料と照らし合わせてお読みください。

← オープンソースの案内へ

software/src/utils/validation.py351 行このファイルを保存 ↓

ZIPに含まれるものと同じ公開ソースです。行番号から、その行へのリンクを保存できます。

ファイルの確認情報SHA-256 f968eccb3012761e493c92a963d052f504311e2d90a60ac46758b86d58b6087e
1"""Input validation utilities for Bible references."""23from __future__ import annotations45import re6from pathlib import Path7from typing import TYPE_CHECKING89from src.exceptions import InvalidReferenceError, VerseNotFoundError1011if TYPE_CHECKING:12    from src.data.loader import DataLoader1314# Valid book names (canonical list)15# New Testament books16NT_BOOKS = [17    "matthew", "mark", "luke", "john", "acts",18    "romans", "1corinthians", "2corinthians", "galatians", "ephesians",19    "philippians", "colossians", "1thessalonians", "2thessalonians",20    "1timothy", "2timothy", "titus", "philemon",21    "hebrews", "james", "1peter", "2peter",22    "1john", "2john", "3john", "jude", "revelation",23]2425# Old Testament books26OT_BOOKS = [27    "genesis", "exodus", "leviticus", "numbers", "deuteronomy",28    "joshua", "judges", "ruth", "1samuel", "2samuel",29    "1kings", "2kings", "1chronicles", "2chronicles",30    "ezra", "nehemiah", "esther", "job", "psalms",31    "proverbs", "ecclesiastes", "songofsolomon",32    "isaiah", "jeremiah", "lamentations", "ezekiel", "daniel",33    "hosea", "joel", "amos", "obadiah", "jonah",34    "micah", "nahum", "habakkuk", "zephaniah", "haggai",35    "zechariah", "malachi",36]3738ALL_BOOKS = OT_BOOKS + NT_BOOKS3940# Book name aliases for common variations41BOOK_ALIASES: dict[str, str] = {42    "gen": "genesis",43    "ex": "exodus",44    "exod": "exodus",45    "lev": "leviticus",46    "num": "numbers",47    "deut": "deuteronomy",48    "josh": "joshua",49    "judg": "judges",50    "1sam": "1samuel",51    "2sam": "2samuel",52    "1kgs": "1kings",53    "2kgs": "2kings",54    "1chr": "1chronicles",55    "2chr": "2chronicles",56    "neh": "nehemiah",57    "est": "esther",58    "ps": "psalms",59    "psa": "psalms",60    "psalm": "psalms",61    "prov": "proverbs",62    "eccl": "ecclesiastes",63    "song": "songofsolomon",64    "sos": "songofsolomon",65    "isa": "isaiah",66    "jer": "jeremiah",67    "lam": "lamentations",68    "ezek": "ezekiel",69    "dan": "daniel",70    "hos": "hosea",71    "ob": "obadiah",72    "mic": "micah",73    "nah": "nahum",74    "hab": "habakkuk",75    "zeph": "zephaniah",76    "hag": "haggai",77    "zech": "zechariah",78    "mal": "malachi",79    "matt": "matthew",80    "mat": "matthew",81    "mk": "mark",82    "lk": "luke",83    "jn": "john",84    "jhn": "john",85    "rom": "romans",86    "1cor": "1corinthians",87    "2cor": "2corinthians",88    "gal": "galatians",89    "eph": "ephesians",90    "phil": "philippians",91    "col": "colossians",92    "1thess": "1thessalonians",93    "2thess": "2thessalonians",94    "1tim": "1timothy",95    "2tim": "2timothy",96    "tit": "titus",97    "phm": "philemon",98    "phlm": "philemon",99    "heb": "hebrews",100    "jas": "james",101    "1pet": "1peter",102    "2pet": "2peter",103    "1jn": "1john",104    "2jn": "2john",105    "3jn": "3john",106    "rev": "revelation",107}108109110def normalize_book_name(book: str) -> str:111    """Normalize a book name to its canonical form.112113    Args:114        book: Book name (may be alias or variant).115116    Returns:117        Canonical lowercase book name.118119    Raises:120        InvalidReferenceError: If book name is not recognized.121    """122    book_lower = book.lower().strip()123124    # Remove common separators125    book_lower = book_lower.replace(" ", "").replace("-", "").replace("_", "")126127    # Check if it's already canonical128    if book_lower in ALL_BOOKS:129        return book_lower130131    # Check aliases132    if book_lower in BOOK_ALIASES:133        return BOOK_ALIASES[book_lower]134135    raise InvalidReferenceError(book, f"Unknown book name: {book}")136137138def validate_chapter(book: str, chapter: int) -> None:139    """Validate that a chapter number is valid for a book.140141    Args:142        book: Canonical book name.143        chapter: Chapter number.144145    Raises:146        InvalidReferenceError: If chapter is invalid.147    """148    if chapter < 1:149        raise InvalidReferenceError(150            f"{book} {chapter}",151            f"Chapter must be positive, got {chapter}",152        )153154    # Maximum chapters per book (approximate, will be validated against data)155    max_chapters = {156        "genesis": 50, "exodus": 40, "leviticus": 27, "numbers": 36,157        "deuteronomy": 34, "joshua": 24, "judges": 21, "ruth": 4,158        "1samuel": 31, "2samuel": 24, "1kings": 22, "2kings": 25,159        "1chronicles": 29, "2chronicles": 36, "ezra": 10, "nehemiah": 13,160        "esther": 10, "job": 42, "psalms": 150, "proverbs": 31,161        "ecclesiastes": 12, "songofsolomon": 8, "isaiah": 66, "jeremiah": 52,162        "lamentations": 5, "ezekiel": 48, "daniel": 12, "hosea": 14,163        "joel": 3, "amos": 9, "obadiah": 1, "jonah": 4, "micah": 7,164        "nahum": 3, "habakkuk": 3, "zephaniah": 3, "haggai": 2,165        "zechariah": 14, "malachi": 4, "matthew": 28, "mark": 16,166        "luke": 24, "john": 21, "acts": 28, "romans": 16,167        "1corinthians": 16, "2corinthians": 13, "galatians": 6,168        "ephesians": 6, "philippians": 4, "colossians": 4,169        "1thessalonians": 5, "2thessalonians": 3, "1timothy": 6,170        "2timothy": 4, "titus": 3, "philemon": 1, "hebrews": 13,171        "james": 5, "1peter": 5, "2peter": 3, "1john": 5,172        "2john": 1, "3john": 1, "jude": 1, "revelation": 22,173    }174175    book_lower = book.lower()176    if book_lower in max_chapters and chapter > max_chapters[book_lower]:177        raise InvalidReferenceError(178            f"{book} {chapter}",179            f"{book} only has {max_chapters[book_lower]} chapters",180        )181182183def validate_verse(book: str, chapter: int, verse: int) -> None:184    """Validate that a verse number is plausible.185186    Args:187        book: Canonical book name.188        chapter: Chapter number.189        verse: Verse number.190191    Raises:192        InvalidReferenceError: If verse is obviously invalid.193    """194    if verse < 1:195        raise InvalidReferenceError(196            f"{book} {chapter}:{verse}",197            f"Verse must be positive, got {verse}",198        )199200    # Most chapters have fewer than 200 verses (Psalm 119 has 176)201    if verse > 200:202        raise InvalidReferenceError(203            f"{book} {chapter}:{verse}",204            f"Verse number {verse} is implausibly high",205        )206207208def validate_reference(book: str, chapter: int, verse: int) -> str:209    """Validate a complete verse reference.210211    Args:212        book: Book name (may be alias).213        chapter: Chapter number.214        verse: Verse number.215216    Returns:217        Canonical book name.218219    Raises:220        InvalidReferenceError: If reference is invalid.221    """222    canonical_book = normalize_book_name(book)223    validate_chapter(canonical_book, chapter)224    validate_verse(canonical_book, chapter, verse)225    return canonical_book226227228def validate_reference_exists(229    loader: DataLoader,230    book: str,231    chapter: int,232    verse: int,233) -> None:234    """Validate that a verse actually exists in the data.235236    Args:237        loader: Data loader to check against.238        book: Canonical book name.239        chapter: Chapter number.240        verse: Verse number.241242    Raises:243        VerseNotFoundError: If verse doesn't exist in data.244    """245    # Load the verse and check if it has any content246    from src.models.verse import VerseReference247    ref = VerseReference(book=book, chapter=chapter, verse=verse)248    loaded = loader.load_verse(ref)249250    # Check if any source text is available251    has_content = (252        loaded.greek_text253        or loaded.hebrew_text254        or loaded.english_kjv255        or loaded.english_asv256        or loaded.japanese_meiji257        or loaded.japanese_taisho258    )259260    if not has_content:261        raise VerseNotFoundError(book, chapter, verse)262263264def parse_reference_string(ref_str: str) -> tuple[str, int, int]:265    """Parse a reference string like 'John 3:16' into components.266267    Supports formats:268    - 'John 3:16'269    - 'john 3:16'270    - 'john:3:16'271    - 'jn 3:16'272    - '1john 1:1'273    - '1 john 1:1'274275    Args:276        ref_str: Reference string to parse.277278    Returns:279        Tuple of (book, chapter, verse).280281    Raises:282        InvalidReferenceError: If format is invalid.283    """284    ref_str = ref_str.strip()285286    # Pattern for references like "John 3:16" or "1 John 1:1"287    pattern = r"^(\d?\s*[a-zA-Z]+)\s+(\d+):(\d+)$"288    match = re.match(pattern, ref_str)289290    if match:291        book_part = match.group(1).strip()292        chapter = int(match.group(2))293        verse = int(match.group(3))294        book = normalize_book_name(book_part)295        return book, chapter, verse296297    # Try alternate pattern "book:chapter:verse"298    pattern2 = r"^(\d?\s*[a-zA-Z]+):(\d+):(\d+)$"299    match2 = re.match(pattern2, ref_str)300301    if match2:302        book_part = match2.group(1).strip()303        chapter = int(match2.group(2))304        verse = int(match2.group(3))305        book = normalize_book_name(book_part)306        return book, chapter, verse307308    raise InvalidReferenceError(309        ref_str,310        "Expected format like 'John 3:16' or 'john:3:16'",311    )312313314def sanitize_book_name_for_path(book: str) -> str:315    """Sanitize a book name for safe use in file paths.316317    Prevents directory traversal attacks.318319    Args:320        book: Book name to sanitize.321322    Returns:323        Safe book name for paths.324325    Raises:326        InvalidReferenceError: If book name contains unsafe characters.327    """328    canonical = normalize_book_name(book)329330    # Additional safety check: ensure no path separators331    if "/" in canonical or "\\" in canonical or ".." in canonical:332        raise InvalidReferenceError(book, "Book name contains invalid path characters")333334    return canonical335336337def is_valid_book(book: str) -> bool:338    """Check if a book name is valid without raising an exception.339340    Args:341        book: Book name to check.342343    Returns:344        True if valid, False otherwise.345    """346    try:347        normalize_book_name(book)348        return True349    except InvalidReferenceError:350        return False351