| """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), |
| } |
|
|