Skip to content
Merged
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ dependencies = [
"pypdf>=3.17.0",
"lxml>=4.9.0",
"matplotlib>=3.5.0",
"citeproc-py>=0.9.0",
"citeproc-py-styles>=0.1.5",
]

[dependency-groups]
Expand Down
54 changes: 49 additions & 5 deletions src/lit_agent/identifiers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,17 +611,22 @@ def render_bibliography_to_strings(
}

try:
entries = list(resolution_result.citations.values())
style_obj = CitationStylesStyle(style, validate=False, locale=locale)
entries = _prepare_citeproc_entries(resolution_result.citations.values())
style_name = _resolve_csl_style(style)
style_obj = CitationStylesStyle(style_name, validate=False, locale=locale)
source = CiteProcJSON(entries)
bibliography = CitationStylesBibliography(style_obj, source, formatter.plain)

for item in source.items:
citation = Citation([CitationItem(item.id)])
for item_id in source:
citation = Citation([CitationItem(item_id)])
bibliography.register(citation)

rendered = [str(entry) for entry in bibliography.bibliography()]
return rendered, {"renderer": "citeproc-py", "style": style, "locale": locale}
return rendered, {
"renderer": "citeproc",
"style": style,
"locale": locale,
}
except Exception as exc: # pragma: no cover - defensive
return _render_compact(resolution_result), {
"renderer": "fallback",
Expand Down Expand Up @@ -678,6 +683,45 @@ def _extract_year(citation: Dict[str, Any]) -> Optional[int]:
return None


def _prepare_citeproc_entries(
citations: Iterable[Dict[str, Any]],
) -> List[Dict[str, Any]]:
"""Normalize CSL entries to avoid citeproc parsing errors."""

prepared: List[Dict[str, Any]] = []
for index, citation in enumerate(citations, start=1):
normalized = dict(citation)

citation_id = normalized.get("id")
normalized["id"] = (
str(citation_id) if citation_id not in [None, ""] else str(index)
)

if not normalized.get("type"):
normalized["type"] = "article-journal"

# Remove internal fields citeproc does not understand
normalized.pop("resolution", None)

# Ensure author list exists
if normalized.get("author") is None:
normalized["author"] = []

prepared.append(normalized)

return prepared


def _resolve_csl_style(style: str) -> str:
"""Map friendly style aliases to canonical CSL identifiers."""

aliases = {
"chicago": "chicago-author-date",
}
normalized = style.lower()
return aliases.get(normalized, normalized)


def _import_citeproc():
"""Import citeproc modules, isolated for easier testing."""

Expand Down
Loading
Loading