Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ Fixes:
- Fix 2 `TypeError`s raised in `get_citations(markup_text=...)` when
`clean_steps` was omitted or lacked `"html"`. The documented fallback that
prepends the `html` step was both unreachable and broken.
- Keep the pin cite on an `Id.` citation that refers to a section, like
`Id. § 1985`. The section mark is its own token and used to stop the pin
cite scan, so the pin cite was lost and the mark was reported as a separate
`UnknownCitation`. #299

## Current

Expand Down
11 changes: 10 additions & 1 deletion eyecite/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
PlaceholderCitationToken,
ReferenceCitation,
ResourceCitation,
SectionToken,
ShortCaseCitation,
StopWordToken,
SupraCitation,
SupraToken,
Token,
Tokens,
UnknownCitation,
)
from eyecite.regexes import (
POST_FULL_CITATION_REGEX,
Expand Down Expand Up @@ -1015,7 +1017,9 @@ def match_on_tokens(
token = words[index]

# check for stop token
if strings_only and not isinstance(token, str):
# A section mark is a marker, not a citation: its text is ordinary
# pin cite vocabulary, so scanning has to see it rather than stop.
if strings_only and not isinstance(token, str | SectionToken):
break
if isinstance(token, ParagraphToken):
break
Expand Down Expand Up @@ -1109,6 +1113,11 @@ def filter_citations(citations: list[CitationBase]) -> list[CitationBase]:
filtered_citations.append(citation)
continue

# A section mark the previous citation took as its pin cite
# is that pin cite, not a citation of its own.
if isinstance(citation, UnknownCitation):
continue

# Known overlap case are parallel full citations
if not (
isinstance(citation, FullCaseCitation)
Expand Down
38 changes: 38 additions & 0 deletions tests/test_FindTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,44 @@ def test_no_duplicate_editions(self):
self.assertEqual(len(ambiguous.all_editions), 2)
self.assertEqual(len(set(ambiguous.all_editions)), 2)

def test_id_citation_with_section_pin_cite(self):
"""Does an Id. cite keep a section pin cite? (#299)

The section mark is its own token, and the pin cite scan used to stop
at any non-string token, so "Id. § 5" lost the pin cite and left the
mark behind as a citation of its own.
"""
# fmt: off
test_pairs = (
('42 U.S.C. § 1983. Id. § 1985.',
[law_citation('42 U.S.C. § 1983', reporter='U.S.C.',
groups={'title': '42', 'section': '1983'}),
id_citation('Id.', metadata={'pin_cite': '§ 1985'})]),
('Foo v. Bar 1 U.S. 12. Id. §§ 5-7.',
[case_citation(page='12',
metadata={'plaintiff': 'Foo', 'defendant': 'Bar'}),
id_citation('Id.', metadata={'pin_cite': '§§ 5-7'})]),
# The mark is glued to the number, so it is one token.
('Foo v. Bar 1 U.S. 12. Id. §5.',
[case_citation(page='12',
metadata={'plaintiff': 'Foo', 'defendant': 'Bar'}),
id_citation('Id.', metadata={'pin_cite': '§5'})]),
('Foo v. Bar 1 U.S. 12. Id. § 5, 7.',
[case_citation(page='12',
metadata={'plaintiff': 'Foo', 'defendant': 'Bar'}),
id_citation('Id.', metadata={'pin_cite': '§ 5, 7'})]),
# A page pin cite still works.
('Foo v. Bar 1 U.S. 12. Id. at 5.',
[case_citation(page='12',
metadata={'plaintiff': 'Foo', 'defendant': 'Bar'}),
id_citation('Id.', metadata={'pin_cite': 'at 5'})]),
# A mark that no citation absorbs is still reported on its own.
('lorem ipsum see § 99 of the U.S. code.',
[unknown_citation('§')]),
)
# fmt: on
self.run_test_pairs(test_pairs, "Id. citation with a section pin cite")

def test_find_law_citations(self):
"""Can we find citations from laws.json?"""
# fmt: off
Expand Down
Loading