"""Reads paper records out of the acl-anthology package's data model.""" from pathlib import Path from typing import Iterator from acl_anthology import Anthology def _author_name(author) -> str: """Convert an author entry to a plain display string. In the real acl_anthology package (verified against the installed 1.2.0 source), Paper.authors is a tuple of NameSpecification objects, each exposing a `.name` property returning a `Name` object with `.as_first_last()`. We prefer that when available, but fall back to plain str() for anything else (e.g. plain strings in tests/mocks), since str() is idempotent on strings and works for any object with __str__. """ name = getattr(author, "name", None) if name is not None and hasattr(name, "as_first_last"): return name.as_first_last() return str(author) def _markup_text(value) -> str: """Convert a MarkupText-like value (or plain string/None) to plain text. Paper.title is a `MarkupText` (has `.as_text()`); Paper.abstract is an `Optional[MarkupText]`. Fall back to str() for plain strings/mocks. """ if value is None: return "" if hasattr(value, "as_text"): return value.as_text() return str(value) def _bibtex(paper) -> str: """Generate the canonical ACL Anthology BibTeX entry for a paper. `Paper.to_bibtex()` (verified against the installed acl_anthology==1.2.0 source) produces the same BibTeX the anthology website serves — including author, booktitle/journal, pages, editor, publisher, address, doi, etc. Some papers (frontmatter, or those whose bibkey is `NO_BIBKEY`) raise `ValueError` when a bibkey is unavailable; return "" for those so they stay citatation-less rather than crashing the sync. Also coerces non-string returns (e.g. unittest mocks) to "". """ try: value = paper.to_bibtex() except Exception: return "" return value if isinstance(value, str) else "" def _pdf_url(paper) -> str: """Return the direct PDF URL for a paper, or "" if unavailable. `Paper.pdf` (verified against the installed acl_anthology==1.2.0 source) is an `Optional[PDFReference]` whose `.url` property returns the canonical anthology PDF URL. Frontmatter and some older papers have no PDF reference. Mirrors `_bibtex`'s defensive try/except for mocks/edge cases. """ try: pdf = paper.pdf if pdf is None: return "" value = pdf.url except Exception: return "" return value if isinstance(value, str) else "" def iter_papers(anthology_path: str) -> Iterator[dict]: """Iterate over every paper in a local acl-anthology data checkout. `anthology_path` is expected to be the root of a cloned acl-anthology git repo (i.e. the directory containing `data/`), not a repo URL. Verified against the installed acl_anthology==1.2.0 source: - `Paper.id` is only unique within its parent Volume (e.g. "1"); the globally unique identifier is `Paper.full_id` (e.g. "2023.acl-long.1"). - `Paper.venue_acronym` does not exist on Paper; it's on the parent Volume (`Paper.parent.venue_acronym`), inherited from the collection. - `Paper.url` does not exist; the paper's canonical URL is `Paper.web_url`. - `Paper.year` returns a str (falls back to the parent Volume's year when not set directly on the paper); cast to int for storage. """ anthology = Anthology(datadir=str(Path(anthology_path) / "data")) for paper in anthology.papers(): authors = getattr(paper, "authors", None) or [] yield { "id": paper.full_id, "title": _markup_text(paper.title), "abstract": _markup_text(paper.abstract), "authors": ", ".join(_author_name(a) for a in authors), "venue": paper.parent.venue_acronym, "year": int(paper.year), "url": paper.web_url, "bibtex": _bibtex(paper), "pdf_url": _pdf_url(paper), }