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   60 use strict;
  11         16  
  11         414  
11              
12             require 5.005;
13              
14             package Gedcom::Individual;
15              
16 11     11   4271 use Gedcom::Record 1.21;
  11         174  
  11         352  
17              
18 11     11   61 use vars qw($VERSION @ISA);
  11         22  
  11         27168  
19             $VERSION = "1.21";
20             @ISA = qw( Gedcom::Record );
21              
22             sub name {
23 4620     4620 1 5293 my $self = shift;
24 4620         6756 my $name = $self->tag_value("NAME");
25 4620 50       6812 return "" unless defined $name;
26 4620         15107 $name =~ s/\s+/ /g;
27 4620         20276 $name =~ s| ?/ ?(.*?) ?/ ?| /$1/ |;
28 4620         7765 $name =~ s/^\s+//g;
29 4620         10662 $name =~ s/\s+$//g;
30 4620         14563 $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 4327 my $self = shift;
42 4464         5311 my ($surname) = $self->name =~ m|/([^/]*)/?|;
43 4464 100       15953 $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 4997 sub soundex { my $self = shift;
57 4464 50       6964 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         5817 Gedcom::soundex($self->surname)
62             }
63              
64             sub sex {
65 6     6 1 15 my $self = shift;
66 6         20 my $sex = $self->tag_value("SEX");
67 6 50       83 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 4317 my $self = shift;
74 1433         2056 my @a = map { $_->husband } $self->famc;
  1110         1978  
75 1433 100       2934 wantarray ? @a : $a[0]
76             }
77              
78             sub mother {
79 1433     1433 0 4062 my $self = shift;
80 1433         1936 my @a = map { $_->wife } $self->famc;
  1110         2085  
81 1433 100       3075 wantarray ? @a : $a[0]
82             }
83              
84             sub parents {
85 1385     1385 0 4117 my $self = shift;
86 1385         2101 ($self->father, $self->mother)
87             }
88              
89             sub husband {
90 1169     1169 0 3633 my $self = shift;
91 708         1659 my @a = grep { $_->{xref} ne $self->{xref} }
92 1169         1665 map { $_->husband } $self->fams;
  768         1476  
93 1169 50       2542 wantarray ? @a : $a[0]
94             }
95              
96             sub wife {
97 1169     1169 0 3688 my $self = shift;
98 756         1724 my @a = grep { $_->{xref} ne $self->{xref} }
99 1169         1583 map { $_->wife } $self->fams;
  768         1307  
100 1169 50       1958 wantarray ? @a : $a[0]
101             }
102              
103             sub spouse {
104 1145     1145 0 3824 my $self = shift;
105 1145         1839 my @a = ($self->husband, $self->wife);
106 1145 50       10063 wantarray ? @a : $a[0]
107             }
108              
109             sub siblings {
110 1193     1193 0 3743 my $self = shift;
111 2148         4177 my @a = grep { $_->{xref} ne $self->{xref} }
112 1193         1733 map { $_->children } $self->famc;
  876         1614  
113 1193 50       2834 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 8033 my $self = shift;
160 24         83 my @a = grep { $_->tag_value("SEX") !~ /^F/i } $self->siblings;
  24         64  
161 24 50       132 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 2369 my $self = shift;
172 24         68 my @a = grep { $_->tag_value("SEX") !~ /^M/i } $self->siblings;
  24         69  
173 24 50       96 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 4226 my $self = shift;
184 1457         2243 my @a = map { $_->children } $self->fams;
  888         1649  
185 1457 50       2777 wantarray ? @a : $a[0]
186             }
187              
188             sub sons {
189 24     24 0 2416 my $self = shift;
190 24         59 my @a = grep { $_->tag_value("SEX") !~ /^F/i } $self->children;
  96         192  
191 24 50       95 wantarray ? @a : $a[0]
192             }
193              
194             sub daughters {
195 24     24 0 2481 my $self = shift;
196 24         64 my @a = grep { $_->tag_value("SEX") !~ /^M/i } $self->children;
  96         209  
197 24 50       99 wantarray ? @a : $a[0]
198             }
199              
200             sub descendents {
201 24     24 0 2485 my $self = shift;
202 24         50 my @d;
203 24         60 my @c = $self->children;
204 24         67 while (@c) {
205 48         98 push @d, @c;
206 48         73 @c = map { $_->children } @c;
  240         337  
207             }
208             @d
209 24         87 }
210              
211             sub ancestors {
212 36     36 0 11202 my $self = shift;
213 36         64 my @d;
214 36         106 my @c = $self->parents;
215 36         113 while (@c) {
216 102         168 push @d, @c;
217 102         145 @c = map { $_->parents } @c;
  204         303  
218             }
219             @d
220 36         207 }
221              
222             sub delete {
223 1     1 1 3 my $self = shift;
224 1         2 my $xref = $self->{xref};
225 1         2 my $ret = 1;
226 1         4 for my $f ([ "(HUSB|WIFE)", [$self->fams] ], [ "CHIL", [$self->famc] ]) {
227 2         11 for my $fam (@{$f->[1]}) {
  2         5  
228             # print "deleting from $fam->{xref}\n";
229 1         2 for my $record (@{$fam->_items}) {
  1         3  
230             # print "looking at $record->{tag} $record->{value}\n";
231 3 100 100     56 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     5 $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         4 $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 5315 my $self = shift;
286 5187         7508 my @a = $self->resolve($self->tag_value("FAMC"));
287 5187 100       8824 wantarray ? @a : $a[0]
288             }
289              
290             sub fams {
291 4917     4917 0 5362 my $self = shift;
292 4917         7619 my @a = $self->resolve($self->tag_value("FAMS"));
293 4917 50       7876 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 69 my $self = shift;
302 12         29 my ($other) = @_;
303              
304 12 100       43 my @ancestors = $self->ancestors() or return;
305              
306 6         28 my $sex = $self->sex;
307 6 50       27 die $self->name, ": unknown sex\n" if $sex eq "U";
308              
309 6         19 for my $person1 (@ancestors) {
310 24 100       64 if ($person1 eq $other) {
311             # Direct ancestor
312 6         28 my $steps = $self->_stepsabove($other, 0);
313 6 50       30 my $title = $sex eq "M" ? "father" : "mother";
314 6 50       54 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         34 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   45 my $self = shift;
410 30         66 my ($target, $count) = @_;
411              
412 30 50       71 return -1 if $count == -1;
413              
414 30 100       86 return $count if $self eq $target;
415              
416 24         43 my $father = $self->father;
417 24 100       66 if ($father) {
418 12         66 my $rc = $father->_stepsabove($target, $count + 1);
419 12 50       45 return $rc unless $rc == -1;
420             }
421              
422 24         47 my $mother = $self->mother;
423 24 100       52 if ($mother) {
424 12         34 return $mother->_stepsabove($target, $count + 1);
425             }
426              
427 12         34 -1
428             }
429              
430             1
431              
432             __END__