gedcom_rs/types/
header.rs

1use crate::parse;
2// use crate::types::corporation;
3// use crate::types::Copyright;
4// use crate::types::Note;
5use crate::types::{Source, Submitter};
6
7use super::DateTime;
8use super::Gedc;
9use super::Line;
10
11/*
12HEADER:= n HEAD
13+1 SOUR <APPROVED_SYSTEM_ID>
14    +2 VERS <VERSION_NUMBER>
15    +2 NAME <NAME_OF_PRODUCT>
16    +2 CORP <NAME_OF_BUSINESS>
17        +3 <<ADDRESS_STRUCTURE>>
18    +2 DATA <NAME_OF_SOURCE_DATA>
19        +3 DATE <PUBLICATION_DATE>
20        +3 COPR <COPYRIGHT_SOURCE_DATA>
21        +4 [CONT|CONC]<COPYRIGHT_SOURCE_DATA>
22+1 DEST <RECEIVING_SYSTEM_NAME>
23+1 DATE <TRANSMISSION_DATE>
24    +2 TIME <TIME_VALUE>
25+1 SUBM @<XREF:SUBM>@
26+1 SUBN @<XREF:SUBN>@
27+1 FILE <FILE_NAME>
28+1 COPR <COPYRIGHT_GEDCOM_FILE> +1 GEDC
29    +2 VERS <VERSION_NUMBER>
30    +2 FORM <GEDCOM_FORM> +1 CHAR <CHARACTER_SET>
31    +2 VERS <VERSION_NUMBER> +1 LANG <LANGUAGE_OF_TEXT> +1 PLAC
32    +2 FORM <PLACE_HIERARCHY>
33+1 NOTE <GEDCOM_CONTENT_DESCRIPTION>
34    +2 [CONC|CONT] <GEDCOM_CONTENT_DESCRIPTION>
35*/
36
37#[derive(Debug, Default)]
38#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
39pub struct Header {
40    pub encoding: Option<String>,
41    pub copyright: Option<String>,
42    pub date: Option<DateTime>,
43    pub destination: Option<String>,
44    pub gedcom_version: Option<Gedc>,
45    pub language: Option<String>,
46    pub filename: Option<String>,
47    pub note: Option<String>,
48    pub source: Option<Source>,
49    pub submitter: Option<Submitter>,
50    pub submission: Option<String>,
51}
52
53impl Header {
54    pub fn parse(mut record: String) -> Header {
55        let mut header = Header {
56            encoding: None,
57            copyright: None,
58            // corporation: None,
59            date: None,
60            destination: None,
61            gedcom_version: None,
62            language: None,
63            filename: None,
64            note: None,
65            source: None,
66            submitter: None,
67            submission: None,
68        };
69
70        // do parser stuff here
71        while !record.is_empty() {
72            let mut buffer: &str = record.as_str();
73            let line = Line::peek(&mut buffer).unwrap();
74
75            // Inspect the top-level tags only.
76            if line.level == 0 && line.tag == "HEAD" {
77                // Consume the line
78                // println!("Consuming HEAD");
79                // (buffer, _) = Line::parse(&record).unwrap();
80                Line::parse(&mut buffer).unwrap();
81            } else if line.level == 1 {
82                // println!("Found an inner tag: {}", line.tag);
83                match line.tag {
84                    "CHAR" => {
85                        header.encoding = Some(line.value.to_string());
86                        // (buffer, _) = Line::parse(&record).unwrap();
87                        Line::parse(&mut buffer).unwrap();
88                    }
89                    "COPR" => {
90                        // println!("Input before copyright: '{}'", buffer);
91                        header.copyright = parse::get_tag_value(&mut buffer).unwrap();
92                        // println!("Input after copyright: '{}'", buffer);
93                        // (buffer, header.copyright) = parse::get_tag_value(&record).unwrap();
94
95                        // header.copyright = Some(line.value.unwrap_or("").to_string());
96                        // (buffer, _) = Line::parse(&record).unwrap();
97                        // (buffer, header.copyright) = Copyright::parse(&record);
98                    }
99                    // "CORP" => {
100                    //     println!("parsing CORP");
101                    //     (buffer, header.corporation) = corporation::Corporation::parse(&record);
102                    // }
103                    "DATE" => {
104                        // We're doing lazy parsing of the date, because parsing
105                        // date strings is hard. For now.
106                        (buffer, header.date) = DateTime::parse(&record);
107                    }
108                    "DEST" => {
109                        header.destination = Some(line.value.to_string());
110                        // (buffer, _) = Line::parse(&record).unwrap();
111                        Line::parse(&mut buffer).unwrap();
112                    }
113                    "FILE" => {
114                        header.filename = Some(line.value.to_string());
115                        // (buffer, _) = Line::parse(&record).unwrap();
116                        Line::parse(&mut buffer).unwrap();
117                    }
118                    "GEDC" => {
119                        (buffer, header.gedcom_version) = Gedc::parse(&record);
120                    }
121                    "LANG" => {
122                        header.language = Some(line.value.to_string());
123                        // (buffer, _) = Line::parse(&record).unwrap();
124                        Line::parse(&mut buffer).unwrap();
125                    }
126                    "NOTE" => {
127                        // This is just parsing the value of a line, and any
128                        // CONC/CONT that follows. Rewrite
129                        header.note = parse::get_tag_value(&mut buffer).unwrap();
130                        // (buffer, header.note) = parse::get_tag_value(&record).unwrap();
131                        // let note: Option<Note>;
132                        // (buffer, note) = Note::parse(&record);
133                        // header.note = note;
134                    }
135                    "SOUR" => {
136                        (buffer, header.source) = Source::parse(&record);
137                    }
138                    "SUBM" => {
139                        (buffer, header.submitter) = Submitter::parse(&record);
140                    }
141                    _ => {
142                        // println!("Unhandled header tag: {}", line.tag);
143                        // (buffer, _) = Line::parse(&record).unwrap();
144                        Line::parse(&mut buffer).unwrap();
145                    }
146                };
147            } else {
148                // (buffer, _) = Line::parse(&record).unwrap();
149                Line::parse(&mut buffer).unwrap();
150            }
151
152            record = buffer.to_string();
153        }
154        header
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use crate::types::{corporation::Corporation, Address, DateTime, Form};
161
162    use super::Header;
163
164    #[test]
165    fn parse_header() {
166        let data = vec![
167            "0 HEAD",
168            "1 CHAR UTF-8",
169            "1 SOUR Ancestry.com Family Trees",
170            "2 DATA Name of source data",
171            "3 DATE 1 JAN 1998",
172            "3 COPR Copyright of source data",
173            "2 VERS (2010.3)",
174            "2 NAME Ancestry.com Family Trees",
175            "2 CORP Ancestry.com",
176            "3 ADDR",
177            "4 ADR1 Example Software",
178            "4 ADR2 123 Main Street",
179            "4 ADR3 Ste 1",
180            "4 CITY Anytown",
181            "4 STAE IL",
182            "4 POST 55555",
183            "4 CTRY USA",
184            "3 PHON +1-800-555-1111",
185            "3 PHON +1-800-555-1212",
186            "3 PHON +1-800-555-1313",
187            "3 EMAIL a@example.com",
188            "3 EMAIL b@example.com",
189            "3 EMAIL c@example.com",
190            "3 FAX +1-800-555-1414",
191            "3 FAX +1-800-555-1515",
192            "3 FAX +1-800-555-1616",
193            "3 WWW https://www.example.com",
194            "3 WWW https://www.example.org",
195            "3 WWW https://www.example.net",
196            "1 SUBM @U1@",
197            "1 GEDC",
198            "2 VERS 5.5",
199            "2 FORM LINEAGE-LINKED",
200            "3 VERS 5.5",
201            "1 COPR A copyright statement",
202            "1 LANG English",
203            "1 DATE 1 JAN 2023",
204            "2 TIME 12:13:14.15",
205            // The submitter record
206            "0 @U1@ SUBM",
207            "1 NAME Adam Israel",
208            "1 ADDR",
209            "2 ADR1 Example Software",
210            "2 ADR2 123 Main Street",
211            "2 ADR3 Ste 1",
212            "2 CITY Anytown",
213            "2 STAE IL ",
214            "2 POST 55555",
215            "2 CTRY USA",
216            "1 PHON +1-800-555-1111",
217            "1 PHON +1-800-555-1212",
218            "1 PHON +1-800-555-1313",
219            "1 EMAIL a@@example.com",
220            "1 EMAIL b@@example.com",
221            "1 EMAIL c@@example.com",
222            "1 FAX +1-800-555-1414",
223            "1 FAX +1-800-555-1515",
224            "1 FAX +1-800-555-1616",
225            "1 WWW https://www.example.com",
226            "1 WWW https://www.example.org",
227            "1 WWW https://www.example.net",
228            "1 OBJE @M1@",
229            "1 RIN 1",
230            "1 CHAN",
231            "2 DATE 7 SEP 2000",
232            "3 TIME 8:35:36",
233        ];
234
235        let header = Header::parse(data.join("\n"));
236
237        // encoding
238        assert!(header.encoding.is_some());
239        assert!(header.encoding == Some("UTF-8".to_string()));
240
241        // copyright
242        assert!(header.copyright.is_some());
243        assert!(header.copyright == Some("A copyright statement".to_string()));
244
245        // source
246        assert!(header.source.is_some());
247        assert!(header.source.as_ref().unwrap().source == "Ancestry.com Family Trees".to_string());
248        assert!(header.source.as_ref().unwrap().version == Some("(2010.3)".to_string()));
249
250        assert!(
251            header
252                .source
253                .as_ref()
254                .unwrap()
255                .data
256                .as_ref()
257                .unwrap()
258                .copyright
259                == Some("Copyright of source data".to_string())
260        );
261        assert!(
262            header.source.as_ref().unwrap().data.as_ref().unwrap().date
263                == Some(DateTime {
264                    date: Some("1 JAN 1998".to_string()),
265                    time: None,
266                })
267        );
268        assert!(
269            header.source.as_ref().unwrap().data.as_ref().unwrap().name
270                == Some("Name of source data".to_string())
271        );
272
273        assert!(
274            header.source.as_ref().unwrap().name == Some("Ancestry.com Family Trees".to_string())
275        );
276        assert!(
277            header.source.as_ref().unwrap().corporation
278                == Some(Corporation {
279                    name: Some("Ancestry.com".to_string()),
280                    address: Some(Address {
281                        addr1: Some("Example Software".to_string()),
282                        addr2: Some("123 Main Street".to_string()),
283                        addr3: Some("Ste 1".to_string()),
284                        city: Some("Anytown".to_string()),
285                        state: Some("IL".to_string()),
286                        postal_code: Some("55555".to_string()),
287                        country: Some("USA".to_string()),
288                        phone: vec![
289                            "+1-800-555-1111".to_string(),
290                            "+1-800-555-1212".to_string(),
291                            "+1-800-555-1313".to_string(),
292                        ],
293                        email: vec![
294                            "a@example.com".to_string(),
295                            "b@example.com".to_string(),
296                            "c@example.com".to_string(),
297                        ],
298                        fax: vec![
299                            "+1-800-555-1414".to_string(),
300                            "+1-800-555-1515".to_string(),
301                            "+1-800-555-1616".to_string(),
302                        ],
303                        www: vec![
304                            "https://www.example.com".to_string(),
305                            "https://www.example.org".to_string(),
306                            "https://www.example.net".to_string(),
307                        ],
308                    })
309                })
310        );
311
312        // Version
313        assert!(
314            header.gedcom_version.as_ref().unwrap().form
315                == Some(Form {
316                    name: Some("LINEAGE-LINKED".to_string()),
317                    version: Some("5.5".to_string()),
318                })
319        );
320        assert!(header.gedcom_version.as_ref().unwrap().version == Some("5.5".to_string()));
321
322        // language
323        assert!(header.language.is_some());
324        assert!(header.language == Some("English".to_string()));
325
326        // datetime
327        assert!(header.date.is_some());
328        assert!(
329            header.date
330                == Some(DateTime {
331                    date: Some("1 JAN 2023".to_string()),
332                    time: Some("12:13:14.15".to_string())
333                })
334        );
335
336        // submitter
337        assert!(header.submitter.is_some());
338    }
339}