Sync the release with the submitted manuscript
Figures, data and code as they stand at submission, including the bibliography record check that verifies every entry against its article in ref/.
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Verify every bibliography entry against the article's own first page.
|
||||
|
||||
The standard asks for one verdict per entry against the publisher record
|
||||
(12.23), and for a missing issue number to be completed "when the
|
||||
publisher record shows one" (8.6). Both are answerable from ref/,
|
||||
because the stored PDF is the published article and its running head
|
||||
carries the volume, the issue when the journal has one, the year and the
|
||||
first page.
|
||||
|
||||
This exists because an audit read eight entries with no `number` field
|
||||
as incomplete. They are not: IEEE now publishes TIFS, JSAC, TWC, TCOM
|
||||
and TSP with continuous volume pagination, and those articles' running
|
||||
heads read "VOL. n, YEAR" with no issue at all. Adding a number there
|
||||
would invent data. The check makes the distinction mechanical so the
|
||||
finding is not raised again.
|
||||
|
||||
Entries with no stored PDF are reported as unverifiable rather than
|
||||
passed, so the count of what remains unchecked is visible.
|
||||
|
||||
Run: python code/check_bib_records.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
BIB = ROOT / "references.bib"
|
||||
REF = ROOT / "ref"
|
||||
|
||||
# a running head, in the several shapes the venues use
|
||||
HEADS = [
|
||||
# IEEE journal with an issue: VOL. 22, NO. 12, and VOL. IT-24, NO. 3,
|
||||
re.compile(r"VOL\.\s*(?:[A-Z]{2}-)?(\d+)\s*,\s*NO\.\s*(\d+)", re.I),
|
||||
# IEEE journal on continuous volume pagination: VOL. 20, 2025
|
||||
re.compile(r"VOL\.\s*(\d+)\s*,\s*(?:19|20)\d{2}", re.I),
|
||||
# a journal that prints volume(issue): 24(6):801-812
|
||||
re.compile(r"\b(\d+)\((\d+)\)\s*:"),
|
||||
]
|
||||
def entries():
|
||||
txt = BIB.read_text(encoding="utf-8")
|
||||
for m in re.finditer(r"@(\w+)\s*\{([^,]+),(.*?)\n\}", txt, re.S):
|
||||
yield m.group(2).strip(), m.group(1).lower(), m.group(3)
|
||||
|
||||
|
||||
def field(body, name):
|
||||
m = re.search(r"\b%s\s*=\s*\{([^}]*)\}" % name, body)
|
||||
return m.group(1).strip() if m else None
|
||||
|
||||
|
||||
def head_of(pdf):
|
||||
import fitz
|
||||
d = fitz.open(pdf)
|
||||
t = " ".join(d[0].get_text().split())
|
||||
d.close()
|
||||
return t
|
||||
|
||||
|
||||
def main() -> int:
|
||||
checked = ok = 0
|
||||
problems, unverifiable, noissue = [], [], []
|
||||
for key, kind, body in entries():
|
||||
if key == "BSTcontrol":
|
||||
continue
|
||||
pdf = REF / (key + ".pdf")
|
||||
if not pdf.exists():
|
||||
unverifiable.append(key)
|
||||
continue
|
||||
checked += 1
|
||||
head = head_of(pdf)
|
||||
vol, num = field(body, "volume"), field(body, "number")
|
||||
why = []
|
||||
|
||||
printed_vol = printed_num = None
|
||||
for rx in HEADS:
|
||||
m = rx.search(head)
|
||||
if m:
|
||||
printed_vol = m.group(1)
|
||||
printed_num = m.group(2) if m.lastindex and m.lastindex > 1 \
|
||||
else None
|
||||
break
|
||||
if printed_vol and vol and printed_vol != vol:
|
||||
why.append("volume %s printed, %s in bib" % (printed_vol, vol))
|
||||
if printed_num and num and printed_num != num.split("--")[0]:
|
||||
why.append("issue %s printed, %s in bib" % (printed_num, num))
|
||||
if printed_num and not num:
|
||||
why.append("issue %s printed, none in bib" % printed_num)
|
||||
# a volume with no printed issue is the continuous-pagination case
|
||||
# 8.6 exempts, and a scanned cover page that omits an issue the
|
||||
# entry carries is not evidence against the entry
|
||||
if not printed_num:
|
||||
noissue.append(key)
|
||||
|
||||
pages = field(body, "pages")
|
||||
if pages:
|
||||
first = pages.split("--")[0].strip()
|
||||
if first and first not in head.replace(",", ""):
|
||||
why.append("first page %s not on the printed page" % first)
|
||||
|
||||
year = field(body, "year")
|
||||
if year and year not in head:
|
||||
why.append("year %s not on the printed page" % year)
|
||||
|
||||
if why:
|
||||
problems.append((key, why))
|
||||
else:
|
||||
ok += 1
|
||||
|
||||
for key, why in problems:
|
||||
print(" MISMATCH %-26s %s" % (key, "; ".join(why)))
|
||||
print()
|
||||
print("verified against ref/: %d of %d entries, %d clean, %d mismatched"
|
||||
% (checked, checked + len(unverifiable), ok, len(problems)))
|
||||
if noissue:
|
||||
print("printed record carries no issue number (%d): %s"
|
||||
% (len(noissue), ", ".join(sorted(noissue))))
|
||||
if unverifiable:
|
||||
print("no stored PDF, not verifiable here: %s"
|
||||
% ", ".join(sorted(unverifiable)))
|
||||
return 1 if problems else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user