Skip to content
Merged
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
35 changes: 31 additions & 4 deletions bibtex_linter/ieeetran_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,30 @@
)


# LaTeX escapes of special characters that can appear in a DOI, mapped to the character they represent
_LATEX_ESCAPES = {
r"\textasciitilde{}": "~",
r"\textasciitilde": "~",
r"\~{}": "~",
r"\_": "_",
r"\#": "#",
r"\%": "%",
r"\&": "&",
r"\$": "$",
}
_LATEX_ESCAPE_PATTERN = re.compile("|".join(re.escape(escape) for escape in _LATEX_ESCAPES))


def _unescape_latex(text: str) -> str:
"""
Replace the LaTeX escapes of special characters (e.g. `\\_`) in the given text with the characters themselves.

:param text: The text to unescape
:return: The text with all escaped special characters unescaped
"""
return _LATEX_ESCAPE_PATTERN.sub(lambda match: _LATEX_ESCAPES[match.group(0)], text)


@linter_rule(entry_type=None)
def check_url_field(entry: BibTeXEntry) -> List[str]:
"""
Expand All @@ -22,8 +46,9 @@ def check_url_field(entry: BibTeXEntry) -> List[str]:
doi: \\href{https://doi.org/10.xxxx/yyy}{10.xxxx/yyy}
```
Note, that the backslash had to be escaped here and is only meant to be a single one.
In the DOI form, the shown DOI must be the same as the DOI in the URL. Since a DOI is persistent, it has no
access date.
In the DOI form, the shown DOI must be the same as the DOI in the URL. Special characters in the shown DOI may be
escaped (e.g. `10.1007/11574620\\_45`), since the shown DOI is typeset as normal text. Since a DOI is persistent,
it has no access date.

:param entry: The BibTeXEntry
:return: A list of string descriptions of rule violations for this entry.
Expand All @@ -38,8 +63,10 @@ def check_url_field(entry: BibTeXEntry) -> List[str]:
if "note" in entry.fields.keys():
note_content: str = entry.fields["note"]
online_pattern = r"^\[ONLINE\]\. Available: \\url\{(.+?)\}, Accessed: (\d{4}-\d{2}-\d{2})$"
doi_pattern = r"^doi: \\href\{https://doi\.org/(10\.\d{4,9}/\S+)\}\{\1\}$"
if not (re.match(online_pattern, note_content) or re.match(doi_pattern, note_content)):
doi_pattern = r"^doi: \\href\{https://doi\.org/(10\.\d{4,9}/\S+)\}\{(\S+)\}$"
doi_match = re.match(doi_pattern, note_content)
is_valid_doi = doi_match is not None and doi_match.group(1) == _unescape_latex(doi_match.group(2))
if not (re.match(online_pattern, note_content) or is_valid_doi):
invariant_violations.append(
"Contains a malformed field [note]. "
"Make sure the [note] field follows one of the following patterns: "
Expand Down
32 changes: 32 additions & 0 deletions test/test_verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,38 @@ def test_doi_note_mismatching_doi(self) -> None:
)
self.assertEqual([self.MALFORMED_NOTE], check_url_field(entry))

def test_doi_note_unescaped_underscore(self) -> None:
entry = BibTeXEntry(
entry_type="incollection",
name="doi_underscore",
fields={"note": "doi: \\href{https://doi.org/10.1007/11574620_45}{10.1007/11574620_45}"}
)
self.assertEqual([], check_url_field(entry))

def test_doi_note_escaped_special_characters(self) -> None:
for url_doi, shown_doi in (
("10.1007/11574620_45", "10.1007/11574620\\_45"),
("10.1007/978-3-642-25073-6_18", "10.1007/978-3-642-25073-6\\_18"),
("10.1234/a#b%c&d$e", "10.1234/a\\#b\\%c\\&d\\$e"),
("10.1234/a~b", "10.1234/a\\textasciitilde{}b"),
("10.1234/a~b", "10.1234/a\\~{}b"),
):
with self.subTest(shown_doi=shown_doi):
entry = BibTeXEntry(
entry_type="incollection",
name="doi_escaped",
fields={"note": f"doi: \\href{{https://doi.org/{url_doi}}}{{{shown_doi}}}"}
)
self.assertEqual([], check_url_field(entry))

def test_doi_note_escaped_mismatching_doi(self) -> None:
entry = BibTeXEntry(
entry_type="incollection",
name="doi_escaped_mismatch",
fields={"note": "doi: \\href{https://doi.org/10.1007/11574620_45}{10.1007/11574620\\_46}"}
)
self.assertEqual([self.MALFORMED_NOTE], check_url_field(entry))

def test_doi_note_with_access_date(self) -> None:
entry = BibTeXEntry(
entry_type="article",
Expand Down
Loading