mirror of
https://git.pumpin.online/funkpower/catena.git
synced 2026-10-22 18:57:14 +00:00
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:
@@ -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
|
that is two entries run together, or a reference to a work rather than the
|
||||||
name of one.
|
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
|
**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
|
a book was published and not by whom, so `container` stays empty. The place
|
||||||
is still in `text`.
|
is still in `text`.
|
||||||
|
|||||||
@@ -128,6 +128,18 @@ static NOT_A_TITLE: LazyLock<Regex> = LazyLock::new(|| {
|
|||||||
.expect("a valid pattern")
|
.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
|
/// A page reference or a bracketed imprint: what a citation has and a remark
|
||||||
/// about the argument does not.
|
/// about the argument does not.
|
||||||
static CITATION_ANCHOR: LazyLock<Regex> = LazyLock::new(|| {
|
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) {
|
if !CITATION_SIGNAL.is_match(text) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
if is_scripture(text) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
// A note that runs on for six sentences with no imprint, no page
|
// 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
|
// 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.
|
// 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()
|
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.
|
/// Every identifier the entry carries, normalised.
|
||||||
///
|
///
|
||||||
/// Order is fixed rather than found: two entries carrying the same
|
/// Order is fixed rather than found: two entries carrying the same
|
||||||
@@ -1075,6 +1104,26 @@ mod tests {
|
|||||||
assert_eq!(found.container, None);
|
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]
|
#[test]
|
||||||
fn a_heading_or_a_page_number_is_not_a_reference() {
|
fn a_heading_or_a_page_number_is_not_a_reference() {
|
||||||
for junk in ["Bibliography", "233", "Notes to pages 12-14", "see below"] {
|
for junk in ["Bibliography", "233", "Notes to pages 12-14", "see below"] {
|
||||||
|
|||||||
@@ -249,7 +249,11 @@ fn strip_marker(raw: &str) -> Entry {
|
|||||||
let consumed = bare[..whole.end()].chars().count();
|
let consumed = bare[..whole.end()].chars().count();
|
||||||
let cut = from[consumed];
|
let cut = from[consumed];
|
||||||
return Entry {
|
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()),
|
marker: Some(found[1].to_string()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -425,6 +429,13 @@ Another Anonymous Work. Cambridge, 2001.";
|
|||||||
assert_eq!(entry.text, "Smith, *Title*, p. 4.");
|
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]
|
#[test]
|
||||||
fn an_entry_with_no_marker_keeps_all_of_itself() {
|
fn an_entry_with_no_marker_keeps_all_of_itself() {
|
||||||
let entry = strip_marker("Smith, John. The Title. Oxford, 1998.");
|
let entry = strip_marker("Smith, John. The Title. Oxford, 1998.");
|
||||||
|
|||||||
Reference in New Issue
Block a user