citefind: chapter and verse is not a work in a bibliography

Found by putting a real model at the end of the chain: a page of notes from a
theological book composed a prompt asking who wrote Matthew 4:17, and the
model rightly answered nothing for all twenty-three entries.

Those are scripture references, which is most of what the notes of such a book
are, and they are now not references at all — no record, and no question put
to anybody about them. A journal and its volume look similar on the page and
are still read as what they are.

Also: the stop that belonged to a marker no longer opens the entry after it.
This commit is contained in:
funkpower
2026-09-20 11:39:41 -04:00
parent 2a2ecdc018
commit b70fdbd413
3 changed files with 66 additions and 1 deletions
+5
View File
@@ -181,6 +181,11 @@ back. Nor does a run carrying a page reference or a semicolon count as one:
that is two entries run together, or a reference to a work rather than the
name of one.
**Take chapter and verse for a work.** `Matthew 4:17` and `Cf. Deuteronomy
18:15-19; Isaiah 53` are scripture references, which is most of what the notes
of a theological book are. Nobody is looking for the publisher of Galatians,
so they are not references and no record is written for them.
**Name a place of publication as the publisher.** `(London, 1973)` says where
a book was published and not by whom, so `container` stays empty. The place
is still in `text`.
+49
View File
@@ -128,6 +128,18 @@ static NOT_A_TITLE: LazyLock<Regex> = LazyLock::new(|| {
.expect("a valid pattern")
});
/// A reference to chapter and verse: `Matthew 4:17`, `1 Peter 2:6-8`.
///
/// A book of the Bible is not a work in a bibliography — nobody is looking
/// for the publisher of Galatians — and a page of them is what the notes of
/// a theological book largely are.
static VERSE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r"(?i)^(?:cf\.?|see|and)?\s*(?:[1-4]\s+)?\p{L}[\p{L}.]{2,14}\.?\s+\d{1,3}(?::\d{1,3}(?:[-–]\d{1,3})?)?\.?$",
)
.expect("a valid pattern")
});
/// A page reference or a bracketed imprint: what a citation has and a remark
/// about the argument does not.
static CITATION_ANCHOR: LazyLock<Regex> = LazyLock::new(|| {
@@ -216,6 +228,9 @@ pub fn one(text: &str, marker: Option<&str>) -> Option<Reference> {
if !CITATION_SIGNAL.is_match(text) {
return None;
}
if is_scripture(text) {
return None;
}
// A note that runs on for six sentences with no imprint, no page
// reference and no identifier is a remark about the argument. The LLM
// path this replaces was told to skip those; a parser has to decide.
@@ -436,6 +451,20 @@ fn strip_markup(text: &str) -> String {
without_tags.replace(['*', '_'], "").trim().to_string()
}
/// Whether an entry is chapter and verse and nothing else.
///
/// Several of them separated by semicolons is still a scripture reference
/// and still not a work: `Cf. Deuteronomy 18:15-19; Isaiah 53; Acts 1:6`.
fn is_scripture(text: &str) -> bool {
let text = text.trim_start_matches(['.', ' ']);
let parts: Vec<&str> = text
.split(';')
.map(str::trim)
.filter(|part| !part.is_empty())
.collect();
!parts.is_empty() && parts.iter().all(|part| VERSE.is_match(part))
}
/// Every identifier the entry carries, normalised.
///
/// Order is fixed rather than found: two entries carrying the same
@@ -1075,6 +1104,26 @@ mod tests {
assert_eq!(found.container, None);
}
#[test]
fn chapter_and_verse_is_not_a_work_in_a_bibliography() {
for verse in [
"Matthew 4:17",
". Cf. Deuteronomy 18:15-19; Isaiah 53; Acts 1:6",
"1 Peter 2:6-8",
"Matthew 13:43; cf Exodus 34:29-35",
] {
assert!(one(verse, None).is_none(), "{verse}");
}
// A journal and its volume look similar and are not the same thing.
assert!(
one(
"Altmann, M. 1951, A Study of Behaviour. Sociometry 14, 351-354.",
None
)
.is_some()
);
}
#[test]
fn a_heading_or_a_page_number_is_not_a_reference() {
for junk in ["Bibliography", "233", "Notes to pages 12-14", "see below"] {
+12 -1
View File
@@ -249,7 +249,11 @@ fn strip_marker(raw: &str) -> Entry {
let consumed = bare[..whole.end()].chars().count();
let cut = from[consumed];
return Entry {
text: text[cut..].trim_start().to_string(),
// `[4] . Smith, John` leaves the stop that belonged to the
// marker sitting in front of the entry.
text: text[cut..]
.trim_start_matches(|c: char| ".) ".contains(c))
.to_string(),
marker: Some(found[1].to_string()),
};
}
@@ -425,6 +429,13 @@ Another Anonymous Work. Cambridge, 2001.";
assert_eq!(entry.text, "Smith, *Title*, p. 4.");
}
#[test]
fn a_stop_that_belonged_to_the_marker_does_not_start_the_entry() {
let entry = strip_marker("[4] . Smith, John. *A Real Book*. Oxford, 1998.");
assert_eq!(entry.marker.as_deref(), Some("4"));
assert_eq!(entry.text, "Smith, John. *A Real Book*. Oxford, 1998.");
}
#[test]
fn an_entry_with_no_marker_keeps_all_of_itself() {
let entry = strip_marker("Smith, John. The Title. Oxford, 1998.");