Files
TOIFAS/code/check_bib_records.py
T
KiHoLee 2f1bce706b 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/.
2026-08-29 13:18:48 +09:00

127 lines
4.4 KiB
Python

# -*- 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())