File Coverage

blib/lib/Gedcom/Record.pm
Criterion Covered Total %
statement 269 317 84.8
branch 129 182 70.8
condition 68 98 69.3
subroutine 29 34 85.2
pod 16 21 76.1
total 511 652 78.3


line stmt bran cond sub pod time code
1             # Copyright 1998-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   58 use strict;
  11         18  
  11         418  
11              
12             require 5.005;
13              
14             package Gedcom::Record;
15              
16 11     11   48 use vars qw($VERSION @ISA $AUTOLOAD);
  11         12  
  11         658  
17             $VERSION = "1.21";
18             @ISA = qw( Gedcom::Item );
19              
20 11     11   58 use Carp;
  11         22  
  11         592  
21 11     11   666 BEGIN { eval "use Date::Manip" } # We'll use this if it is available
  11     11   4113  
  11         1312154  
  11         1458  
22              
23 11     11   92 use Gedcom::Item 1.21;
  11         179  
  11         265  
24 11     11   6479 use Gedcom::Comparison 1.21;
  11         133  
  11         362  
25              
26             BEGIN
27             {
28 11     11   4835 use subs keys %Gedcom::Funcs;
  11         222  
  11         894  
29 11     11   20978 *tag_record = \&Gedcom::Item::get_item;
30 11         27 *delete_record = \&Gedcom::Item::delete_item;
31 11         1174 *get_record = \&record;
32             }
33              
34       0     sub DESTROY {}
35              
36             sub AUTOLOAD {
37 26     26   254 my ($self) = @_; # don't change @_ because of the goto
38 26         49 my $func = $AUTOLOAD;
39             # print "autoloading $func\n";
40 26         153 $func =~ s/^.*:://;
41 26 50       128 carp "Undefined subroutine $func called" unless $Gedcom::Funcs{lc $func};
42 11     11   71 no strict "refs";
  11         26  
  11         35533  
43             *$func = sub {
44 3113     3113   41373 my $self = shift;
45 3113         3639 my ($count) = @_;
46 3113         2999 my $v;
47             # print "[[ $func ]]\n";
48 3113 100       3751 if (wantarray) {
49             return map {
50 3106         5722 $_ &&
51 3091 100 66     4390 do { $v = $_->full_value; defined $v && length $v ? $v : $_ }
  3091 50       7295  
  3091         14748  
52             } $self->record([$func, $count]);
53             } else {
54 7         41 my $r = $self->record([$func, $count]);
55             return $r &&
56 7   33     36 do { $v = $r->full_value; defined $v && length $v ? $v : $r }
57             }
58 26         390 };
59 26         117 goto &$func
60             }
61              
62             sub record {
63 3219     3219 1 4629 my $self = shift;
64 3219         3687 my @records = ($self);
65 3219 100       3878 for my $func (map { ref() ? $_ : split } @_) {
  3314         6414  
66 3322         3518 my $count = 0;
67 3322 100       6494 ($func, $count) = @$func if ref $func eq "ARRAY";
68 3322 50       5659 if (ref $func) {
69 0         0 warn "Invalid record of type ", ref $func, " requested";
70 0         0 return undef;
71             }
72 3322         4404 my $record = $Gedcom::Funcs{lc $func};
73 3322 50       4278 unless ($record) {
74 0 0       0 warn $func
    0          
75             ? "Non standard record of type $func requested"
76             : "Record type not specified"
77             unless $func =~ /^_/;
78 0         0 $record = $func;
79             }
80              
81 3322         4922 @records = map { $_->tag_record($record, $count) } @records;
  3315         5333  
82              
83             # fams and famc need to be resolved
84 3322 50 33     9665 @records = map { $self->resolve($_->{value}) } @records
  0         0  
85             if $record eq "FAMS" || $record eq "FAMC";
86             }
87 3219 100       5759 wantarray ? @records : $records[0]
88             }
89              
90             sub get_value {
91 103     103 1 33404 my $self = shift;
92 103 100       213 if (wantarray) {
93 96 50       183 return map { my $v = $_->full_value; defined $v and length $v ? $v : () }
  82 50       154  
  82         339  
94             $self->record(@_);
95             } else {
96 7         29 my $record = $self->record(@_);
97 7   66     38 return $record && $record->full_value;
98             }
99             }
100              
101             sub tag_value {
102 62489     62489 0 63467 my $self = shift;
103 62489 100       73337 if (wantarray) {
104 52414 50       80867 return map { my $v = $_->full_value; defined $v and length $v ? $v : () }
  53259 50       81953  
  53259         179447  
105             $self->tag_record(@_);
106             } else {
107 10075         15343 my $record = $self->tag_record(@_);
108 10075   33     20304 return $record && $record->full_value;
109             }
110             }
111              
112             sub add_record {
113 80     80 0 90 my $self = shift;
114 80         173 my (%args) = @_;
115              
116 80 50       145 die "No tag specified" unless defined $args{tag};
117              
118             my $record = Gedcom::Record->new(
119             gedcom => $self->{gedcom},
120             callback => $self->{callback},
121             tag => $args{tag},
122 80         178 );
123              
124 80 50       215 if (!defined $self->{grammar}) {
    50          
125 0         0 warn "$self->{tag} has no grammar\n";
126             } elsif (my @g = $self->{grammar}->item($args{tag})) {
127             # use DDS; print Dump \@g;
128 80         168 my $grammar = $g[0];
129 80         114 for my $g (@g) {
130             # print "testing $args{tag} ", $args{val} // "undef", " against ",
131             # $g->{value} // "undef", "\n";
132 82 100       124 if ($args{tag} eq "NOTE") {
133 6 100 66     44 if (( defined $args{xref} && $g->{value} =~ /xref/i) ||
      66        
      100        
134             (!defined $args{xref} && $g->{value} !~ /xref/i)) {
135             # print "note match\n";
136 5         6 $grammar = $g;
137 5         9 last;
138             }
139             } else {
140 76 100 100     260 if (( defined $args{val} && $g->{value}) ||
      100        
      100        
141             (!defined $args{val} && !$g->{value})) {
142             # print "match\n";
143 67         75 $grammar = $g;
144 67         88 last;
145             }
146             }
147             }
148 80         158 $self->parse($record, $grammar);
149             } else {
150 0         0 warn "$args{tag} is not a sub-item of $self->{tag}\n";
151             }
152              
153 80         85 push @{$self->{items}}, $record;
  80         109  
154              
155 80         197 $record
156             }
157              
158             sub add {
159 62     62 1 92 my $self = shift;
160 62         81 my ($xref, $val);
161 62 100 66     189 if (@_ > 1 && ref $_[-1] ne "ARRAY") {
162 59         78 $val = pop;
163 59 100       199 if (UNIVERSAL::isa($val, "Gedcom::Record")) {
164 6         6 $xref = $val;
165 6         7 $val = undef;
166             }
167             }
168              
169 62 100       95 my @funcs = map { ref() ? $_ : split } @_;
  64         188  
170 62 100       174 $funcs[-1] = [$funcs[-1], 0] unless ref $funcs[-1];
171 62         74 push @{$funcs[-1]}, { xref => $xref, val => $val };
  62         162  
172 62         124 my $record = $self->get_and_create(@funcs);
173              
174 62 100       93 if (defined $xref) {
175 6         11 $record->{value} = $xref->{xref};
176 6         12 $self->{gedcom}{xrefs}{$xref->{xref}} = $xref;
177             }
178              
179 62 100       87 if (defined $val) {
180 53         80 $record->{value} = $val;
181             }
182              
183             $record
184 62         216 }
185              
186             sub set {
187 1     1 1 3 my $self = shift;
188 1         2 my $val = pop;
189              
190 1 50       3 my @funcs = map { ref() ? $_ : split } @_;
  1         6  
191 1         3 my $r = $self->get_and_create(@funcs);
192              
193 1 50       6 if (UNIVERSAL::isa($val, "Gedcom::Record")) {
194 0         0 $r->{value} = $val->{xref};
195 0         0 $self->{gedcom}{xrefs}{$val->{xref}} = $val;
196             } else {
197 1         3 $r->{value} = $val;
198             }
199              
200 1         13 $r
201             }
202              
203             sub get_and_create {
204 63     63 0 69 my $self = shift;
205 63         84 my @funcs = @_;
206             # use DDS; print "get_and_create: " , Dump \@funcs;
207              
208 63         68 my $rec = $self;
209 63         113 for my $f (0 .. $#funcs) {
210 72         120 my ($func, $count, $args) = ($funcs[$f], 1);
211 72 50       119 $args = {} unless defined $args;
212 72 100       193 ($func, $count, $args) = @$func if ref $func eq "ARRAY";
213 72         77 $count--;
214              
215 72 50       113 if (ref $func) {
216 0         0 warn "Invalid record of type ", ref $func, " requested";
217 0         0 return undef;
218             }
219              
220 72         130 my $record = $Gedcom::Funcs{lc $func};
221 72 50       99 unless ($record) {
222 0 0       0 warn $func
    0          
223             ? "Non standard record of type $func requested"
224             : "Record type not specified"
225             unless $func =~ /^_/;
226 0         0 $record = $func;
227             }
228              
229             # print "$func [$count] - $record\n";
230              
231 72         163 my @records = $rec->tag_record($record);
232              
233 72 100       116 if ($count < 0) {
    100          
234 61         161 $rec = $rec->add_record(tag => $record, %$args);
235             } elsif ($#records < $count) {
236 7         77 my $new;
237             $new = $rec->add_record(tag => $record, %$args)
238 7         56 for (0 .. @records - $count);
239 7         15 $rec = $new;
240             } else {
241 4         9 $rec = $records[$count];
242             }
243             }
244              
245             $rec
246 63         82 }
247              
248             sub parse {
249             # print "parsing\n";
250 6531     6531 1 6640 my $self = shift;
251 6531         8471 my ($record, $grammar, $test) = @_;
252 6531   50     15996 $test ||= 0;
253              
254             # print "checking "; $record->print();
255             # print "against "; $grammar->print();
256             # print "test is $test\n";
257              
258 6531         7697 my $t = $record->{tag};
259 6531         6982 my $g = $grammar->{tag};
260 6531 50 33     13842 die "Can't match $t with $g" if $t && $t ne $g; # internal error
261              
262 6531         7819 $record->{grammar} = $grammar;
263 6531         8297 my $class = $record->{gedcom}{types}{$t};
264 6531 100       9630 bless $record, "Gedcom::$class" if $class;
265              
266 6531         6309 my $match = 1;
267              
268 6531         6233 for my $r (@{$record->{items}}) {
  6531         9593  
269 5580         6627 my $tag = $r->{tag};
270 5580         5233 my @i;
271             # print "- valid sub-items of $t: @{[keys %{$grammar->valid_items}]}\n";
272 5580         8448 for my $i ($grammar->item($tag)) {
273             # Try to get rid of matches we don't want because they only match
274             # in name.
275              
276             # Check that the level is appropriate.
277             # print " - ", $i->level, "|", $r->level, "\n";
278 5590 50 33     8811 next unless $i->level =~ /^[+0]/ || $i->level == $r->level;
279              
280             # Check we have a pointer iff we need one.
281             # print " + ", $i->value, "|", $r->value, "|", $r->pointer, "\n";
282 5590 100 100     9258 next if $i->value && ($i->value =~ /^pointer || 0));
      100        
283              
284             # print "pushing\n";
285 5575         9184 push @i, $i;
286             }
287              
288             # print "valid sub-items of $t: @{[keys %{$grammar->valid_items}]}\n";
289             # print "<$tag> => <@i>\n";
290              
291 5580 100       8122 unless (@i) {
292             # unless $tag eq "CONT" || $tag eq "CONC"
293             # || substr($tag, 0, 1) eq "_";
294             # TODO - should CONT and CONC be allowed anywhere?
295 5 50       26 unless (substr($tag, 0, 1) eq "_") {
296             warn "$self->{file}:$r->{line}: $tag is not a sub-item of $t\n",
297             "Valid sub-items are ",
298 0 0       0 join(", ", sort keys %{$grammar->{_valid_items}}), "\n"
  0         0  
299             unless $test;
300 0         0 $match = 0;
301 0         0 next;
302             }
303             }
304              
305             # print "$self->{file}:$r->{line}: Ambiguous tag $tag as sub-item of $t, ",
306             # "found ", scalar @i, " matches\n" if @i > 1;
307 5580         5815 my $m = 0;
308 5580         6186 for my $i (@i) {
309 5575 50       8076 last if $m = $self->parse($r, $i, @i > 1);
310             }
311              
312 5580 50 33     10362 if (@i > 1 && !$m) {
313             # TODO - I'm not even sure if this can happen.
314 0         0 warn "$self->{file}:$r->{line}:" ,
315             "Ambiguous tag $tag as sub-item of $t, ",
316             "found ", scalar @i, " matches, all of which have errors. ",
317             "Reporting errors from last match.\n";
318 0         0 $self->parse($r, $i[-1]);
319 0         0 $match = 0;
320             # TODO - count the errors in each match and use the best.
321             }
322             }
323             # print "parsed $match\n";
324              
325             $match
326 6531         11518 }
327              
328             sub collect_xrefs {
329 21861     21861 1 20917 my $self = shift;
330 21861         22630 my ($callback) = @_;
331 21861 100       34266 $self->{gedcom}{xrefs}{$self->{xref}} = $self if defined $self->{xref};
332 21861         20214 $_->collect_xrefs($callback) for @{$self->{items}};
  21861         35778  
333 21861         32366 $self
334             }
335              
336             sub resolve_xref {
337 5287     5287 1 9444 shift->{gedcom}->resolve_xref(@_);
338             }
339              
340             sub resolve {
341 15632     15632 1 19560 my $self = shift;
342             my @x = map {
343 15632         18251 ref($_)
344             ? $_
345 14083 100       25297 : do { my $x = $self->{gedcom}->resolve_xref($_); defined $x ? $x : () }
  1387 100       2498  
  1387         2569  
346             } @_;
347 15632 100       30620 wantarray ? @x : $x[0]
348             }
349              
350             sub resolve_xrefs {
351 22803     22803 1 24511 my $self = shift;
352 22803         26734 my ($callback) = @_;
353 22803 100       45285 if (my $xref = $self->{gedcom}->resolve_xref($self->{value})) {
354 2572         3014 $self->{value} = $xref;
355             }
356 22803         25041 $_->resolve_xrefs($callback) for @{$self->_items};
  22803         38941  
357 22803         42237 $self
358             }
359              
360             sub unresolve_xrefs {
361 15156     15156 1 15024 my $self = shift;;
362 15156         16305 my ($callback) = @_;
363             $self->{value} = $self->{value}{xref}
364             if defined $self->{value}
365             and UNIVERSAL::isa $self->{value}, "Gedcom::Record"
366 15156 50 100     49910 and exists $self->{value}{xref};
      66        
367 15156         14502 $_->unresolve_xrefs($callback) for @{$self->_items};
  15156         19435  
368 15156         22935 $self
369             }
370              
371             my $D = 0; # turn on debug output
372             my $I = -1; # indent for debug output
373              
374             sub validate_syntax {
375 69989     69989 0 71866 my $self = shift;
376 69989 100       110122 return 1 unless exists $self->{grammar};
377 53489         51076 my $ok = 1;
378             $self->{gedcom}{validate_callback}->($self)
379 53489 50       103910 if defined $self->{gedcom}{validate_callback};
380 53489         61943 my $grammar = $self->{grammar};
381 53489         49103 $I++;
382             print " " x $I . "validate_syntax(" .
383 53489 0       63007 (defined $grammar->{tag} ? $grammar->{tag} : "") . ")\n" if $D;
    50          
384 53489         65581 my $file = $self->{gedcom}{record}{file};
385             my $here = "$file:$self->{line}: $self->{tag}" .
386 53489 100       115732 (defined $self->{xref} ? " $self->{xref}" : "");
387             # print "$self->{line}: "; $self->print;
388             $ok = 0, warn "$here: $self->{tag} Can't contain a value ($self->{value})\n"
389             if defined $self->{value} && length $self->{value} &&
390 53489 100 100     176024 !defined $grammar->{value};
      100        
391 53489         56555 my %counts;
392 53489         50498 for my $record (@{$self->_items}) {
  53489         82184  
393 69928 50       89333 print " " x $I . "level $record->{level} on $self->{level}\n" if $D;
394             $ok = 0,
395             warn "$here: Can't add level $record->{level} to $self->{level}\n"
396 69928 50       126770 if $record->{level} > $self->{level} + 1;
397 69928         113814 $counts{$record->{tag}}++;
398 69928 100       94643 $ok = 0 unless $record->validate_syntax;
399             }
400 53489         86972 my $valid_items = $grammar->valid_items;
401 53489         212507 for my $tag (sort keys %$valid_items) {
402 563458         544313 for my $g (@{$valid_items->{$tag}}) {
  563458         702825  
403 637008         653679 my $min = $g->{min};
404 637008         639197 my $max = $g->{max};
405 637008   100     1130659 my $matches = delete $counts{$tag} || 0;
406 637008 100       1076357 my $msg = "$here has $matches $tag" . ($matches == 1 ? "" : "s");
407 637008 50       821449 print " " x $I . "$msg - min is $min max is $max\n" if $D;
408 637008 50       777558 $ok = 0, warn "$msg - minimum is $min\n" if $matches < $min;
409 637008 100 100     1102814 $ok = 0, warn "$msg - maximum is $max\n" if $matches > $max && $max;
410             }
411             }
412 53489         81106 for my $tag (keys %counts) {
413 60         335 for my $c ($self->tag_record($tag)) {
414 60 50       261 $ok = 0,
415             warn "$file:$c->{line}: $tag is not a sub-item of $self->{tag}\n"
416             unless substr($tag, 0, 1) eq "_";
417             # unless $tag eq "CONT" || $tag eq "CONC" || substr($tag, 0, 1) eq "_";
418             # TODO - should CONT and CONC be allowed anywhere?
419             }
420             }
421 53489         50773 $I--;
422 53489         106439 $ok;
423             }
424              
425             my $Check = {
426             INDI => {
427             FAMS => [ "HUSB", "WIFE" ],
428             FAMC => [ "CHIL" ]
429             },
430             FAM => {
431             HUSB => [ "FAMS" ],
432             WIFE => [ "FAMS" ],
433             CHIL => [ "FAMC" ],
434             },
435             };
436              
437             sub validate_semantics {
438 8770     8770 1 9317 my $self = shift;
439 8770 100 100     21112 return 1 unless $self->{tag} eq "INDI" || $self->{tag} eq "FAM";
440             # print "validating: "; $self->print; print $self->summary, "\n";
441 8405         8384 my $ok = 1;
442 8405         10818 my $xrefs = $self->{gedcom}{xrefs};
443 8405         11645 my $chk = $Check->{$self->{tag}};
444 8405         14148 for my $f (keys %$chk) {
445 19630         21678 my $found = 1;
446             RECORD:
447 19630         27126 for my $record ($self->tag_value($f)) {
448 15244         16736 $found = 0;
449 15244 100       24579 $record = $xrefs->{$record} unless ref $record;
450 15244 100       21886 if ($record) {
451 15240         15270 for my $back (@{$chk->{$f}}) {
  15240         21303  
452             # print "back $back\n";
453 17160         23795 for my $i ($record->tag_value($back)) {
454             # print "record is $i\n";
455 20400 100       33909 $i = $xrefs->{$i} unless ref $i;
456 20400 100 66     51264 if ($i && $i->{xref} eq $self->{xref}) {
457 15240         15626 $found = 1;
458             # print "found...\n";
459 15240         24071 next RECORD;
460             }
461             }
462             }
463 0 0       0 unless ($found) {
464             # TODO - use the line of the offending record
465 0         0 $ok = 0;
466 0         0 my $file = $self->{gedcom}{record}{file};
467             warn "$file:$self->{line}: $f $record->{xref} " .
468             "does not reference $self->{tag} $self->{xref}. " .
469             "Add the line:\n" .
470             "$file:" . ($record->{line} + 1) . ": 1 " .
471 0         0 join("or ", @{$chk->{$f}}) . " $self->{xref}\n";
  0         0  
472             }
473             }
474             }
475             }
476 8405         19587 $ok;
477             }
478              
479             sub normalise_dates {
480 7578     7578 1 8877 my $self = shift;
481 7578 50       10680 unless ($INC{"Date/Manip.pm"}) {
482 0         0 warn "Date::Manip.pm is required to use normalise_dates()";
483 0         0 return;
484             }
485 7578 50 33     18340 if( eval { Date::Manip->VERSION( 6 ) } &&
  7578         45993  
486 7578         54286 !eval { Date::Manip->VERSION( 6.13 ) } ) {
487 0         0 warn "Unable to normalize dates with this version of Date::Manip. " .
488             "Please upgrade to version 6.13.";
489             return
490 0         0 }
491 7578   100     14136 my $format = shift || "%A, %E %B %Y";
492 7578 100 66     25036 if (defined $self->{tag} && $self->{tag} =~ /^date$/i) {
493 906 50 33     3712 if (defined $self->{value} && $self->{value}) {
494             # print "date was $self->{value}\n";
495 906         2249 my @dates = split / or /, $self->{value};
496 906         1346 for my $dt (@dates) {
497             # Don't change the date if it looks like 'AFT 1989'.
498             # AFT means AFTER and ParseDate returns the current date and the tests
499             # are failing.
500             # Current date can symbolize such an "after" date, but can also
501             # symbolize a very specific point in time and that could also confuse
502             # the user.
503 906 100       1656 next if $dt =~ /^AFT/;
504              
505             # Don't change the date if it is just < 7 digits.
506 900 100 66     3242 if ($dt !~ /^\s*(\d+)\s*$/ || length $1 > 6) {
507 642         1516 my $date = ParseDate($dt);
508 642         947602 my $d = UnixDate($date, $format);
509 642 100       340920 $dt = $d if $d;
510             }
511             }
512 906         2133 $self->{value} = join " or ", @dates;
513             # print "date is $self->{value}\n";
514             }
515             }
516 7578         8423 $_->normalise_dates($format) for @{$self->_items};
  7578         12666  
517 7578 100       12839 $self->delete_items if $self->level > 1;
518             }
519              
520             sub renumber {
521 13600     13600 1 13840 my $self = shift;
522 13600         14701 my ($args, $recurse) = @_;
523             # TODO - add the xref if there is supposed to be one
524 13600 100 100     31585 return if exists $self->{recursed} or not defined $self->{xref};
525             # we can't actually change the xrefs until the end
526 4496 100       8169 my $x = $self->{tag} eq "SUBM" ? "SUBM" : substr $self->{tag}, 0, 1;
527             $self->{new_xref} = $x . ++$args->{$self->{tag}}
528 4496 100       8222 unless exists $self->{new_xref};
529 4496 100 66     10007 return unless $recurse and not exists $self->{recursed};
530 1736         2247 $self->{recursed} = 1;
531 1736 100       4444 if ($self->{tag} eq "INDI") {
532 1121         1470 my @r = map { $self->$_() }
  6726         14161  
533             qw( fams famc spouse children parents siblings );
534 1121         2569 $_->renumber($args, 0) for @r;
535 1121         2043 $_->renumber($args, 1) for @r;
536             }
537             }
538              
539             sub child_value {
540             # NOTE - This function is deprecated - use tag_value instead
541 0     0 1   my $self = shift;
542 0           $self->tag_value(@_)
543             }
544              
545             sub child_values {
546             # NOTE - This function is deprecated - use tag_value instead
547 0     0 1   my $self = shift;
548 0           $self->tag_value(@_)
549             }
550              
551             sub compare {
552 0     0 0   my $self = shift;
553 0           my ($r) = @_;
554 0           Gedcom::Comparison->new($self, $r)
555             }
556              
557             sub summary {
558 0     0 1   my $self = shift;
559 0           my $s = "";
560 0           $s .= sprintf "%-5s", $self->{xref};
561 0           my $r = $self->tag_record("NAME");
562 0 0         $s .= sprintf " %-40s", $r ? $r->{value} : "";
563 0           $r = $self->tag_record("SEX");
564 0 0         $s .= sprintf " %1s", $r ? $r->{value} : "";
565 0           my $d = "";
566 0 0 0       if ($r = $self->tag_record("BIRT") and my $date = $r->tag_record("DATE")) {
567 0           $d = $date->{value};
568             }
569 0           $s .= sprintf " %16s", $d;
570 0           $s;
571             }
572              
573             1;
574              
575             __END__