1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
use crate::{
parser::{parse_subset, Parser},
tokenizer::Tokenizer,
types::{custom::UserDefinedTag, date::change_date::ChangeDate, note::Note, Xref},
};
#[cfg(feature = "json")]
use serde::{Deserialize, Serialize};
/// GEDCOM Submission Record Structure
///
/// In non-LDS terms, this acts like a cover sheet or instruction set for the GEDCOM file. It
/// points to the submitter, provides creation/update dates, and can indicate the generating
/// software.
///
/// While the GEDCOM 5.5.1 specification highlights its original use for LDS internal processing
/// (e.g., `TempleReady`, "Temple Code", "Ordinance Process Flag"), for general genealogical use,
/// many fields (like `TEMP`, `ORDI`) are often ignored or left blank by non-LDS software.
///
/// Its primary value for non-LDS users is identifying the data's origin (via the `SUBMITTER`) and
/// providing basic file metadata.
///
/// References:
/// [GEDCOM 5.5.1 specification, page 28](https://gedcom.io/specifications/ged551.pdf)
/// [GEDCOM 7.0 Specification](gedcom.io/specifications/FamilySearchGEDCOMv7.html)
#[derive(Debug, Default)]
#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
pub struct Submission {
/// Cross-reference identifier for this submission record
/// Format: `@XREF:SUBN@`
pub xref: Option<Xref>,
/// Name of the family file being submitted
/// Used to identify the source family file
/// Tag: `FAMF`
pub family_file_name: Option<String>,
/// Temple code indicating which temple should receive the records
/// Used by `TempleReady` to route cleared records appropriately
/// Tag: `TEMP`
pub temple_code: Option<String>,
/// Reference to who is submitting this data (optional)
/// Points to a submitter record that contains contact information
/// Tag: `SUBM`
pub submitter_ref: Option<String>,
/// Number of generations of ancestors to include
/// Controls the scope of ancestral data in the submission
/// Tag: `ANCE`
pub ancestor_generations: Option<String>,
/// Number of generations of descendants to include
/// Controls the scope of descendant data in the submission
/// Tag: `DESC`
pub descendant_generations: Option<String>,
/// Ordinance process flag
/// Indicates how ordinance information should be processed
/// Tag: `ORDI`
pub ordinance_process_flag: Option<String>,
/// Automated Record Identification number
/// System-generated unique identifier for automated processing
/// Tag: `RIN`
pub automated_record_id: Option<String>,
/// Collection of note structures providing additional information
/// Can contain multiple notes with various details about the submission
/// Tag: `NOTE`
pub note: Option<Note>,
/// When this submission record was last changed (optional) Helps track the history of
/// modifications to your submission
/// Tag: `CHAN`
pub change_date: Option<ChangeDate>,
/// Custom user-defined tags not part of the standard GEDCOM specification.
/// These tags allow for extensions to the GEDCOM format, storing
/// non-standard or proprietary data associated with the submission.
/// Tag: `_XXXX` (where XXXX is a user-defined tag)
pub custom: Vec<Box<UserDefinedTag>>,
}
impl Submission {
#[must_use]
fn with_xref(xref: Option<Xref>) -> Self {
Self {
xref,
..Default::default()
}
}
#[must_use]
pub fn new(tokenizer: &mut Tokenizer, level: u8, xref: Option<Xref>) -> Submission {
let mut subn = Submission::with_xref(xref);
subn.parse(tokenizer, level);
subn
}
}
impl Parser for Submission {
fn parse(&mut self, tokenizer: &mut Tokenizer, level: u8) {
tokenizer.next_token();
let handle_subset = |tag: &str, tokenizer: &mut Tokenizer| match tag {
"ANCE" => self.ancestor_generations = Some(tokenizer.take_line_value()),
"CHAN" => self.change_date = Some(ChangeDate::new(tokenizer, level + 1)),
"DESC" => self.descendant_generations = Some(tokenizer.take_line_value()),
"FAMF" => self.family_file_name = Some(tokenizer.take_line_value()),
"NOTE" => self.note = Some(Note::new(tokenizer, level + 1)),
"ORDI" => self.ordinance_process_flag = Some(tokenizer.take_line_value()),
"RIN" => self.automated_record_id = Some(tokenizer.take_line_value()),
"SUBM" => self.submitter_ref = Some(tokenizer.take_line_value()),
"TEMP" => self.temple_code = Some(tokenizer.take_line_value()),
_ => panic!(
"{}, Unhandled SubmissionRecord tag: {}",
tokenizer.debug(),
tag
),
};
self.custom = parse_subset(tokenizer, level, handle_subset);
}
}
#[cfg(test)]
mod tests {
use crate::{types::Submission, Gedcom};
#[test]
fn test_parse_submission_record() {
let sample = "\
0 HEAD\n\
1 GEDC\n\
2 VERS 5.5\n\
0 @SUBMISSION@ SUBN\n\
1 SUBM @SUBMITTER@\n\
1 FAMF NameOfFamilyFile\n\
1 TEMP LDS\n\
1 ANCE 1\n\
1 DESC 1\n\
1 ORDI LDS\n\
1 RIN 12345\n\
1 CHAN\n\
2 DATE 1 APR 1998\n\
3 TIME 12:34:56.789\n\
1 _MYCUSTOMTAG Some custom data here\n\
1 _ANOTHER_TAG Another piece of custom data\n\
0 TRLR";
let mut doc = Gedcom::new(sample.chars());
let gedcom_data = doc.parse();
let mut submissions = gedcom_data.submissions;
assert!(!submissions.is_empty());
let first_submission = submissions.remove(0);
let Submission {
submitter_ref,
family_file_name,
temple_code,
custom,
ancestor_generations,
descendant_generations,
ordinance_process_flag,
automated_record_id,
change_date,
..
} = first_submission;
assert_eq!(submitter_ref.unwrap(), "@SUBMITTER@");
assert_eq!(family_file_name.unwrap(), "NameOfFamilyFile");
assert_eq!(temple_code.unwrap(), "LDS");
assert_eq!(ancestor_generations.unwrap(), "1");
assert_eq!(descendant_generations.unwrap(), "1");
assert_eq!(ordinance_process_flag.unwrap(), "LDS");
assert_eq!(automated_record_id.unwrap(), "12345");
let date = change_date.unwrap().date.unwrap();
assert_eq!(date.value.unwrap(), "1 APR 1998");
assert_eq!(date.time.unwrap(), "12:34:56.789");
assert_eq!(custom[0].tag, "_MYCUSTOMTAG");
assert_eq!(custom[0].value.as_ref().unwrap(), "Some custom data here");
assert!(custom[0].children.is_empty());
assert_eq!(custom[1].tag, "_ANOTHER_TAG");
assert_eq!(
custom[1].value.as_ref().unwrap(),
"Another piece of custom data"
);
assert!(custom[1].children.is_empty());
}
}