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   60 use strict;
  11         16  
  11         463  
11              
12             require 5.005;
13              
14             package Gedcom::Record;
15              
16 11     11   50 use vars qw($VERSION @ISA $AUTOLOAD);
  11         17  
  11         606  
17             $VERSION = "1.22";
18             @ISA = qw( Gedcom::Item );
19              
20 11     11   73 use Carp;
  11         24  
  11         579  
21 11     11   682 BEGIN { eval "use Date::Manip" } # We'll use this if it is available
  11     11   4059  
  11         1325272  
  11         1527  
22              
23 11     11   100 use Gedcom::Item 1.22;
  11         191  
  11         272  
24 11     11   4876 use Gedcom::Comparison 1.22;
  11         184  
  11         369  
25              
26             BEGIN
27             {
28 11     11   4944 use subs keys %Gedcom::Funcs;
  11         222  
  11         992  
29 11     11   22003 *tag_record = \&Gedcom::Item::get_item;
30 11         26 *delete_record = \&Gedcom::Item::delete_item;
31 11         1208 *get_record = \&record;
32             }
33              
34       0     sub DESTROY {}
35              
36             sub AUTOLOAD {
37 26     26   304 my ($self) = @_; # don't change @_ because of the goto
38 26         62 my $func = $AUTOLOAD;
39             # print "autoloading $func\n";
40 26         172 $func =~ s/^.*:://;
41 26 50       145 carp "Undefined subroutine $func called" unless $Gedcom::Funcs{lc $func};
42 11     11   70 no strict "refs";
  11         24  
  11         36324  
43             *$func = sub {
44 3113     3113   41997 my $self = shift;
45 3113         3687 my ($count) = @_;
46 3113         2922 my $v;
47             # print "[[ $func ]]\n";
48 3113 100       3755 if (wantarray) {
49             return map {
50 3106         5481 $_ &&
51 3091 100 66     4504 do { $v = $_->full_value; defined $v && length $v ? $v : $_ }
  3091 50       4730  
  3091         13708  
52             } $self->record([$func, $count]);
53             } else {
54 7         49 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         463 };
59 26         133 goto &$func
60             }
61              
62             sub record {
63 3219     3219 1 3429 my $self = shift;
64 3219         3865 my @records = ($self);
65 3219 100       3793 for my $func (map { ref() ? $_ : split } @_) {
  3314         6369  
66 3322         3760 my $count = 0;
67 3322 100       6593 ($func, $count) = @$func if ref $func eq "ARRAY";
68 3322 50       4501 if (ref $func) {
69 0         0 warn "Invalid record of type ", ref $func, " requested";
70 0         0 return undef;
71             }
72 3322         4390 my $record = $Gedcom::Funcs{lc $func};
73 3322 50       4235 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         3776 @records = map { $_->tag_record($record, $count) } @records;
  3315         5335  
82              
83             # fams and famc need to be resolved
84 3322 50 33     9588 @records = map { $self->resolve($_->{value}) } @records
  0         0  
85             if $record eq "FAMS" || $record eq "FAMC";
86             }
87 3219 100       5825 wantarray ? @records : $records[0]
88             }
89              
90             sub get_value {
91 103     103 1 33873 my $self = shift;
92 103 100       195 if (wantarray) {
93 96 50       173 return map { my $v = $_->full_value; defined $v and length $v ? $v : () }
  82 50       154  
  82         314  
94             $self->record(@_);
95             } else {
96 7         26 my $record = $self->record(@_);
97 7   66     50 return $record && $record->full_value;
98             }
99             }
100              
101             sub tag_value {
102 62489     62489 0 63971 my $self = shift;
103 62489 100       75233 if (wantarray) {
104 52414 50       78226 return map { my $v = $_->full_value; defined $v and length $v ? $v : () }
  53259 50       79965  
  53259         177698  
105             $self->tag_record(@_);
106             } else {
107 10075         15763 my $record = $self->tag_record(@_);
108 10075   33     20744 return $record && $record->full_value;
109             }
110             }
111              
112             sub add_record {
113 80     80 0 118 my $self = shift;
114 80         169 my (%args) = @_;
115              
116 80 50       150 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         171 );
123              
124 80 50       228 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         90 my $grammar = $g[0];
129 80         107 for my $g (@g) {
130             # print "testing $args{tag} ", $args{val} // "undef", " against ",
131             # $g->{value} // "undef", "\n";
132 82 100       123 if ($args{tag} eq "NOTE") {
133 6 100 66     41 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     254 if (( defined $args{val} && $g->{value}) ||
      100        
      100        
141             (!defined $args{val} && !$g->{value})) {
142             # print "match\n";
143 67         69 $grammar = $g;
144 67         86 last;
145             }
146             }
147             }
148 80         136 $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         89 push @{$self->{items}}, $record;
  80         125  
154              
155 80         185 $record
156             }
157              
158             sub add {
159 62     62 1 88 my $self = shift;
160 62         68 my ($xref, $val);
161 62 100 66     203 if (@_ > 1 && ref $_[-1] ne "ARRAY") {
162 59         74 $val = pop;
163 59 100       191 if (UNIVERSAL::isa($val, "Gedcom::Record")) {
164 6         8 $xref = $val;
165 6         9 $val = undef;
166             }
167             }
168              
169 62 100       85 my @funcs = map { ref() ? $_ : split } @_;
  64         193  
170 62 100       187 $funcs[-1] = [$funcs[-1], 0] unless ref $funcs[-1];
171 62         72 push @{$funcs[-1]}, { xref => $xref, val => $val };
  62         163  
172 62         123 my $record = $self->get_and_create(@funcs);
173              
174 62 100       97 if (defined $xref) {
175 6         13 $record->{value} = $xref->{xref};
176 6         10 $self->{gedcom}{xrefs}{$xref->{xref}} = $xref;
177             }
178              
179 62 100       87 if (defined $val) {
180 53         79 $record->{value} = $val;
181             }
182              
183             $record
184 62         207 }
185              
186             sub set {
187 1     1 1 2 my $self = shift;
188 1         2 my $val = pop;
189              
190 1 50       3 my @funcs = map { ref() ? $_ : split } @_;
  1         6  
191 1         4 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 71 my $self = shift;
205 63         83 my @funcs = @_;
206             # use DDS; print "get_and_create: " , Dump \@funcs;
207              
208 63         66 my $rec = $self;
209 63         124 for my $f (0 .. $#funcs) {
210 72         122 my ($func, $count, $args) = ($funcs[$f], 1);
211 72 50       135 $args = {} unless defined $args;
212 72 100       172 ($func, $count, $args) = @$func if ref $func eq "ARRAY";
213 72         80 $count--;
214              
215 72 50       103 if (ref $func) {
216 0         0 warn "Invalid record of type ", ref $func, " requested";
217 0         0 return undef;
218             }
219              
220 72         136 my $record = $Gedcom::Funcs{lc $func};
221 72 50       101 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         138 my @records = $rec->tag_record($record);
232              
233 72 100       111 if ($count < 0) {
    100          
234 61         155 $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         28 for (0 .. @records - $count);
239 7         16 $rec = $new;
240             } else {
241 4         9 $rec = $records[$count];
242             }
243             }
244              
245             $rec
246 63         85 }
247              
248             sub parse {
249             # print "parsing\n";
250 6531     6531 1 6620 my $self = shift;
251 6531         8650 my ($record, $grammar, $test) = @_;
252 6531   50     16393 $test ||= 0;
253              
254             # print "checking "; $record->print();
255             # print "against "; $grammar->print();
256             # print "test is $test\n";
257              
258 6531         7363 my $t = $record->{tag};
259 6531         7049 my $g = $grammar->{tag};
260 6531 50 33     14435 die "Can't match $t with $g" if $t && $t ne $g; # internal error
261              
262 6531         7687 $record->{grammar} = $grammar;
263 6531         8335 my $class = $record->{gedcom}{types}{$t};
264 6531 100       9565 bless $record, "Gedcom::$class" if $class;
265              
266 6531         6787 my $match = 1;
267              
268 6531         6289 for my $r (@{$record->{items}}) {
  6531         9682  
269 5580         6481 my $tag = $r->{tag};
270 5580         5325 my @i;
271             # print "- valid sub-items of $t: @{[keys %{$grammar->valid_items}]}\n";
272 5580         8488 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     8468 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     9277 next if $i->value && ($i->value =~ /^pointer || 0));
      100        
283              
284             # print "pushing\n";
285 5575         9258 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       8372 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       31 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         5714 my $m = 0;
308 5580         6185 for my $i (@i) {
309 5575 50       8127 last if $m = $self->parse($r, $i, @i > 1);
310             }
311              
312 5580 50 33     10392 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         11594 }
327              
328             sub collect_xrefs {
329 21861     21861 1 20970 my $self = shift;
330 21861         22804 my ($callback) = @_;
331 21861 100       32573 $self->{gedcom}{xrefs}{$self->{xref}} = $self if defined $self->{xref};
332 21861         19237 $_->collect_xrefs($callback) for @{$self->{items}};
  21861         35009  
333 21861         31431 $self
334             }
335              
336             sub resolve_xref {
337 5287     5287 1 9602 shift->{gedcom}->resolve_xref(@_);
338             }
339              
340             sub resolve {
341 15632     15632 1 18547 my $self = shift;
342             my @x = map {
343 15632         18029 ref($_)
344             ? $_
345 14083 100       25094 : do { my $x = $self->{gedcom}->resolve_xref($_); defined $x ? $x : () }
  1387 100       2590  
  1387         2729  
346             } @_;
347 15632 100       30659 wantarray ? @x : $x[0]
348             }
349              
350             sub resolve_xrefs {
351 22803     22803 1 23319 my $self = shift;
352 22803         25125 my ($callback) = @_;
353 22803 100       39945 if (my $xref = $self->{gedcom}->resolve_xref($self->{value})) {
354 2572         3044 $self->{value} = $xref;
355             }
356 22803         23647 $_->resolve_xrefs($callback) for @{$self->_items};
  22803         30799  
357 22803         36634 $self
358             }
359              
360             sub unresolve_xrefs {
361 15156     15156 1 15319 my $self = shift;;
362 15156         16406 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     50015 and exists $self->{value}{xref};
      66        
367 15156         14904 $_->unresolve_xrefs($callback) for @{$self->_items};
  15156         19687  
368 15156         23455 $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 71828 my $self = shift;
376 69989 100       109271 return 1 unless exists $self->{grammar};
377 53489         51012 my $ok = 1;
378             $self->{gedcom}{validate_callback}->($self)
379 53489 50       78564 if defined $self->{gedcom}{validate_callback};
380 53489         61905 my $grammar = $self->{grammar};
381 53489         50629 $I++;
382             print " " x $I . "validate_syntax(" .
383 53489 0       65113 (defined $grammar->{tag} ? $grammar->{tag} : "") . ")\n" if $D;
    50          
384 53489         62216 my $file = $self->{gedcom}{record}{file};
385             my $here = "$file:$self->{line}: $self->{tag}" .
386 53489 100       116545 (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     176779 !defined $grammar->{value};
      100        
391 53489         55898 my %counts;
392 53489         50694 for my $record (@{$self->_items}) {
  53489         80648  
393 69928 50       87374 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       129563 if $record->{level} > $self->{level} + 1;
397 69928         113723 $counts{$record->{tag}}++;
398 69928 100       91385 $ok = 0 unless $record->validate_syntax;
399             }
400 53489         83313 my $valid_items = $grammar->valid_items;
401 53489         208679 for my $tag (sort keys %$valid_items) {
402 563458         524061 for my $g (@{$valid_items->{$tag}}) {
  563458         682705  
403 637008         641049 my $min = $g->{min};
404 637008         613118 my $max = $g->{max};
405 637008   100     1122036 my $matches = delete $counts{$tag} || 0;
406 637008 100       1063433 my $msg = "$here has $matches $tag" . ($matches == 1 ? "" : "s");
407 637008 50       783544 print " " x $I . "$msg - min is $min max is $max\n" if $D;
408 637008 50       763980 $ok = 0, warn "$msg - minimum is $min\n" if $matches < $min;
409 637008 100 100     1089483 $ok = 0, warn "$msg - maximum is $max\n" if $matches > $max && $max;
410             }
411             }
412 53489         79935 for my $tag (keys %counts) {
413 60         390 for my $c ($self->tag_record($tag)) {
414 60 50       309 $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         50537 $I--;
422 53489         109030 $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 9505 my $self = shift;
439 8770 100 100     21813 return 1 unless $self->{tag} eq "INDI" || $self->{tag} eq "FAM";
440             # print "validating: "; $self->print; print $self->summary, "\n";
441 8405         8340 my $ok = 1;
442 8405         10938 my $xrefs = $self->{gedcom}{xrefs};
443 8405         10464 my $chk = $Check->{$self->{tag}};
444 8405         13109 for my $f (keys %$chk) {
445 19630         19770 my $found = 1;
446             RECORD:
447 19630         25467 for my $record ($self->tag_value($f)) {
448 15244         16844 $found = 0;
449 15244 100       24044 $record = $xrefs->{$record} unless ref $record;
450 15244 100       20356 if ($record) {
451 15240         14494 for my $back (@{$chk->{$f}}) {
  15240         19912  
452             # print "back $back\n";
453 17160         23152 for my $i ($record->tag_value($back)) {
454             # print "record is $i\n";
455 20400 100       33118 $i = $xrefs->{$i} unless ref $i;
456 20400 100 66     50765 if ($i && $i->{xref} eq $self->{xref}) {
457 15240         15720 $found = 1;
458             # print "found...\n";
459 15240         24207 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         19963 $ok;
477             }
478              
479             sub normalise_dates {
480 7578     7578 1 8423 my $self = shift;
481 7578 50       10141 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     7406 if( eval { Date::Manip->VERSION( 6 ) } &&
  7578         45246  
486 7578         52881 !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     13518 my $format = shift || "%A, %E %B %Y";
492 7578 100 66     25912 if (defined $self->{tag} && $self->{tag} =~ /^date$/i) {
493 906 50 33     3655 if (defined $self->{value} && $self->{value}) {
494             # print "date was $self->{value}\n";
495 906         2271 my @dates = split / or /, $self->{value};
496 906         1305 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       1530 next if $dt =~ /^AFT/;
504              
505             # Don't change the date if it is just < 7 digits.
506 900 100 66     3092 if ($dt !~ /^\s*(\d+)\s*$/ || length $1 > 6) {
507 642         1422 my $date = ParseDate($dt);
508 642         887411 my $d = UnixDate($date, $format);
509 642 100       331509 $dt = $d if $d;
510             }
511             }
512 906         3074 $self->{value} = join " or ", @dates;
513             # print "date is $self->{value}\n";
514             }
515             }
516 7578         7722 $_->normalise_dates($format) for @{$self->_items};
  7578         11964  
517 7578 100       11872 $self->delete_items if $self->level > 1;
518             }
519              
520             sub renumber {
521 13600     13600 1 13679 my $self = shift;
522 13600         14943 my ($args, $recurse) = @_;
523             # TODO - add the xref if there is supposed to be one
524 13600 100 100     29804 return if exists $self->{recursed} or not defined $self->{xref};
525             # we can't actually change the xrefs until the end
526 4496 100       7997 my $x = $self->{tag} eq "SUBM" ? "SUBM" : substr $self->{tag}, 0, 1;
527             $self->{new_xref} = $x . ++$args->{$self->{tag}}
528 4496 100       8010 unless exists $self->{new_xref};
529 4496 100 66     9365 return unless $recurse and not exists $self->{recursed};
530 1736         2170 $self->{recursed} = 1;
531 1736 100       3053 if ($self->{tag} eq "INDI") {
532 1121         1440 my @r = map { $self->$_() }
  6726         12486  
533             qw( fams famc spouse children parents siblings );
534 1121         2533 $_->renumber($args, 0) for @r;
535 1121         2062 $_->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__