gedcomkit 0.1.12__tar.gz → 0.1.13__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.12 → gedcomkit-0.1.13}/CHANGELOG.md +20 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/Cargo.lock +1 -1
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/Cargo.toml +1 -1
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/PKG-INFO +1 -1
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/bindings/gedcomkit-py/Cargo.lock +2 -2
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/bindings/gedcomkit-py/Cargo.toml +1 -1
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/src/convert.rs +278 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/LICENSE +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/README.md +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/bindings/gedcomkit-py/LICENSE +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/bindings/gedcomkit-py/README.md +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/bindings/gedcomkit-py/gedcomkit.pyi +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/bindings/gedcomkit-py/src/lib.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom-spec/LICENSE +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom-spec/README.md +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom-spec/cardinalities.tsv +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom-spec/enumerations.tsv +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom-spec/enumerationsets.tsv +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom-spec/grammar.abnf +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom-spec/grammar.gedstruct +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom-spec/payloads.tsv +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom-spec/substructures.tsv +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/age-invalid.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/age-valid.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/atsign.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/char_ascii_1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/char_ascii_2.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/char_utf16be-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/char_utf16be-2.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/char_utf16le-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/char_utf16le-2.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/char_utf8-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/char_utf8-2.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/char_utf8-3.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/date-all.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/date-dual-invalid.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/date-dual-valid.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/enum-ext.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/filename-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/lang-all.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/notes-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/obje-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/obsolete-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/pedi-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/rela_1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/sour-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/tiny-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/5/xref-case.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/age-invalid.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/age-valid.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/atsign.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/char_ascii_1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/char_ascii_2.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/char_utf16be-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/char_utf16be-2.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/char_utf16le-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/char_utf16le-2.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/char_utf8-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/char_utf8-2.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/char_utf8-3.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/date-all.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/date-dual-invalid.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/enum-ext.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/filename-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/lang-all.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/notes-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/obje-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/obsolete-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/pedi-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/rela_1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/sour-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/tiny-1.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/7/xref-case.ged +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/LICENSE +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/gedcom7code/UPSTREAM-README.md +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/zip-interop/README.md +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/zip-interop/python-zip64.gdz +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/fixtures/vendored/zip-interop/python-zipfile.gdz +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/pyproject.toml +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/src/build.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/src/dates.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/src/decode.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/src/family.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/src/gedzip.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/src/lib.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/src/names.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/src/schema.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/src/synthetic.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/src/tags.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/src/validate.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/src/view.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/tests/endurance.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/tests/external_charset.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/tests/properties.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/tests/requirement_corpus.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/tests/spec_conformance.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/tests/typescript.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/tests/vendored_corpus.rs +0 -0
- {gedcomkit-0.1.12 → gedcomkit-0.1.13}/tests/version_conversion.rs +0 -0
|
@@ -5,6 +5,26 @@ 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.13] - 2026-10-02
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- `convert::to_version_7` / `to_version_7_with` now convert roles. A role in
|
|
13
|
+
a cited event (`SOUR`.`EVEN`.`ROLE`) that 5.5.1 writes as free text —
|
|
14
|
+
its `(descriptor)` form, or a bare word — becomes version 7's enumerated
|
|
15
|
+
role it names (`Son` → `CHIL`, `Godmother` → `GODP`, `Witness` → `WITN`;
|
|
16
|
+
`OTHER` when it names none), with the words kept verbatim as a `PHRASE`;
|
|
17
|
+
an enumerated value in another case is upper-cased. An association's
|
|
18
|
+
`RELA`, which version 7 spells `ROLE`, gets the same treatment. Before, both
|
|
19
|
+
were carried into the 7.0 document unchanged, where neither is valid. A
|
|
20
|
+
role that already has a `PHRASE`, is empty, or has a continuation line with
|
|
21
|
+
substructures is kept and reported.
|
|
22
|
+
- `convert::to_version_5_5_1` turns them back: under a cited event a role
|
|
23
|
+
5.5.1 enumerates (`CHIL`, `HUSB`, `WIFE`, `MOTH`, `FATH`, `SPOU`) without a
|
|
24
|
+
phrase stays, and any other becomes `(words)` — the `PHRASE` when there is
|
|
25
|
+
one, the role in words otherwise; under an association the role becomes
|
|
26
|
+
`RELA` with the same words.
|
|
27
|
+
|
|
8
28
|
## [0.1.12] - 2026-09-30
|
|
9
29
|
|
|
10
30
|
### 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.13"
|
|
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.13"
|
|
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.13"
|
|
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.13"
|
|
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"
|
|
@@ -324,6 +324,18 @@ fn structures_to_7_node(node: &mut Node, shared: &[String], pass: &mut Pass) ->
|
|
|
324
324
|
changed |= date_to_7(node, pass);
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
// A role in a cited event (`SOUR`.`EVEN`.`ROLE`), and an association's
|
|
328
|
+
// relation, which 5.5.1 spells `RELA` and version 7 `ROLE`: both become
|
|
329
|
+
// version 7's enumerated role, the words as written kept in a `PHRASE`.
|
|
330
|
+
if node.tag == "EVEN" || node.tag == "ASSO" {
|
|
331
|
+
let association = node.tag == "ASSO";
|
|
332
|
+
for child in &mut node.children {
|
|
333
|
+
if child.tag == "ROLE" || (association && child.tag == "RELA") {
|
|
334
|
+
changed |= role_to_7(child, pass);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
327
339
|
// A multimedia record's files: `FORM` moves from the object to each
|
|
328
340
|
// `FILE` (5.5), and each file's path, format and medium take version 7's
|
|
329
341
|
// shape (5.5.1 already nests `FORM` under `FILE`, with `TYPE` beneath).
|
|
@@ -845,6 +857,201 @@ fn medium_to_7(medium: &str) -> (String, bool) {
|
|
|
845
857
|
}
|
|
846
858
|
}
|
|
847
859
|
|
|
860
|
+
/// Version 7's enumerated roles (`enumset-ROLE`).
|
|
861
|
+
const ROLES_7: &[&str] = &[
|
|
862
|
+
"CHIL",
|
|
863
|
+
"CLERGY",
|
|
864
|
+
"FATH",
|
|
865
|
+
"FRIEND",
|
|
866
|
+
"GODP",
|
|
867
|
+
"HUSB",
|
|
868
|
+
"MOTH",
|
|
869
|
+
"MULTIPLE",
|
|
870
|
+
"NGHBR",
|
|
871
|
+
"OFFICIATOR",
|
|
872
|
+
"PARENT",
|
|
873
|
+
"SPOU",
|
|
874
|
+
"WIFE",
|
|
875
|
+
"WITN",
|
|
876
|
+
"OTHER",
|
|
877
|
+
];
|
|
878
|
+
|
|
879
|
+
/// The enumerated role a word for one reads as, and `OTHER` when none does.
|
|
880
|
+
/// Only a word that names the role outright counts: a son is a child, a
|
|
881
|
+
/// godmother a godparent; anything else is `OTHER`, and the words survive in
|
|
882
|
+
/// the `PHRASE` either way.
|
|
883
|
+
fn role_word_to_7(words: &str) -> &'static str {
|
|
884
|
+
match words.trim().to_lowercase().as_str() {
|
|
885
|
+
"child" | "son" | "daughter" => "CHIL",
|
|
886
|
+
"husband" => "HUSB",
|
|
887
|
+
"wife" => "WIFE",
|
|
888
|
+
"mother" => "MOTH",
|
|
889
|
+
"father" => "FATH",
|
|
890
|
+
"spouse" => "SPOU",
|
|
891
|
+
"parent" => "PARENT",
|
|
892
|
+
"friend" => "FRIEND",
|
|
893
|
+
"godparent" | "godfather" | "godmother" => "GODP",
|
|
894
|
+
"witness" => "WITN",
|
|
895
|
+
"neighbor" | "neighbour" => "NGHBR",
|
|
896
|
+
"clergy" | "priest" | "minister" | "pastor" => "CLERGY",
|
|
897
|
+
"officiator" | "officiant" => "OFFICIATOR",
|
|
898
|
+
"multiple" => "MULTIPLE",
|
|
899
|
+
_ => "OTHER",
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/// A 5.5.1 role (`ROLE` under a cited event, or an association's `RELA`) in
|
|
904
|
+
/// version 7's shape. A value already in the enumeration stays; free text —
|
|
905
|
+
/// 5.5.1's `(descriptor)` form, or any other word — becomes the enumerated
|
|
906
|
+
/// role it names (`OTHER` when it names none) with the text kept verbatim as
|
|
907
|
+
/// a `PHRASE`.
|
|
908
|
+
fn role_to_7(role: &mut Node, pass: &mut Pass) -> bool {
|
|
909
|
+
let from_rela = role.tag == "RELA";
|
|
910
|
+
let original = role.logical_value();
|
|
911
|
+
let trimmed = original.trim();
|
|
912
|
+
let upper = trimmed.to_ascii_uppercase();
|
|
913
|
+
let label = if from_rela { "ASSO.RELA" } else { "ROLE" };
|
|
914
|
+
|
|
915
|
+
if !from_rela && ROLES_7.contains(&upper.as_str()) {
|
|
916
|
+
if upper == original {
|
|
917
|
+
return false;
|
|
918
|
+
}
|
|
919
|
+
if continuation_has_children(role) {
|
|
920
|
+
refuse_for_continuation_children(label, "role", pass);
|
|
921
|
+
return false;
|
|
922
|
+
}
|
|
923
|
+
role.set_logical_value(&upper);
|
|
924
|
+
pass.note(
|
|
925
|
+
label,
|
|
926
|
+
ConversionKind::Converted,
|
|
927
|
+
format!("the role {original:?} is version 7's enumerated value {upper}"),
|
|
928
|
+
);
|
|
929
|
+
return true;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
let words = trimmed
|
|
933
|
+
.strip_prefix('(')
|
|
934
|
+
.and_then(|inner| inner.strip_suffix(')'))
|
|
935
|
+
.unwrap_or(trimmed)
|
|
936
|
+
.trim()
|
|
937
|
+
.to_owned();
|
|
938
|
+
if continuation_has_children(role) {
|
|
939
|
+
refuse_for_continuation_children(label, "role", pass);
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
if words.is_empty() {
|
|
943
|
+
pass.note(
|
|
944
|
+
label,
|
|
945
|
+
ConversionKind::Kept,
|
|
946
|
+
"an empty role has no version 7 reading; it is left as it is",
|
|
947
|
+
);
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
if role.first("PHRASE").is_some() {
|
|
951
|
+
pass.note(
|
|
952
|
+
label,
|
|
953
|
+
ConversionKind::Kept,
|
|
954
|
+
format!(
|
|
955
|
+
"the role {original:?} is not version 7's enumeration, but it already has a PHRASE to keep the words in; it is left as it is"
|
|
956
|
+
),
|
|
957
|
+
);
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
960
|
+
let value = role_word_to_7(&words);
|
|
961
|
+
if from_rela {
|
|
962
|
+
role.set_tag("ROLE");
|
|
963
|
+
}
|
|
964
|
+
role.set_logical_value(value);
|
|
965
|
+
role.push(Node::with_value("PHRASE", &words));
|
|
966
|
+
pass.note(
|
|
967
|
+
label,
|
|
968
|
+
ConversionKind::Converted,
|
|
969
|
+
if from_rela {
|
|
970
|
+
format!(
|
|
971
|
+
"an association's relation {words:?} is version 7's ROLE {value}, with the words kept as a PHRASE"
|
|
972
|
+
)
|
|
973
|
+
} else {
|
|
974
|
+
format!("the role {original:?} became {value}, with the words kept as a PHRASE")
|
|
975
|
+
},
|
|
976
|
+
);
|
|
977
|
+
true
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/// The roles 5.5.1 enumerates under a cited event.
|
|
981
|
+
const ROLES_5: &[&str] = &["CHIL", "HUSB", "WIFE", "MOTH", "FATH", "SPOU"];
|
|
982
|
+
|
|
983
|
+
/// A version 7 role in 5.5.1's spelling. Under an association it becomes
|
|
984
|
+
/// `RELA`, whose payload is free text; under a cited event, a role 5.5.1
|
|
985
|
+
/// enumerates stays and any other becomes `(descriptor)`. A `PHRASE` is the
|
|
986
|
+
/// words to keep, and leaves with them.
|
|
987
|
+
fn role_to_5(role: &mut Node, association: bool, pass: &mut Pass) -> bool {
|
|
988
|
+
let enumerated = role.logical_value().trim().to_ascii_uppercase();
|
|
989
|
+
let phrase = role
|
|
990
|
+
.first("PHRASE")
|
|
991
|
+
.filter(|phrase| phrase.substructures().next().is_none())
|
|
992
|
+
.map(Node::logical_value)
|
|
993
|
+
.filter(|phrase| !phrase.contains('\n') && !phrase.trim().is_empty());
|
|
994
|
+
if role.first("PHRASE").is_some() && phrase.is_none() {
|
|
995
|
+
pass.note(
|
|
996
|
+
"ROLE",
|
|
997
|
+
ConversionKind::Kept,
|
|
998
|
+
"5.5.1 holds a role on one line with nothing beneath it, and this role's PHRASE spans lines or has substructures; it is kept as written, which 5.5.1 does not define",
|
|
999
|
+
);
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
if continuation_has_children(role) {
|
|
1003
|
+
refuse_for_continuation_children("ROLE", "role", pass);
|
|
1004
|
+
return false;
|
|
1005
|
+
}
|
|
1006
|
+
let words = phrase.unwrap_or_else(|| role_label(&enumerated));
|
|
1007
|
+
if association {
|
|
1008
|
+
remove_child(role, "PHRASE");
|
|
1009
|
+
role.set_tag("RELA");
|
|
1010
|
+
role.set_logical_value(words.trim());
|
|
1011
|
+
pass.note(
|
|
1012
|
+
"ROLE",
|
|
1013
|
+
ConversionKind::Converted,
|
|
1014
|
+
"an association's role is RELA in 5.5.1, a relation in words",
|
|
1015
|
+
);
|
|
1016
|
+
return true;
|
|
1017
|
+
}
|
|
1018
|
+
if role.first("PHRASE").is_none() && ROLES_5.contains(&enumerated.as_str()) {
|
|
1019
|
+
return false;
|
|
1020
|
+
}
|
|
1021
|
+
remove_child(role, "PHRASE");
|
|
1022
|
+
role.set_logical_value(&format!("({})", words.trim()));
|
|
1023
|
+
pass.note(
|
|
1024
|
+
"ROLE",
|
|
1025
|
+
ConversionKind::Converted,
|
|
1026
|
+
"a role 5.5.1 does not enumerate, or one with a phrase, is 5.5.1's parenthesized role descriptor",
|
|
1027
|
+
);
|
|
1028
|
+
true
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/// An enumerated role in words, for a 5.5.1 descriptor that has no phrase to
|
|
1032
|
+
/// take them from.
|
|
1033
|
+
fn role_label(enumerated: &str) -> String {
|
|
1034
|
+
match enumerated {
|
|
1035
|
+
"CHIL" => "Child",
|
|
1036
|
+
"CLERGY" => "Clergy",
|
|
1037
|
+
"FATH" => "Father",
|
|
1038
|
+
"FRIEND" => "Friend",
|
|
1039
|
+
"GODP" => "Godparent",
|
|
1040
|
+
"HUSB" => "Husband",
|
|
1041
|
+
"MOTH" => "Mother",
|
|
1042
|
+
"MULTIPLE" => "Multiple",
|
|
1043
|
+
"NGHBR" => "Neighbor",
|
|
1044
|
+
"OFFICIATOR" => "Officiator",
|
|
1045
|
+
"PARENT" => "Parent",
|
|
1046
|
+
"SPOU" => "Spouse",
|
|
1047
|
+
"WIFE" => "Wife",
|
|
1048
|
+
"WITN" => "Witness",
|
|
1049
|
+
"OTHER" => "Other",
|
|
1050
|
+
other => return other.to_owned(),
|
|
1051
|
+
}
|
|
1052
|
+
.to_owned()
|
|
1053
|
+
}
|
|
1054
|
+
|
|
848
1055
|
/// Converts a document to GEDCOM 5.5.1.
|
|
849
1056
|
#[must_use]
|
|
850
1057
|
pub fn to_version_5_5_1(document: &Document) -> Conversion {
|
|
@@ -1031,6 +1238,18 @@ fn structures_to_5_node(node: &mut Node, level: usize, pass: &mut Pass) -> bool
|
|
|
1031
1238
|
}
|
|
1032
1239
|
}
|
|
1033
1240
|
|
|
1241
|
+
// Version 7's enumerated role goes back to 5.5.1's spelling: free text
|
|
1242
|
+
// in parentheses under a cited event, and `RELA` under an association.
|
|
1243
|
+
if node.tag == "EVEN" || node.tag == "ASSO" {
|
|
1244
|
+
let association = node.tag == "ASSO";
|
|
1245
|
+
for child in &mut node.children {
|
|
1246
|
+
if child.tag == "ROLE" && role_to_5(child, association, pass) {
|
|
1247
|
+
changed = true;
|
|
1248
|
+
crate::build::fit_own_lines(child, level + 1, crate::build::MAX_LINE_LENGTH);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1034
1253
|
// Everything else version 7 added and 5.5.1 does not define is kept and
|
|
1035
1254
|
// named. Deleting a translation or a creation date to satisfy a version
|
|
1036
1255
|
// number would be the worst trade this application could make.
|
|
@@ -1312,6 +1531,65 @@ mod tests {
|
|
|
1312
1531
|
assert!(note.detail.contains("ole"), "{}", note.detail);
|
|
1313
1532
|
}
|
|
1314
1533
|
|
|
1534
|
+
#[test]
|
|
1535
|
+
fn a_cited_role_in_words_becomes_an_enumerated_role_with_its_phrase() {
|
|
1536
|
+
let document = parse(
|
|
1537
|
+
"0 HEAD\n1 GEDC\n2 VERS 5.5.1\n0 @I1@ INDI\n1 CENS\n2 SOUR @S1@\n3 EVEN CENS\n4 ROLE (Son)\n0 @I2@ INDI\n1 CENS\n2 SOUR @S1@\n3 EVEN CENS\n4 ROLE (Lodger)\n0 @I3@ INDI\n1 BIRT\n2 SOUR @S1@\n3 EVEN BIRT\n4 ROLE MOTH\n0 @S1@ SOUR\n0 TRLR\n",
|
|
1538
|
+
);
|
|
1539
|
+
let outcome = to_version_7(&document);
|
|
1540
|
+
let text = outcome.document.to_text();
|
|
1541
|
+
assert!(text.contains("4 ROLE CHIL\n5 PHRASE Son\n"), "{text}");
|
|
1542
|
+
assert!(text.contains("4 ROLE OTHER\n5 PHRASE Lodger\n"), "{text}");
|
|
1543
|
+
assert!(
|
|
1544
|
+
text.contains("4 ROLE MOTH\n0 @S1@"),
|
|
1545
|
+
"an enumerated role is untouched: {text}"
|
|
1546
|
+
);
|
|
1547
|
+
assert_eq!(
|
|
1548
|
+
outcome
|
|
1549
|
+
.notes
|
|
1550
|
+
.iter()
|
|
1551
|
+
.filter(|note| note.tag == "ROLE")
|
|
1552
|
+
.count(),
|
|
1553
|
+
2,
|
|
1554
|
+
"{:?}",
|
|
1555
|
+
outcome.notes
|
|
1556
|
+
);
|
|
1557
|
+
|
|
1558
|
+
// And back: the words return as 5.5.1's descriptor, the enumerated
|
|
1559
|
+
// role 5.5.1 also knows stays as it is.
|
|
1560
|
+
let back = to_version_5_5_1(&outcome.document).document.to_text();
|
|
1561
|
+
assert!(back.contains("4 ROLE (Son)\n"), "{back}");
|
|
1562
|
+
assert!(back.contains("4 ROLE (Lodger)\n"), "{back}");
|
|
1563
|
+
assert!(back.contains("4 ROLE MOTH\n"), "{back}");
|
|
1564
|
+
assert!(!back.contains("PHRASE"), "{back}");
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
#[test]
|
|
1568
|
+
fn an_association_relation_is_a_role_in_version_seven_and_back() {
|
|
1569
|
+
let document = parse(
|
|
1570
|
+
"0 HEAD\n1 GEDC\n2 VERS 5.5.1\n0 @I1@ INDI\n1 ASSO @I2@\n2 RELA Godfather\n0 @I2@ INDI\n0 TRLR\n",
|
|
1571
|
+
);
|
|
1572
|
+
let seven = to_version_7(&document).document;
|
|
1573
|
+
let text = seven.to_text();
|
|
1574
|
+
assert!(
|
|
1575
|
+
text.contains("1 ASSO @I2@\n2 ROLE GODP\n3 PHRASE Godfather\n"),
|
|
1576
|
+
"{text}"
|
|
1577
|
+
);
|
|
1578
|
+
|
|
1579
|
+
let back = to_version_5_5_1(&seven).document.to_text();
|
|
1580
|
+
assert!(back.contains("1 ASSO @I2@\n2 RELA Godfather\n"), "{back}");
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
#[test]
|
|
1584
|
+
fn a_version_seven_role_without_a_phrase_reads_in_words_in_5_5_1() {
|
|
1585
|
+
let document = parse(
|
|
1586
|
+
"0 HEAD\n1 GEDC\n2 VERS 7.0\n0 @I1@ INDI\n1 BAPM\n2 SOUR @S1@\n3 EVEN BAPM\n4 ROLE GODP\n1 ASSO @I2@\n2 ROLE FRIEND\n0 @I2@ INDI\n0 @S1@ SOUR\n0 TRLR\n",
|
|
1587
|
+
);
|
|
1588
|
+
let back = to_version_5_5_1(&document).document.to_text();
|
|
1589
|
+
assert!(back.contains("4 ROLE (Godparent)\n"), "{back}");
|
|
1590
|
+
assert!(back.contains("2 RELA Friend\n"), "{back}");
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1315
1593
|
#[test]
|
|
1316
1594
|
fn an_unrecognized_source_medium_becomes_other_with_a_phrase() {
|
|
1317
1595
|
let document = parse(
|
|
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
|
|
File without changes
|
|
File without changes
|