File size: 4,070 Bytes
df51fac d1ccc47 df51fac 03b3d27 9ca8ee5 cbd62a3 df51fac d1ccc47 df51fac d1ccc47 03b3d27 d1ccc47 9ca8ee5 cbd62a3 df51fac | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | """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),
}
|