1use std::fmt;
7
8use crate::types::{
9 family::Family,
10 header::Header,
11 individual::{name::Name, Individual},
12 multimedia::Multimedia,
13 note::Note,
14 repository::Repository,
15 source::Source,
16 submission::Submission,
17 submitter::Submitter,
18 GedcomData,
19};
20
21impl fmt::Display for GedcomData {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 writeln!(f, "GEDCOM Data")?;
24 writeln!(f, "============")?;
25
26 if let Some(ref header) = self.header {
27 writeln!(f, "{header}")?;
28 }
29
30 if !self.individuals.is_empty() {
31 writeln!(f, "\nIndividuals ({}):", self.individuals.len())?;
32 for individual in &self.individuals {
33 writeln!(f, " {individual}")?;
34 }
35 }
36
37 if !self.families.is_empty() {
38 writeln!(f, "\nFamilies ({}):", self.families.len())?;
39 for family in &self.families {
40 writeln!(f, " {family}")?;
41 }
42 }
43
44 if !self.sources.is_empty() {
45 writeln!(f, "\nSources ({}):", self.sources.len())?;
46 for source in &self.sources {
47 writeln!(f, " {source}")?;
48 }
49 }
50
51 if !self.repositories.is_empty() {
52 writeln!(f, "\nRepositories ({}):", self.repositories.len())?;
53 for repo in &self.repositories {
54 writeln!(f, " {repo}")?;
55 }
56 }
57
58 if !self.multimedia.is_empty() {
59 writeln!(f, "\nMultimedia ({}):", self.multimedia.len())?;
60 for media in &self.multimedia {
61 writeln!(f, " {media}")?;
62 }
63 }
64
65 if !self.submitters.is_empty() {
66 writeln!(f, "\nSubmitters ({}):", self.submitters.len())?;
67 for submitter in &self.submitters {
68 writeln!(f, " {submitter}")?;
69 }
70 }
71
72 if !self.submissions.is_empty() {
73 writeln!(f, "\nSubmissions ({}):", self.submissions.len())?;
74 for submission in &self.submissions {
75 writeln!(f, " {submission}")?;
76 }
77 }
78
79 Ok(())
80 }
81}
82
83impl fmt::Display for Header {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 write!(f, "Header")?;
86
87 if let Some(ref gedcom) = self.gedcom {
88 if let Some(ref version) = gedcom.version {
89 write!(f, " (GEDCOM {version})")?;
90 }
91 }
92
93 if let Some(ref source) = self.source {
94 if let Some(ref name) = source.name {
95 write!(f, " - Source: {name}")?;
96 }
97 }
98
99 if let Some(ref encoding) = self.encoding {
100 if let Some(ref value) = encoding.value {
101 write!(f, " [{value}]")?;
102 }
103 }
104
105 Ok(())
106 }
107}
108
109impl fmt::Display for Individual {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 if let Some(ref xref) = self.xref {
113 write!(f, "{xref} ")?;
114 }
115
116 if let Some(ref name) = self.name {
118 write!(f, "{name}")?;
119 } else {
120 write!(f, "(Unknown Name)")?;
121 }
122
123 if let Some(ref sex) = self.sex {
125 write!(f, " ({})", sex.value)?;
126 }
127
128 let mut birth_date: Option<&str> = None;
129 let mut baptism_date: Option<&str> = None;
130 let mut death_date: Option<&str> = None;
131 let mut inhumation_date: Option<&str> = None;
132
133 for event in &self.events {
134 match event.event {
135 crate::types::event::Event::Birth if birth_date.is_none() => {
136 birth_date = event.date.as_ref().and_then(|d| d.value.as_deref());
137 }
138 crate::types::event::Event::Baptism if baptism_date.is_none() => {
139 baptism_date = event.date.as_ref().and_then(|d| d.value.as_deref());
140 }
141 crate::types::event::Event::Death if death_date.is_none() => {
142 death_date = event.date.as_ref().and_then(|d| d.value.as_deref());
143 }
144 crate::types::event::Event::Burial if inhumation_date.is_none() => {
145 inhumation_date = event.date.as_ref().and_then(|d| d.value.as_deref());
146 }
147 _ => {}
148 }
149 }
150
151 if let Some(date) = birth_date.or(baptism_date) {
152 write!(f, ", b. {date}")?;
153 }
154
155 if let Some(date) = death_date.or(inhumation_date) {
156 write!(f, ", d. {date}")?;
157 }
158
159 Ok(())
160 }
161}
162
163impl fmt::Display for Name {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 if let Some(ref value) = self.value {
166 let display_name = value.replace('/', "").trim().to_string();
169 if display_name.is_empty() {
170 write!(f, "(Unknown)")?;
171 } else {
172 write!(f, "{display_name}")?;
173 }
174 } else {
175 let mut parts = Vec::new();
177
178 if let Some(ref prefix) = self.prefix {
179 parts.push(prefix.clone());
180 }
181 if let Some(ref given) = self.given {
182 parts.push(given.clone());
183 }
184 if let Some(ref surname_prefix) = self.surname_prefix {
185 parts.push(surname_prefix.clone());
186 }
187 if let Some(ref surname) = self.surname {
188 parts.push(surname.clone());
189 }
190 if let Some(ref suffix) = self.suffix {
191 parts.push(suffix.clone());
192 }
193
194 if parts.is_empty() {
195 write!(f, "(Unknown)")?;
196 } else {
197 write!(f, "{}", parts.join(" "))?;
198 }
199 }
200
201 Ok(())
202 }
203}
204
205impl fmt::Display for Family {
206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207 if let Some(ref xref) = self.xref {
208 write!(f, "{xref} ")?;
209 }
210
211 let mut members = Vec::new();
212
213 if let Some(ref ind1) = self.individual1 {
214 members.push(format!("Partner 1: {ind1}"));
215 }
216 if let Some(ref ind2) = self.individual2 {
217 members.push(format!("Partner 2: {ind2}"));
218 }
219
220 if members.is_empty() {
221 write!(f, "(No partners)")?;
222 } else {
223 write!(f, "{}", members.join(", "))?;
224 }
225
226 if !self.children.is_empty() {
227 write!(f, " [{} child(ren)]", self.children.len())?;
228 }
229
230 let mut marriage_date: Option<&str> = None;
231 let mut engagement_date: Option<&str> = None;
232 let mut separated_date: Option<&str> = None;
233 let mut divorce_date: Option<&str> = None;
234 let mut annulment_date: Option<&str> = None;
235
236 for event in &self.events {
237 match event.event {
238 crate::types::event::Event::Marriage if marriage_date.is_none() => {
239 marriage_date = event.date.as_ref().and_then(|d| d.value.as_deref());
240 }
241 crate::types::event::Event::Engagement if engagement_date.is_none() => {
242 engagement_date = event.date.as_ref().and_then(|d| d.value.as_deref());
243 }
244 crate::types::event::Event::Separated if separated_date.is_none() => {
245 separated_date = event.date.as_ref().and_then(|d| d.value.as_deref());
246 }
247 crate::types::event::Event::Divorce if divorce_date.is_none() => {
248 divorce_date = event.date.as_ref().and_then(|d| d.value.as_deref());
249 }
250 crate::types::event::Event::Annulment if annulment_date.is_none() => {
251 annulment_date = event.date.as_ref().and_then(|d| d.value.as_deref());
252 }
253 _ => {}
254 }
255 }
256
257 if let Some(date) = marriage_date {
258 write!(f, ", m. {date}")?;
259 } else if let Some(date) = engagement_date {
260 write!(f, ", rel. {date}")?;
261 } else if let Some(date) = separated_date {
262 write!(f, ", sep. {date}")?;
263 } else if let Some(date) = divorce_date {
264 write!(f, ", div. {date}")?;
265 } else if let Some(date) = annulment_date {
266 write!(f, ", anul. {date}")?;
267 }
268
269 Ok(())
270 }
271}
272
273impl fmt::Display for Source {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 if let Some(ref xref) = self.xref {
276 write!(f, "{xref} ")?;
277 }
278
279 if let Some(ref title) = self.title {
280 write!(f, "\"{title}\"")?;
281 } else if let Some(ref abbr) = self.abbreviation {
282 write!(f, "{abbr}")?;
283 } else {
284 write!(f, "(Untitled Source)")?;
285 }
286
287 if let Some(ref author) = self.author {
288 write!(f, " by {author}")?;
289 }
290
291 Ok(())
292 }
293}
294
295impl fmt::Display for Repository {
296 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
297 if let Some(ref xref) = self.xref {
298 write!(f, "{xref} ")?;
299 }
300
301 if let Some(ref name) = self.name {
302 write!(f, "{name}")?;
303 } else {
304 write!(f, "(Unnamed Repository)")?;
305 }
306
307 if let Some(ref address) = self.address {
308 if let Some(ref city) = address.city {
309 write!(f, ", {city}")?;
310 }
311 }
312
313 Ok(())
314 }
315}
316
317impl fmt::Display for Multimedia {
318 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319 if let Some(ref xref) = self.xref {
320 write!(f, "{xref} ")?;
321 }
322
323 if let Some(ref title) = self.title {
324 write!(f, "\"{title}\"")?;
325 } else if let Some(ref file) = self.file {
326 if let Some(ref file_value) = file.value {
327 write!(f, "{file_value}")?;
328 } else {
329 write!(f, "(File reference)")?;
330 }
331 } else {
332 write!(f, "(Unnamed Media)")?;
333 }
334
335 if let Some(ref form) = self.form {
336 if let Some(ref format_value) = form.value {
337 write!(f, " [{format_value}]")?;
338 }
339 }
340
341 Ok(())
342 }
343}
344
345impl fmt::Display for Submitter {
346 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347 if let Some(ref xref) = self.xref {
348 write!(f, "{xref} ")?;
349 }
350
351 if let Some(ref name) = self.name {
352 write!(f, "{name}")?;
353 } else {
354 write!(f, "(Unknown Submitter)")?;
355 }
356
357 Ok(())
358 }
359}
360
361impl fmt::Display for Submission {
362 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363 if let Some(ref xref) = self.xref {
364 write!(f, "{xref} ")?;
365 }
366
367 if let Some(ref family_file) = self.family_file_name {
368 write!(f, "Family File: {family_file}")?;
369 } else {
370 write!(f, "(Submission Record)")?;
371 }
372
373 Ok(())
374 }
375}
376
377impl fmt::Display for Note {
378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379 if let Some(ref value) = self.value {
380 const MAX_LEN: usize = 100;
382 if value.len() > MAX_LEN {
383 write!(f, "{}...", &value[..MAX_LEN])?;
384 } else {
385 write!(f, "{value}")?;
386 }
387 } else {
388 write!(f, "(Empty Note)")?;
389 }
390
391 Ok(())
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use crate::Gedcom;
399
400 #[test]
401 fn test_gedcom_data_display() {
402 let sample = "\
403 0 HEAD\n\
404 1 GEDC\n\
405 2 VERS 5.5\n\
406 0 @I1@ INDI\n\
407 1 NAME John /Doe/\n\
408 1 SEX M\n\
409 0 TRLR";
410
411 let mut gedcom = Gedcom::new(sample.chars()).unwrap();
412 let data = gedcom.parse_data().unwrap();
413
414 let display = format!("{data}");
415 assert!(display.contains("GEDCOM Data"));
416 assert!(display.contains("Individuals (1)"));
417 assert!(display.contains("John Doe"));
418 }
419
420 #[test]
421 fn test_individual_display() {
422 let sample = "\
423 0 HEAD\n\
424 1 GEDC\n\
425 2 VERS 5.5\n\
426 0 @I1@ INDI\n\
427 1 NAME Jane /Smith/\n\
428 1 SEX F\n\
429 1 BIRT\n\
430 2 DATE 15 MAR 1985\n\
431 0 TRLR";
432
433 let mut gedcom = Gedcom::new(sample.chars()).unwrap();
434 let data = gedcom.parse_data().unwrap();
435
436 let display = format!("{}", data.individuals[0]);
437 assert!(display.contains("@I1@"));
438 assert!(display.contains("Jane Smith"));
439 assert!(display.contains("Female"));
440 assert!(display.contains("b. 15 MAR 1985"));
441 }
442
443 #[test]
444 fn test_family_display() {
445 let sample = "\
446 0 HEAD\n\
447 1 GEDC\n\
448 2 VERS 5.5\n\
449 0 @F1@ FAM\n\
450 1 HUSB @I1@\n\
451 1 WIFE @I2@\n\
452 1 CHIL @I3@\n\
453 1 MARR\n\
454 2 DATE 1 JUN 2000\n\
455 0 TRLR";
456
457 let mut gedcom = Gedcom::new(sample.chars()).unwrap();
458 let data = gedcom.parse_data().unwrap();
459
460 let display = format!("{}", data.families[0]);
461 assert!(display.contains("@F1@"));
462 assert!(display.contains("@I1@"));
463 assert!(display.contains("@I2@"));
464 assert!(display.contains("1 child(ren)"));
465 assert!(display.contains("m. 1 JUN 2000"));
466 }
467
468 #[test]
469 fn test_family_display_engagement_fallback() {
470 let sample = "\
471 0 HEAD\n\
472 1 GEDC\n\
473 2 VERS 5.5\n\
474 0 @F1@ FAM\n\
475 1 HUSB @I1@\n\
476 1 WIFE @I2@\n\
477 1 ENGA\n\
478 2 DATE 1 JUN 1999\n\
479 0 TRLR";
480
481 let mut gedcom = Gedcom::new(sample.chars()).unwrap();
482 let data = gedcom.parse_data().unwrap();
483
484 let display = format!("{}", data.families[0]);
485 assert!(display.contains("rel. 1 JUN 1999"));
486 }
487
488 #[test]
489 fn test_family_display_separated_fallback() {
490 let sample = "\
491 0 HEAD\n\
492 1 GEDC\n\
493 2 VERS 5.5\n\
494 0 @F1@ FAM\n\
495 1 HUSB @I1@\n\
496 1 WIFE @I2@\n\
497 1 SEP\n\
498 2 DATE 1 JUN 2001\n\
499 0 TRLR";
500
501 let mut gedcom = Gedcom::new(sample.chars()).unwrap();
502 let data = gedcom.parse_data().unwrap();
503
504 let display = format!("{}", data.families[0]);
505 assert!(display.contains("sep. 1 JUN 2001"));
506 }
507
508 #[test]
509 fn test_family_display_divorce_fallback() {
510 let sample = "\
511 0 HEAD\n\
512 1 GEDC\n\
513 2 VERS 5.5\n\
514 0 @F1@ FAM\n\
515 1 HUSB @I1@\n\
516 1 WIFE @I2@\n\
517 1 DIV\n\
518 2 DATE 1 JUN 2002\n\
519 0 TRLR";
520
521 let mut gedcom = Gedcom::new(sample.chars()).unwrap();
522 let data = gedcom.parse_data().unwrap();
523
524 let display = format!("{}", data.families[0]);
525 assert!(display.contains("div. 1 JUN 2002"));
526 }
527
528 #[test]
529 fn test_family_display_annulment_fallback() {
530 let sample = "\
531 0 HEAD\n\
532 1 GEDC\n\
533 2 VERS 5.5\n\
534 0 @F1@ FAM\n\
535 1 HUSB @I1@\n\
536 1 WIFE @I2@\n\
537 1 ANUL\n\
538 2 DATE 1 JUN 2003\n\
539 0 TRLR";
540
541 let mut gedcom = Gedcom::new(sample.chars()).unwrap();
542 let data = gedcom.parse_data().unwrap();
543
544 let display = format!("{}", data.families[0]);
545 assert!(display.contains("anul. 1 JUN 2003"));
546 }
547
548 #[test]
549 fn test_name_display() {
550 let sample = "\
551 0 HEAD\n\
552 1 GEDC\n\
553 2 VERS 5.5\n\
554 0 @I1@ INDI\n\
555 1 NAME Robert /Johnson/ Jr.\n\
556 0 TRLR";
557
558 let mut gedcom = Gedcom::new(sample.chars()).unwrap();
559 let data = gedcom.parse_data().unwrap();
560
561 let name = data.individuals[0].name.as_ref().unwrap();
562 let display = format!("{name}");
563 assert!(display.contains("Robert"));
564 assert!(display.contains("Johnson"));
565 assert!(!display.contains('/'));
567 }
568
569 #[test]
570 fn test_source_display() {
571 let sample = "\
572 0 HEAD\n\
573 1 GEDC\n\
574 2 VERS 5.5\n\
575 0 @S1@ SOUR\n\
576 1 TITL Census Records 1900\n\
577 1 AUTH Government\n\
578 0 TRLR";
579
580 let mut gedcom = Gedcom::new(sample.chars()).unwrap();
581 let data = gedcom.parse_data().unwrap();
582
583 let display = format!("{}", data.sources[0]);
584 assert!(display.contains("@S1@"));
585 assert!(display.contains("Census Records 1900"));
586 assert!(display.contains("by Government"));
587 }
588
589 #[test]
590 fn test_header_display() {
591 let sample = "\
592 0 HEAD\n\
593 1 GEDC\n\
594 2 VERS 5.5\n\
595 1 SOUR MyApp\n\
596 2 NAME My Application\n\
597 1 CHAR UTF-8\n\
598 0 TRLR";
599
600 let mut gedcom = Gedcom::new(sample.chars()).unwrap();
601 let data = gedcom.parse_data().unwrap();
602
603 let header = data.header.as_ref().unwrap();
604 let display = format!("{header}");
605 assert!(display.contains("Header"));
606 assert!(display.contains("GEDCOM 5.5"));
607 }
608
609 #[test]
610 fn test_note_display_truncation() {
611 let long_note = "A".repeat(200);
612 let note = Note {
613 value: Some(long_note),
614 mime: None,
615 translation: None,
616 citation: None,
617 language: None,
618 };
619
620 let display = format!("{note}");
621 assert!(display.ends_with("..."));
622 assert!(display.len() < 110); }
624}