File Coverage

blib/lib/Gedcom/Individual.pm
Criterion Covered Total %
statement 133 236 56.3
branch 45 128 35.1
condition 5 21 23.8
subroutine 26 35 74.2
pod 8 31 25.8
total 217 451 48.1


line stmt bran cond sub pod time code
1             # Copyright 1999-2019, Paul Johnson (paul@pjcj.net)
2              
3             # This software is free. It is licensed under the same terms as Perl itself.
4              
5             # The latest version of this software should be available from my homepage:
6             # http://www.pjcj.net
7              
8             # documentation at __END__
9              
10 11     11   62 use strict;
  11         15  
  11         429  
11              
12             require 5.005;
13              
14             package Gedcom::Individual;
15              
16 11     11   4371 use Gedcom::Record 1.22;
  11         184  
  11         357  
17              
18 11     11   100 use vars qw($VERSION @ISA);
  11         19  
  11         28329  
19             $VERSION = "1.22";
20             @ISA = qw( Gedcom::Record );
21              
22             sub name {
23 4620     4620 1 5367 my $self = shift;
24 4620         6975 my $name = $self->tag_value("NAME");
25 4620 50       6946 return "" unless defined $name;
26 4620         15495 $name =~ s/\s+/ /g;
27 4620         20913 $name =~ s| ?/ ?(.*?) ?/ ?| /$1/ |;
28 4620         7996 $name =~ s/^\s+//g;
29 4620         10728 $name =~ s/\s+$//g;
30 4620         15204 $name
31             }
32              
33             sub cased_name {
34 0     0 1 0 my $self = shift;
35 0         0 my $name = $self->name;
36 0         0 $name =~ s|/([^/]*)/?|uc $1|e;
  0         0  
37 0         0 $name
38             }
39              
40             sub surname {
41 4464     4464 1 4517 my $self = shift;
42 4464         5371 my ($surname) = $self->name =~ m|/([^/]*)/?|;
43 4464 100       16231 $surname || ""
44             }
45              
46             sub given_names {
47 0     0 1 0 my $self = shift;
48 0         0 my $name = $self->name;
49 0         0 $name =~ s|/([^/]*)/?| |;
50 0         0 $name =~ s|^\s+||;
51 0         0 $name =~ s|\s+$||;
52 0         0 $name =~ s|\s+| |g;
53 0         0 $name
54             }
55              
56 4464     4464 1 4881 sub soundex { my $self = shift;
57 4464 50       6628 unless ($INC{"Text/Soundex.pm"}) {
58 0         0 warn "Text::Soundex.pm is required to use soundex()";
59             return undef
60 0         0 }
61 4464         5816 Gedcom::soundex($self->surname)
62             }
63              
64             sub sex {
65 6     6 1 16 my $self = shift;
66 6         24 my $sex = $self->tag_value("SEX");
67 6 50       121 defined $sex
    50          
    50          
68             ? $sex =~ /^F/i ? "F" : $sex =~ /^M/i ? "M" : "U"
69             : "U"
70             }
71              
72             sub father {
73 1433     1433 0 4670 my $self = shift;
74 1433         2045 my @a = map { $_->husband } $self->famc;
  1110         2055  
75 1433 100       2858 wantarray ? @a : $a[0]
76             }
77              
78             sub mother {
79 1433     1433 0 4799 my $self = shift;
80 1433         1881 my @a = map { $_->wife } $self->famc;
  1110         1977  
81 1433 100       3131 wantarray ? @a : $a[0]
82             }
83              
84             sub parents {
85 1385     1385 0 4422 my $self = shift;
86 1385         2023 ($self->father, $self->mother)
87             }
88              
89             sub husband {
90 1169     1169 0 4070 my $self = shift;
91 708         1542 my @a = grep { $_->{xref} ne $self->{xref} }
92 1169         1647 map { $_->husband } $self->fams;
  768         1408  
93 1169 50       2426 wantarray ? @a : $a[0]
94             }
95              
96             sub wife {
97 1169     1169 0 4067 my $self = shift;
98 756         1756 my @a = grep { $_->{xref} ne $self->{xref} }
99 1169         1647 map { $_->wife } $self->fams;
  768         1322  
100 1169 50       2009 wantarray ? @a : $a[0]
101             }
102              
103             sub spouse {
104 1145     1145 0 4120 my $self = shift;
105 1145         1728 my @a = ($self->husband, $self->wife);
106 1145 50       2150 wantarray ? @a : $a[0]
107             }
108              
109             sub siblings {
110 1193     1193 0 4148 my $self = shift;
111 2148         3970 my @a = grep { $_->{xref} ne $self->{xref} }
112 1193         1727 map { $_->children } $self->famc;
  876         1579  
113 1193 50       2823 wantarray ? @a : $a[0]
114             }
115              
116             sub half_siblings {
117 0     0 0 0 my $self = shift;
118             my @all_siblings_multiple =
119 0         0 map { $_->children } map { $_->fams } $self->parents;
  0         0  
  0         0  
120 0         0 my @excludelist = ($self, $self->siblings);
121             my @a = grep {
122 0         0 my $cur = $_;
  0         0  
123 0         0 my $half_sibling = 1;
124 0         0 for my $test (@excludelist) {
125 0 0       0 if ($cur->{xref} eq $test->{xref} ) {
126 0         0 $half_sibling = 0;
127 0         0 last;
128             }
129             }
130 0 0       0 push @excludelist, $cur if $half_sibling; # to avoid multiple output
131 0         0 $half_sibling;
132             } @all_siblings_multiple;
133 0 0       0 wantarray ? @a : $a[0]
134             }
135              
136             sub older_siblings {
137 0     0 0 0 my $self = shift;
138 0         0 my @a = map { $_->children } $self->famc;
  0         0  
139 0         0 my $i;
140 0         0 for ($i = 0; $i <= $#a; $i++) {
141             last if $a[$i]->{xref} eq $self->{xref}
142 0 0       0 }
143 0         0 splice @a, $i;
144 0 0       0 wantarray ? @a : $a[-1]
145             }
146              
147             sub younger_siblings {
148 0     0 0 0 my $self = shift;
149 0         0 my @a = map { $_->children } $self->famc;
  0         0  
150 0         0 my $i;
151 0         0 for ($i = 0; $i <= $#a; $i++) {
152             last if $a[$i]->{xref} eq $self->{xref}
153 0 0       0 }
154 0         0 splice @a, 0, $i + 1;
155 0 0       0 wantarray ? @a : $a[0]
156             }
157              
158             sub brothers {
159 24     24 0 3089 my $self = shift;
160 24         86 my @a = grep { $_->tag_value("SEX") !~ /^F/i } $self->siblings;
  24         59  
161 24 50       116 wantarray ? @a : $a[0]
162             }
163              
164             sub half_brothers {
165 0     0 0 0 my $self = shift;
166 0         0 my @a = grep { $_->tag_value("SEX") !~ /^F/i } $self->half_siblings;
  0         0  
167 0 0       0 wantarray ? @a : $a[0]
168             }
169              
170             sub sisters {
171 24     24 0 2832 my $self = shift;
172 24         72 my @a = grep { $_->tag_value("SEX") !~ /^M/i } $self->siblings;
  24         65  
173 24 50       85 wantarray ? @a : $a[0]
174             }
175              
176             sub half_sisters {
177 0     0 0 0 my $self = shift;
178 0         0 my @a = grep { $_->tag_value("SEX") !~ /^M/i } $self->half_siblings;
  0         0  
179 0 0       0 wantarray ? @a : $a[0]
180             }
181              
182             sub children {
183 1457     1457 0 4575 my $self = shift;
184 1457         2079 my @a = map { $_->children } $self->fams;
  888         1589  
185 1457 50       2876 wantarray ? @a : $a[0]
186             }
187              
188             sub sons {
189 24     24 0 2828 my $self = shift;
190 24         66 my @a = grep { $_->tag_value("SEX") !~ /^F/i } $self->children;
  96         199  
191 24 50       107 wantarray ? @a : $a[0]
192             }
193              
194             sub daughters {
195 24     24 0 2942 my $self = shift;
196 24         64 my @a = grep { $_->tag_value("SEX") !~ /^M/i } $self->children;
  96         204  
197 24 50       94 wantarray ? @a : $a[0]
198             }
199              
200             sub descendents {
201 24     24 0 2882 my $self = shift;
202 24         40 my @d;
203 24         60 my @c = $self->children;
204 24         64 while (@c) {
205 48         95 push @d, @c;
206 48         73 @c = map { $_->children } @c;
  240         323  
207             }
208             @d
209 24         88 }
210              
211             sub ancestors {
212 36     36 0 2857 my $self = shift;
213 36         76 my @d;
214 36         104 my @c = $self->parents;
215 36         99 while (@c) {
216 102         179 push @d, @c;
217 102         136 @c = map { $_->parents } @c;
  204         293  
218             }
219             @d
220 36         174 }
221              
222             sub delete {
223 1     1 1 3 my $self = shift;
224 1         10 my $xref = $self->{xref};
225 1         2 my $ret = 1;
226 1         6 for my $f ([ "(HUSB|WIFE)", [$self->fams] ], [ "CHIL", [$self->famc] ]) {
227 2         10 for my $fam (@{$f->[1]}) {
  2         7  
228             # print "deleting from $fam->{xref}\n";
229 1         3 for my $record (@{$fam->_items}) {
  1         3  
230             # print "looking at $record->{tag} $record->{value}\n";
231 3 100 100     58 if (($record->{tag} =~ /$f->[0]/) &&
232             $self->resolve($record->{value})->{xref} eq $xref) {
233 1 50       7 $ret = 0 unless $fam->delete_record($record);
234             }
235             }
236 1 0 33     6 $self->{gedcom}{record}->delete_record($fam)
      33        
237             unless $fam->tag_value("HUSB") ||
238             $fam->tag_value("WIFE") ||
239             $fam->tag_value("CHIL");
240             # TODO - write Family::delete ?
241             # - delete associated notes?
242             }
243             }
244 1 50       5 $ret = 0 unless $self->{gedcom}{record}->delete_record($self);
245 1 50       3 $_[0] = undef if $ret; # Can't reuse a deleted person
246 1         5 $ret
247             }
248              
249             sub print {
250 0     0 1 0 my $self = shift;
251 0 0       0 $self->_items if shift;
252 0         0 $self->SUPER::print; $_->print for @{$self->{items}};
  0         0  
  0         0  
253             # print "fams:\n"; $_->print for $self->fams;
254             # print "famc:\n"; $_->print for $self->famc;
255             }
256              
257             sub print_generations {
258 0     0 0 0 my $self = shift;
259 0         0 my ($generations, $indent) = @_;
260 0 0       0 $generations = 0 unless $generations;
261 0 0       0 $indent = 0 unless $indent;
262 0 0       0 return unless $generations > 0;
263 0         0 my $i = " " x $indent;
264 0 0       0 print "$i$self->{xref} (", $self->rin, ") ", $self->name, "\n"
265             unless $indent;
266 0         0 $self->print;
267 0         0 for my $fam ($self->fams) {
268             # $fam->print;
269 0         0 for my $spouse ($fam->parents) {
270 0 0       0 next unless $spouse;
271             # print "[$spouse]\n";
272 0 0       0 next if $self->xref eq $spouse->xref;
273 0         0 print "$i= $spouse->{xref} (", $spouse->rin, ") ",
274             $spouse->name, "\n";
275             }
276 0         0 for my $child ($fam->children) {
277 0         0 print "$i> $child->{xref} (", $child->rin, ") ",
278             $child->name, "\n";
279 0         0 $child->print_generations($generations - 1, $indent + 1);
280             }
281             }
282             }
283              
284             sub famc {
285 5187     5187 0 5567 my $self = shift;
286 5187         7863 my @a = $self->resolve($self->tag_value("FAMC"));
287 5187 100       8554 wantarray ? @a : $a[0]
288             }
289              
290             sub fams {
291 4917     4917 0 5151 my $self = shift;
292 4917         7372 my @a = $self->resolve($self->tag_value("FAMS"));
293 4917 50       8331 wantarray ? @a : $a[0]
294             }
295              
296             # FIXME: currently only finds ancestors
297             # TODO: find in-laws
298             # See http://www.myrelative.com/html/relationship.html for inspiration
299              
300             sub relationship {
301 12     12 0 77 my $self = shift;
302 12         28 my ($other) = @_;
303              
304 12 100       42 my @ancestors = $self->ancestors() or return;
305              
306 6         29 my $sex = $self->sex;
307 6 50       32 die $self->name, ": unknown sex\n" if $sex eq "U";
308              
309 6         22 for my $person1 (@ancestors) {
310 24 100       78 if ($person1 eq $other) {
311             # Direct ancestor
312 6         31 my $steps = $self->_stepsabove($other, 0);
313 6 50       39 my $title = $sex eq "M" ? "father" : "mother";
314 6 50       55 if ($steps >= 5) {
    50          
    50          
    0          
    0          
    0          
315 0         0 $steps -= 2;
316 0         0 return "$steps times great-grand$title";
317             } elsif ($steps == 1) {
318 0         0 return $title;
319             } elsif ($steps == 2) {
320 6         44 return "grand$title";
321             } elsif ($steps == 3) {
322 0         0 return "great-grand$title";
323             } elsif ($steps == 4) {
324 0         0 return "great-great-grand$title";
325             } elsif ($steps <= 0) {
326 0 0       0 if (my $spouse = $other->spouse) {
327 0 0       0 if ($self->_stepsabove($spouse, 0)) {
328             # The caller should now check
329             # the spouse's relationship
330 0         0 return;
331             }
332             }
333 0         0 die $other->name,
334             ": BUG - not a direct ancestor, steps = $steps";
335             }
336             }
337             }
338              
339 0 0       0 my @ancestors2 = $other->ancestors or return;
340              
341 0         0 for my $person1 (@ancestors) {
342 0         0 for my $person2 (@ancestors2) {
343             # print $person1->name, '->', $person2->name, "\n";
344             # G::C is noisy
345             # TODO - apparently fixed in Github, awaiting new version on CPAN
346             # my $c = Gedcom::Comparison->new($person1, $person2);
347             # if($c->identical($person2)) {
348             # die 'match found';
349             # }
350 0 0       0 if ($person1 eq $person2) {
351             # Common ancestor is $person2
352 0         0 my $steps1 = $self->_stepsabove($person1, 0);
353 0 0       0 return if $steps1 > 7;
354 0         0 my $steps2 = $other->_stepsabove($person2, 0);
355 0 0       0 return if $steps2 > 7;
356              
357             # It would be nice to do this as an algorithm, but this will do
358             # e.g. 2, 1 is uncle
359 0         0 my $rel = {
360             2 << 8 | 2 => "cousin",
361             2 << 8 | 3 => "first cousin once-removed",
362             3 << 8 | 2 => "first cousin once-removed",
363             2 << 8 | 4 => "first cousin twice-removed",
364             3 << 8 | 3 => "second cousin",
365             3 << 8 | 4 => "second cousin once-removed",
366             4 << 8 | 2 => "first cousin twice-removed",
367             5 << 8 | 2 => "first cousin three-times-removed",
368             5 << 8 | 3 => "second cousin twice-removed",
369             6 << 8 | 3 => "second cousin three-times-removed",
370             6 << 8 | 4 => "third cousin twice-removed",
371             6 << 8 | 5 => "fourth cousin once-removed",
372             7 << 8 | 5 => "fourth cousin twice-removed",
373             };
374 0         0 my $m_rel = {
375             1 << 8 | 1 => "brother",
376             1 << 8 | 2 => "nephew",
377             2 << 8 | 1 => "uncle",
378             3 << 8 | 1 => "great-uncle",
379             4 << 8 | 1 => "great-great-uncle",
380             };
381 0         0 my $f_rel = {
382             1 << 8 | 1 => "sister",
383             1 << 8 | 2 => "niece",
384             2 << 8 | 1 => "aunt",
385             3 << 8 | 1 => "great-aunt",
386             4 << 8 | 1 => "great-great-aunt",
387             };
388              
389 0         0 my $n = ($steps1 << 8) | $steps2;
390 0   0     0 my $rc = $rel->{$n} || ($sex eq "M" ? $m_rel : $f_rel)->{$n};
391 0 0 0     0 if ($rc && $rc =~ /cousin/) {
392 0         0 my $father = $self->father;
393 0         0 my $mother = $self->mother;
394 0 0 0     0 if ($father && ($father->_stepsabove($person2, 0) > 0)) {
    0 0        
395 0         0 $rc .= " on your father's side";
396             } elsif ($mother && ($mother->_stepsabove($person2, 0) > 0)) {
397 0         0 $rc .= " on your mother's side";
398             }
399             }
400             # print "$steps1, $steps2\n" if(!defined($rc));
401              
402 0         0 return $rc;
403             }
404             }
405             }
406             }
407              
408             sub _stepsabove {
409 30     30   40 my $self = shift;
410 30         48 my ($target, $count) = @_;
411              
412 30 50       89 return -1 if $count == -1;
413              
414 30 100       84 return $count if $self eq $target;
415              
416 24         43 my $father = $self->father;
417 24 100       63 if ($father) {
418 12         64 my $rc = $father->_stepsabove($target, $count + 1);
419 12 50       45 return $rc unless $rc == -1;
420             }
421              
422 24         53 my $mother = $self->mother;
423 24 100       61 if ($mother) {
424 12         35 return $mother->_stepsabove($target, $count + 1);
425             }
426              
427 12         37 -1
428             }
429              
430             1
431              
432             __END__