gedcom_rs/types/
source.rs

1// use crate::parse;
2// use crate::types::corporation::Corporation;
3
4use super::{corporation::Corporation, Line, SourceData};
5
6// +1 SOUR <APPROVED_SYSTEM_ID>
7//     +2 VERS <VERSION_NUMBER>
8//     +2 NAME <NAME_OF_PRODUCT>
9//     +2 CORP <NAME_OF_BUSINESS>
10//         +3 <<ADDRESS_STRUCTURE>>
11//     +2 DATA <NAME_OF_SOURCE_DATA>
12//         +3 DATE <PUBLICATION_DATE>
13//         +3 COPR <COPYRIGHT_SOURCE_DATA>
14//         +4 [CONT|CONC]<COPYRIGHT_SOURCE_DATA>
15
16// 1 SOUR Ancestry.com Family Trees
17// 2 NAME Ancestry.com Member Trees
18// 2 VERS 2021.07
19// 2 _TREE Ambrose Bierce Family Tree
20// 3 RIN 116823582
21// 3 _ENV prd
22// 2 CORP Ancestry.com
23// 3 PHON 801-705-7000
24// 3 WWW www.ancestry.com
25// 3 ADDR 1300 West Traverse Parkway
26// 4 CONT Lehi, UT  84043
27// 4 CONT USA
28
29#[derive(Debug, Default)]
30pub struct Source {
31    /// A corporation tag contains the name of the corporation and its address.
32    pub corporation: Option<Corporation>,
33    // pub data: Option<Data>,
34    pub name: Option<String>,
35    pub source: String,
36    pub data: Option<SourceData>,
37    // pub copyright: Option<Copyright>,
38    pub version: Option<String>,
39}
40
41impl Source {
42    /// Parse a SOUR record
43    pub fn parse(mut buffer: &str) -> (&str, Option<Source>) {
44        let mut source = Source {
45            corporation: None,
46            data: None,
47            name: None,
48            source: "".to_string(),
49            version: None,
50        };
51        let mut line: Line;
52
53        line = Line::peek(&mut buffer).unwrap();
54
55        // Verify we have a SOUR record
56        if line.level == 1 && line.tag == "SOUR" {
57            // Consume the first line
58            line = Line::parse(&mut buffer).unwrap();
59
60            source.source = line.value.to_string();
61
62            let mut next = Line::peek(&mut buffer).unwrap();
63
64            while next.level >= line.level {
65                // We don't want to consume the line yet because we may need
66                // the original for a parser.
67                let inner_line: Line = Line::peek(&mut buffer).unwrap();
68
69                // println!("Evaluating tag: {:?}", inner_line.tag);
70                match inner_line.tag {
71                    // An ancestry-specific tag
72                    "_TREE" => {
73                        // The value of tree contains the tree name, which is useful,
74                        // but not a part of the GEDCOM spec.
75                        // The next level (3) may contain RIN, some sort of internal id
76                        // but is probably not useful for anything
77                        println!("Skipping _TREE");
78                        // Consume the line
79                        Line::parse(&mut buffer).unwrap();
80                    }
81                    "CORP" => {
82                        (buffer, source.corporation) = Corporation::parse(buffer);
83                    }
84                    "NAME" => {
85                        source.name = Some(inner_line.value.to_string());
86                        Line::parse(&mut buffer).unwrap();
87                    }
88                    "VERS" => {
89                        source.version = Some(inner_line.value.to_string());
90                        Line::parse(&mut buffer).unwrap();
91                    }
92                    "DATA" => {
93                        (buffer, source.data) = SourceData::parse(buffer);
94                    }
95                    _ => {
96                        println!("Unknown line: {:?}", inner_line);
97
98                        // consume the line so we can parse the next
99                        Line::parse(&mut buffer).unwrap();
100                    }
101                }
102
103                // Peek at the next level
104                if !buffer.is_empty() {
105                    next = Line::peek(&mut buffer).unwrap();
106                    if next.level <= 1 {
107                        break;
108                    }
109                } else {
110                    break;
111                }
112            }
113        }
114
115        (buffer, Some(source))
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use crate::types::DateTime;
122
123    use super::{Source, SourceData};
124
125    #[test]
126    fn parse() {
127        let data = vec![
128            "1 SOUR GEDitCOM",
129            "2 VERS 2.9.4",
130            "2 NAME GEDitCOM",
131            "2 CORP RSAC Software",
132            "3 ADDR",
133            "4 ADR1 RSAC Software",
134            "4 ADR2 7108 South Pine Cone Street",
135            "4 ADR3 Ste 1",
136            "4 CITY Salt Lake City",
137            "4 STAE UT",
138            "4 POST 84121",
139            "4 CTRY USA",
140            "3 PHON +1-801-942-7768",
141            "3 PHON +1-801-555-1212",
142            "3 PHON +1-801-942-1148",
143            "3 EMAIL a@@example.com",
144            "3 EMAIL b@@example.com",
145            "3 EMAIL c@@example.com",
146            "3 FAX +1-801-942-7768",
147            "3 FAX +1-801-555-1212",
148            "3 FAX +1-801-942-1148",
149            "3 WWW https://www.example.com",
150            "3 WWW https://www.example.org",
151            "3 WWW https://www.example.net",
152            "2 DATA Name of source data",
153            "3 DATE 1 JAN 1998",
154            "3 COPR Copyright of source data",
155        ];
156
157        let (_data, source) = Source::parse(&data.join("\n"));
158        let sour = source.unwrap();
159
160        assert_eq!(sour.source, "GEDitCOM".to_string());
161        assert_eq!(sour.name, Some("GEDitCOM".to_string()));
162        assert_eq!(sour.version, Some("2.9.4".to_string()));
163        assert_eq!(
164            sour.data,
165            Some(SourceData {
166                name: Some("Name of source data".to_string()),
167                date: Some(DateTime {
168                    date: Some("1 JAN 1998".to_string()),
169                    time: None
170                }),
171                copyright: Some("Copyright of source data".to_string()),
172            })
173        );
174        let corp = sour.corporation.unwrap();
175
176        assert_eq!(corp.name, Some("RSAC Software".to_string()));
177
178        let corp_address: crate::types::Address = corp.address.unwrap();
179        assert_eq!(corp_address.addr1, Some("RSAC Software".to_string()));
180        assert_eq!(
181            corp_address.addr2,
182            Some("7108 South Pine Cone Street".to_string())
183        );
184        assert_eq!(corp_address.addr3, Some("Ste 1".to_string()));
185        assert_eq!(corp_address.city, Some("Salt Lake City".to_string()));
186        assert_eq!(corp_address.state, Some("UT".to_string()));
187        assert_eq!(corp_address.postal_code, Some("84121".to_string()));
188        assert_eq!(corp_address.country, Some("USA".to_string()));
189
190        assert!(corp_address.phone.contains(&"+1-801-942-7768".to_string()));
191        assert!(corp_address.phone.contains(&"+1-801-555-1212".to_string()));
192        assert!(corp_address.phone.contains(&"+1-801-942-1148".to_string()));
193        assert!(corp_address.email.contains(&"a@@example.com".to_string()));
194        assert!(corp_address.email.contains(&"b@@example.com".to_string()));
195        assert!(corp_address.email.contains(&"c@@example.com".to_string()));
196        assert!(corp_address.fax.contains(&"+1-801-942-1148".to_string()));
197        assert!(corp_address.fax.contains(&"+1-801-942-1148".to_string()));
198        assert!(corp_address.fax.contains(&"+1-801-942-1148".to_string()));
199        assert!(corp_address
200            .www
201            .contains(&"https://www.example.com".to_string()));
202        assert!(corp_address
203            .www
204            .contains(&"https://www.example.org".to_string()));
205        assert!(corp_address
206            .www
207            .contains(&"https://www.example.net".to_string()));
208    }
209}