@@ -0,0 +1,57 @@
|
||||
"""Bibliography parsing helpers (no docx dependency) and optional renderable."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
BIBLIO_LINE_RE = re.compile(
|
||||
r"^\[(\d+(?:\.\d+)?)\]:\s*(.+)$"
|
||||
)
|
||||
CITE_RE = re.compile(
|
||||
r"\[(\d+(?:\.\d+)?(?:\s*,\s*\d+(?:\.\d+)?)*)"
|
||||
r"(?:\s*,\s*[cс]\.\s*[\d\-–]+)?"
|
||||
r"\]"
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BiblioEntry:
|
||||
key: str
|
||||
text: str
|
||||
year: int | None = None
|
||||
|
||||
|
||||
def extract_year(text: str) -> int | None:
|
||||
years = re.findall(r"(?:^|[^\d])((?:19|20)\d{2})(?:[^\d]|$)", text)
|
||||
if not years:
|
||||
return None
|
||||
return max(int(y) for y in years)
|
||||
|
||||
|
||||
def parse_biblio_block(lines: list[str]) -> list[BiblioEntry]:
|
||||
entries = []
|
||||
for line in lines:
|
||||
m = BIBLIO_LINE_RE.match(line.strip())
|
||||
if m:
|
||||
key, text = m.group(1), m.group(2).strip()
|
||||
entries.append(BiblioEntry(key=key, text=text, year=extract_year(text)))
|
||||
return entries
|
||||
|
||||
|
||||
def find_citations(text: str) -> list[str]:
|
||||
seen: list[str] = []
|
||||
for m in CITE_RE.finditer(text):
|
||||
inner = m.group(1)
|
||||
for part in re.split(r"\s*,\s*", inner):
|
||||
part = part.strip()
|
||||
if part and part not in seen:
|
||||
seen.append(part)
|
||||
return seen
|
||||
|
||||
|
||||
def Bibliography(*args, **kwargs):
|
||||
"""Lazy wrapper to avoid importing docx/freetype at checker import time."""
|
||||
from .bibliography_renderable import Bibliography as _Bibliography
|
||||
return _Bibliography(*args, **kwargs)
|
||||
Reference in New Issue
Block a user