"""Parse and normalize US legal citations (CFR regulations, USC statutes). Pure and network-free. Tolerant of the common written forms:: 29 CFR 1604.11 29 C.F.R. § 1604.11 29/1604.11 42 USC 1983 42 U.S.C. § 1983 42/1983 A parsed citation carries the corpus ("cfr"/"usc"), the title number, and the section as written (subsection parentheticals are preserved here and only dropped when building the per-section page URL in ``urls``). """ from __future__ import annotations import re from dataclasses import dataclass __all__ = ["Citation", "CitationError", "parse_cfr", "parse_usc"] class CitationError(ValueError): """Raised when a citation string cannot be parsed.""" @dataclass(frozen=True) class Citation: corpus: str # "cfr" | "usc" title: str section: str @property def label(self) -> str: word = "CFR" if self.corpus == "cfr" else "USC" return f"{self.title} {word} {self.section}" # Match the corpus token in any common spelling (CFR / C.F.R. / USC / U.S.C.) and # the section sign, so they can be stripped before pulling out the numbers. _CORPUS_RE = re.compile(r"(?i)\b(?:c\.?\s*f\.?\s*r\.?|u\.?\s*s\.?\s*c\.?)\b|§") def _split_title_section(text: str, *, corpus: str) -> tuple[str, str]: cleaned = _CORPUS_RE.sub(" ", text) cleaned = cleaned.replace("/", " ") tokens = [t.strip(".,;") for t in cleaned.split() if t.strip(".,;")] nums = [t for t in tokens if any(ch.isdigit() for ch in t)] word = "CFR" if corpus == "cfr" else "USC" if len(nums) < 2: raise CitationError( f"Could not read a {word} citation from {text!r}. " f"Expected something like '29 {word} 1604.11'." ) title, section = nums[0], nums[1] if not title.isdigit(): raise CitationError(f"{title!r} is not a valid {word} title number (from {text!r}).") return title, section def parse_cfr(text: str) -> Citation: """Parse a Code of Federal Regulations citation, e.g. ``29 CFR 1604.11``.""" title, section = _split_title_section(text, corpus="cfr") return Citation("cfr", title, section) def parse_usc(text: str) -> Citation: """Parse a US Code citation, e.g. ``42 USC 1983``.""" title, section = _split_title_section(text, corpus="usc") return Citation("usc", title, section)