gedcomkit 0.1.13__tar.gz → 0.1.14__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/CHANGELOG.md +18 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/Cargo.lock +1 -1
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/Cargo.toml +1 -1
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/PKG-INFO +1 -1
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/bindings/gedcomkit-py/Cargo.lock +2 -2
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/bindings/gedcomkit-py/Cargo.toml +1 -1
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/src/build.rs +1 -2
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/src/lib.rs +43 -4
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/src/view.rs +61 -2
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/LICENSE +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/README.md +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/bindings/gedcomkit-py/LICENSE +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/bindings/gedcomkit-py/README.md +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/bindings/gedcomkit-py/gedcomkit.pyi +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/bindings/gedcomkit-py/src/lib.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom-spec/LICENSE +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom-spec/README.md +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom-spec/cardinalities.tsv +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom-spec/enumerations.tsv +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom-spec/enumerationsets.tsv +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom-spec/grammar.abnf +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom-spec/grammar.gedstruct +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom-spec/payloads.tsv +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom-spec/substructures.tsv +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/age-invalid.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/age-valid.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/atsign.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/char_ascii_1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/char_ascii_2.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/char_utf16be-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/char_utf16be-2.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/char_utf16le-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/char_utf16le-2.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/char_utf8-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/char_utf8-2.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/char_utf8-3.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/date-all.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/date-dual-invalid.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/date-dual-valid.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/enum-ext.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/filename-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/lang-all.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/notes-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/obje-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/obsolete-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/pedi-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/rela_1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/sour-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/tiny-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/5/xref-case.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/age-invalid.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/age-valid.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/atsign.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/char_ascii_1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/char_ascii_2.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/char_utf16be-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/char_utf16be-2.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/char_utf16le-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/char_utf16le-2.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/char_utf8-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/char_utf8-2.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/char_utf8-3.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/date-all.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/date-dual-invalid.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/enum-ext.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/filename-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/lang-all.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/notes-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/obje-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/obsolete-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/pedi-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/rela_1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/sour-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/tiny-1.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/7/xref-case.ged +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/LICENSE +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/gedcom7code/UPSTREAM-README.md +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/zip-interop/README.md +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/zip-interop/python-zip64.gdz +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/fixtures/vendored/zip-interop/python-zipfile.gdz +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/pyproject.toml +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/src/convert.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/src/dates.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/src/decode.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/src/family.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/src/gedzip.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/src/names.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/src/schema.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/src/synthetic.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/src/tags.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/src/validate.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/tests/endurance.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/tests/external_charset.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/tests/properties.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/tests/requirement_corpus.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/tests/spec_conformance.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/tests/typescript.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/tests/vendored_corpus.rs +0 -0
- {gedcomkit-0.1.13 → gedcomkit-0.1.14}/tests/version_conversion.rs +0 -0
|
@@ -5,6 +5,24 @@ format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
|
5
5
|
the project uses semantic versioning within the usual pre-1.0 compatibility
|
|
6
6
|
rules.
|
|
7
7
|
|
|
8
|
+
## [0.1.14] - 2026-10-10
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- `IndividualView::life_years` now reads the birth year from the first
|
|
13
|
+
`BIRT` that has a year, else the first `CHR` with one, else the first
|
|
14
|
+
`BAPM`. Before, it looked only at `BIRT` then `CHR`, and at the first event
|
|
15
|
+
with the tag even when its date did not parse, so a dateless `BIRT` ahead of
|
|
16
|
+
a dated one gave no year. `IndividualView::event_year` is unchanged. The
|
|
17
|
+
death year is still `DEAT` only.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- `Document::record` and `Document::record_mut` no longer allocate an
|
|
22
|
+
upper-cased copy of every identifier they scan; they compare ASCII
|
|
23
|
+
case-insensitively in place. Matching is identical to before (ASCII case
|
|
24
|
+
folding only, first duplicate wins) and to `Document::xref_index`.
|
|
25
|
+
|
|
8
26
|
## [0.1.13] - 2026-10-02
|
|
9
27
|
|
|
10
28
|
### Fixed
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# was checked on crates.io, npm, and PyPI before the initial release.
|
|
4
4
|
[package]
|
|
5
5
|
name = "gedcomkit"
|
|
6
|
-
version = "0.1.
|
|
6
|
+
version = "0.1.14"
|
|
7
7
|
description = "A byte-preserving GEDCOM document model: decoding, parsing, readings, version conversion, plausibility checks, and the GEDZIP container, for GEDCOM 5.5 through 7.x."
|
|
8
8
|
edition = "2024"
|
|
9
9
|
# The true minimum, not the workspace's toolchain: a parser library is a leaf
|
|
@@ -35,7 +35,7 @@ dependencies = [
|
|
|
35
35
|
|
|
36
36
|
[[package]]
|
|
37
37
|
name = "gedcomkit"
|
|
38
|
-
version = "0.1.
|
|
38
|
+
version = "0.1.14"
|
|
39
39
|
dependencies = [
|
|
40
40
|
"flate2",
|
|
41
41
|
"serde",
|
|
@@ -43,7 +43,7 @@ dependencies = [
|
|
|
43
43
|
|
|
44
44
|
[[package]]
|
|
45
45
|
name = "gedcomkit-py"
|
|
46
|
-
version = "0.1.
|
|
46
|
+
version = "0.1.14"
|
|
47
47
|
dependencies = [
|
|
48
48
|
"gedcomkit",
|
|
49
49
|
"pyo3",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# abi3 keeps it to one wheel per platform across Python versions.
|
|
3
3
|
[package]
|
|
4
4
|
name = "gedcomkit-py"
|
|
5
|
-
version = "0.1.
|
|
5
|
+
version = "0.1.14"
|
|
6
6
|
description = "Python bindings for gedcomkit: an archival-grade GEDCOM library — byte-preserving round-trip, encoding detection, GEDCOM 5.5 through 7.x, GEDZIP."
|
|
7
7
|
edition = "2024"
|
|
8
8
|
rust-version = "1.88"
|
|
@@ -455,14 +455,13 @@ impl Document {
|
|
|
455
455
|
),
|
|
456
456
|
));
|
|
457
457
|
}
|
|
458
|
-
let wanted = record.to_ascii_uppercase();
|
|
459
458
|
let owner = self
|
|
460
459
|
.records
|
|
461
460
|
.iter_mut()
|
|
462
461
|
.find(|node| {
|
|
463
462
|
node.xref.as_ref().map_or_else(
|
|
464
463
|
|| node.tag == record,
|
|
465
|
-
|xref| xref.
|
|
464
|
+
|xref| xref.eq_ignore_ascii_case(record),
|
|
466
465
|
)
|
|
467
466
|
})
|
|
468
467
|
.ok_or_else(|| fault(0, GedcomErrorKind::Structure, format!("no record {record}")))?;
|
|
@@ -847,23 +847,24 @@ impl Document {
|
|
|
847
847
|
/// case and `xref-case.ged` in the fixture corpus proves it matters.
|
|
848
848
|
#[must_use]
|
|
849
849
|
pub fn record(&self, xref: &str) -> Option<&Node> {
|
|
850
|
-
|
|
850
|
+
// `eq_ignore_ascii_case` is exactly "equal after `to_ascii_uppercase`"
|
|
851
|
+
// (non-ASCII bytes compare as themselves), without allocating an
|
|
852
|
+
// upper-cased copy of every identifier scanned.
|
|
851
853
|
self.records.iter().find(|record| {
|
|
852
854
|
record
|
|
853
855
|
.xref
|
|
854
856
|
.as_ref()
|
|
855
|
-
.is_some_and(|own| own.
|
|
857
|
+
.is_some_and(|own| own.eq_ignore_ascii_case(xref))
|
|
856
858
|
})
|
|
857
859
|
}
|
|
858
860
|
|
|
859
861
|
/// The record with this cross-reference identifier, mutably.
|
|
860
862
|
pub fn record_mut(&mut self, xref: &str) -> Option<&mut Node> {
|
|
861
|
-
let wanted = xref.to_ascii_uppercase();
|
|
862
863
|
self.records.iter_mut().find(|record| {
|
|
863
864
|
record
|
|
864
865
|
.xref
|
|
865
866
|
.as_ref()
|
|
866
|
-
.is_some_and(|own| own.
|
|
867
|
+
.is_some_and(|own| own.eq_ignore_ascii_case(xref))
|
|
867
868
|
})
|
|
868
869
|
}
|
|
869
870
|
|
|
@@ -1297,6 +1298,44 @@ mod tests {
|
|
|
1297
1298
|
}
|
|
1298
1299
|
}
|
|
1299
1300
|
|
|
1301
|
+
#[test]
|
|
1302
|
+
fn lookup_folds_ascii_case_only_and_the_first_duplicate_wins() {
|
|
1303
|
+
let document = Document::parse(
|
|
1304
|
+
"0 HEAD\n0 @a1@ INDI\n1 NAME First\n0 @A1@ INDI\n1 NAME Second\n\
|
|
1305
|
+
0 @\u{e9}1@ INDI\n0 @\u{c9}1@ INDI\n1 NAME Upper\n0 TRLR\n",
|
|
1306
|
+
)
|
|
1307
|
+
.expect("parse");
|
|
1308
|
+
|
|
1309
|
+
// Case-insensitive for ASCII, and the earlier duplicate is the one found.
|
|
1310
|
+
for wanted in ["@a1@", "@A1@"] {
|
|
1311
|
+
let found = document.record(wanted).expect("found");
|
|
1312
|
+
assert_eq!(found.xref.as_deref(), Some("@a1@"));
|
|
1313
|
+
}
|
|
1314
|
+
// Non-ASCII letters are not case-folded, matching `xref_index`.
|
|
1315
|
+
assert_eq!(
|
|
1316
|
+
document
|
|
1317
|
+
.record("@\u{e9}1@")
|
|
1318
|
+
.and_then(|node| node.xref.as_deref()),
|
|
1319
|
+
Some("@\u{e9}1@")
|
|
1320
|
+
);
|
|
1321
|
+
assert_eq!(
|
|
1322
|
+
document
|
|
1323
|
+
.record("@\u{c9}1@")
|
|
1324
|
+
.and_then(|node| node.xref.as_deref()),
|
|
1325
|
+
Some("@\u{c9}1@")
|
|
1326
|
+
);
|
|
1327
|
+
assert!(document.record("@z9@").is_none());
|
|
1328
|
+
|
|
1329
|
+
let index = document.xref_index();
|
|
1330
|
+
assert_eq!(index["@A1@"], 1);
|
|
1331
|
+
assert_ne!(index["@\u{c9}1@"], index["@\u{e9}1@"]);
|
|
1332
|
+
|
|
1333
|
+
let mut document = document;
|
|
1334
|
+
let found = document.record_mut("@A1@").expect("found mutably");
|
|
1335
|
+
assert_eq!(found.xref.as_deref(), Some("@a1@"));
|
|
1336
|
+
assert!(document.record_mut("@\u{c9}1@").is_some());
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1300
1339
|
#[test]
|
|
1301
1340
|
fn next_xref_continues_the_files_own_numbering() {
|
|
1302
1341
|
let document = Document::parse("0 HEAD\n0 @I1@ INDI\n0 @I7@ INDI\n0 @F1@ FAM\n0 TRLR\n")
|
|
@@ -452,11 +452,30 @@ impl IndividualView {
|
|
|
452
452
|
.and_then(|event| event.year)
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
+
/// The year of the first event with this tag whose date parsed.
|
|
456
|
+
///
|
|
457
|
+
/// [`IndividualView::event_year`] stops at the first event with the tag,
|
|
458
|
+
/// so a dateless `BIRT` hides a dated one after it; a displayed birth year
|
|
459
|
+
/// should not be lost that way. That method keeps its documented meaning
|
|
460
|
+
/// and this private helper serves [`IndividualView::life_years`].
|
|
461
|
+
fn first_year(&self, tag: &str) -> Option<i32> {
|
|
462
|
+
self.events
|
|
463
|
+
.iter()
|
|
464
|
+
.filter(|event| event.tag == tag)
|
|
465
|
+
.find_map(|event| event.year)
|
|
466
|
+
}
|
|
467
|
+
|
|
455
468
|
/// The years to show beside a name: birth and death, when they parsed.
|
|
469
|
+
///
|
|
470
|
+
/// The birth year is the first `BIRT` with a year, else the first `CHR`
|
|
471
|
+
/// with one, else the first `BAPM`; the death year is `DEAT` only.
|
|
456
472
|
#[must_use]
|
|
457
473
|
pub fn life_years(&self) -> Option<String> {
|
|
458
|
-
let
|
|
459
|
-
|
|
474
|
+
let birth = self
|
|
475
|
+
.first_year("BIRT")
|
|
476
|
+
.or_else(|| self.first_year("CHR"))
|
|
477
|
+
.or_else(|| self.first_year("BAPM"));
|
|
478
|
+
match (birth, self.first_year("DEAT")) {
|
|
460
479
|
(Some(birth), Some(death)) => Some(format!("{birth}–{death}")),
|
|
461
480
|
(Some(birth), None) => Some(format!("b. {birth}")),
|
|
462
481
|
(None, Some(death)) => Some(format!("d. {death}")),
|
|
@@ -634,6 +653,46 @@ mod tests {
|
|
|
634
653
|
assert_eq!(view.identifiers[0].kind.as_deref(), Some("FamilySearch"));
|
|
635
654
|
}
|
|
636
655
|
|
|
656
|
+
fn life_years_of(body: &str) -> Option<String> {
|
|
657
|
+
let text = format!("0 HEAD\n0 @I1@ INDI\n{body}0 TRLR\n");
|
|
658
|
+
let document = Document::parse(&text).expect("parse");
|
|
659
|
+
IndividualView::from_node(document.record("@I1@").expect("individual")).life_years()
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
#[test]
|
|
663
|
+
fn a_birth_year_skips_events_whose_date_gave_none() {
|
|
664
|
+
// A dateless BIRT before a dated one still yields the year.
|
|
665
|
+
let body = "1 BIRT\n1 BIRT\n2 DATE 1850\n";
|
|
666
|
+
assert_eq!(life_years_of(body).as_deref(), Some("b. 1850"));
|
|
667
|
+
// An unparsed date is skipped the same way.
|
|
668
|
+
let body = "1 BIRT\n2 DATE nonsense\n1 BIRT\n2 DATE 1851\n";
|
|
669
|
+
assert_eq!(life_years_of(body).as_deref(), Some("b. 1851"));
|
|
670
|
+
// event_year keeps its documented first-event meaning.
|
|
671
|
+
let document =
|
|
672
|
+
Document::parse("0 HEAD\n0 @I1@ INDI\n1 BIRT\n1 BIRT\n2 DATE 1850\n0 TRLR\n")
|
|
673
|
+
.expect("parse");
|
|
674
|
+
let view = IndividualView::from_node(document.record("@I1@").expect("individual"));
|
|
675
|
+
assert_eq!(view.event_year("BIRT"), None);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
#[test]
|
|
679
|
+
fn a_birth_year_falls_back_from_birth_to_christening_to_baptism() {
|
|
680
|
+
let bapm = "1 BAPM\n2 DATE 1802\n";
|
|
681
|
+
assert_eq!(life_years_of(bapm).as_deref(), Some("b. 1802"));
|
|
682
|
+
let both = "1 BAPM\n2 DATE 1802\n1 CHR\n2 DATE 1801\n";
|
|
683
|
+
assert_eq!(life_years_of(both).as_deref(), Some("b. 1801"));
|
|
684
|
+
let all = "1 BAPM\n2 DATE 1802\n1 CHR\n2 DATE 1801\n1 BIRT\n2 DATE 1800\n";
|
|
685
|
+
assert_eq!(life_years_of(all).as_deref(), Some("b. 1800"));
|
|
686
|
+
// A dateless BIRT does not block the fallback.
|
|
687
|
+
let dateless = "1 BIRT\n1 CHR\n2 DATE 1801\n";
|
|
688
|
+
assert_eq!(life_years_of(dateless).as_deref(), Some("b. 1801"));
|
|
689
|
+
// Death stays DEAT only: a burial year is not a death year.
|
|
690
|
+
let burial = "1 BAPM\n2 DATE 1802\n1 BURI\n2 DATE 1870\n";
|
|
691
|
+
assert_eq!(life_years_of(burial).as_deref(), Some("b. 1802"));
|
|
692
|
+
let died = "1 BAPM\n2 DATE 1802\n1 DEAT\n2 DATE 1870\n";
|
|
693
|
+
assert_eq!(life_years_of(died).as_deref(), Some("1802–1870"));
|
|
694
|
+
}
|
|
695
|
+
|
|
637
696
|
#[test]
|
|
638
697
|
fn a_record_type_we_do_not_know_still_has_a_title_and_a_view() {
|
|
639
698
|
let record = Document::parse("0 HEAD\n0 @X1@ _OPAQUE\n1 QUERY Tova Testwright\n0 TRLR\n")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|