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-2013, 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   63 use strict;
  11         18  
  11         406  
11              
12             require 5.005;
13              
14             package Gedcom::Record;
15              
16 11     11   47 use vars qw($VERSION @ISA $AUTOLOAD);
  11         19  
  11         587  
17             $VERSION = "1.20";
18             @ISA = qw( Gedcom::Item );
19              
20 11     11   46 use Carp;
  11         18  
  11         577  
21 11     11   537 BEGIN { eval "use Date::Manip" } # We'll use this if it is available
  11     11   3870  
  11         1180308  
  11         1448  
22              
23 11     11   100 use Gedcom::Item 1.20;
  11         224  
  11         270  
24 11     11   4316 use Gedcom::Comparison 1.20;
  11         129  
  11         342  
25              
26             BEGIN
27             {
28 11     11   4501 use subs keys %Gedcom::Funcs;
  11         204  
  11         775  
29 11     11   19547 *tag_record = \&Gedcom::Item::get_item;
30 11         26 *delete_record = \&Gedcom::Item::delete_item;
31 11         988 *get_record = \&record;
32             }
33              
34       0     sub DESTROY {}
35              
36             sub AUTOLOAD {
37 26     26   298 my ($self) = @_; # don't change @_ because of the goto
38 26         58 my $func = $AUTOLOAD;
39             # print "autoloading $func\n";
40 26         170 $func =~ s/^.*:://;
41 26 50       149 carp "Undefined subroutine $func called" unless $Gedcom::Funcs{lc $func};
42 11     11   72 no strict "refs";
  11         23  
  11         30875  
43             *$func = sub {
44 3113     3113   41631 my $self = shift;
45 3113         4320 my ($count) = @_;
46 3113         3246 my $v;
47             # print "[[ $func ]]\n";
48 3113 100       4122 if (wantarray) {
49             return map {
50 3106         6251 $_ &&
51 3091 100 66     5092 do { $v = $_->full_value; defined $v && length $v ? $v : $_ }
  3091 50       5333  
  3091         15599  
52             } $self->record([$func, $count]);
53             } else {
54 7         46 my $r = $self->record([$func, $count]);
55             return $r &&
56 7   33     38 do { $v = $r->full_value; defined $v && length $v ? $v : $r }
57             }
58 26         1137 };
59 26         127 goto &$func
60             }
61              
62             sub record {
63 3219     3219 1 4047 my $self = shift;
64 3219         4133 my @records = ($self);
65 3219 100       4331 for my $func (map { ref() ? $_ : split } @_) {
  3314         7020  
66 3322         3955 my $count = 0;
67 3322 100       7219 ($func, $count) = @$func if ref $func eq "ARRAY";
68 3322 50       4980 if (ref $func) {
69 0         0 warn "Invalid record of type ", ref $func, " requested";
70 0         0 return undef;
71             }
72 3322         5008 my $record = $Gedcom::Funcs{lc $func};
73 3322 50       4727 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         4183 @records = map { $_->tag_record($record, $count) } @records;
  3315         5902  
82              
83             # fams and famc need to be resolved
84 3322 50 33     10668 @records = map { $self->resolve($_->{value}) } @records
  0         0  
85             if $record eq "FAMS" || $record eq "FAMC";
86             }
87 3219 100       6508 wantarray ? @records : $records[0]
88             }
89              
90             sub get_value {
91 103     103 1 32486 my $self = shift;
92 103 100       213 if (wantarray) {
93 96 50       161 return map { my $v = $_->full_value; defined $v and length $v ? $v : () }
  82 50       159  
  82         331  
94             $self->record(@_);
95             } else {
96 7         28 my $record = $self->record(@_);
97 7   66     48 return $record && $record->full_value;
98             }
99             }
100              
101             sub tag_value {
102 62111     62111 0 68918 my $self = shift;
103 62111 100       85237 if (wantarray) {
104 52042 50       84994 return map { my $v = $_->full_value; defined $v and length $v ? $v : () }
  52995 50       85680  
  52995         191100  
105             $self->tag_record(@_);
106             } else {
107 10069         17813 my $record = $self->tag_record(@_);
108 10069   33     23427 return $record && $record->full_value;
109             }
110             }
111              
112             sub add_record {
113 80     80 0 104 my $self = shift;
114 80         187 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         196 );
123              
124 80 50       247 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         105 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       129 if ($args{tag} eq "NOTE") {
133 6 100 66     46 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         8 $grammar = $g;
137 5         7 last;
138             }
139             } else {
140 76 100 100     280 if (( defined $args{val} && $g->{value}) ||
      100        
      100        
141             (!defined $args{val} && !$g->{value})) {
142             # print "match\n";
143 67         80 $grammar = $g;
144 67         87 last;
145             }
146             }
147             }
148 80         163 $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         82 push @{$self->{items}}, $record;
  80         121  
154              
155 80         202 $record
156             }
157              
158             sub add {
159 62     62 1 96 my $self = shift;
160 62         74 my ($xref, $val);
161 62 100 66     217 if (@_ > 1 && ref $_[-1] ne "ARRAY") {
162 59         80 $val = pop;
163 59 100       207 if (UNIVERSAL::isa($val, "Gedcom::Record")) {
164 6         8 $xref = $val;
165 6         9 $val = undef;
166             }
167             }
168              
169 62 100       100 my @funcs = map { ref() ? $_ : split } @_;
  64         198  
170 62 100       160 $funcs[-1] = [$funcs[-1], 0] unless ref $funcs[-1];
171 62         86 push @{$funcs[-1]}, { xref => $xref, val => $val };
  62         175  
172 62         126 my $record = $self->get_and_create(@funcs);
173              
174 62 100       102 if (defined $xref) {
175 6         13 $record->{value} = $xref->{xref};
176 6         13 $self->{gedcom}{xrefs}{$xref->{xref}} = $xref;
177             }
178              
179 62 100       93 if (defined $val) {
180 53         86 $record->{value} = $val;
181             }
182              
183             $record
184 62         224 }
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         65  
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         2 $r->{value} = $val;
198             }
199              
200 1         4 $r
201             }
202              
203             sub get_and_create {
204 63     63 0 81 my $self = shift;
205 63         87 my @funcs = @_;
206             # use DDS; print "get_and_create: " , Dump \@funcs;
207              
208 63         68 my $rec = $self;
209 63         160 for my $f (0 .. $#funcs) {
210 72         137 my ($func, $count, $args) = ($funcs[$f], 1);
211 72 50       131 $args = {} unless defined $args;
212 72 100       197 ($func, $count, $args) = @$func if ref $func eq "ARRAY";
213 72         87 $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         133 my $record = $Gedcom::Funcs{lc $func};
221 72 50       113 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         177 my @records = $rec->tag_record($record);
232              
233 72 100       137 if ($count < 0) {
    100          
234 61         159 $rec = $rec->add_record(tag => $record, %$args);
235             } elsif ($#records < $count) {
236 7         13 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         10 $rec = $records[$count];
242             }
243             }
244              
245             $rec
246 63         95 }
247              
248             sub parse {
249             # print "parsing\n";
250 6531     6531 1 7445 my $self = shift;
251 6531         9329 my ($record, $grammar, $test) = @_;
252 6531   50     17408 $test ||= 0;
253              
254             # print "checking "; $record->print();
255             # print "against "; $grammar->print();
256             # print "test is $test\n";
257              
258 6531         8266 my $t = $record->{tag};
259 6531         8112 my $g = $grammar->{tag};
260 6531 50 33     15085 die "Can't match $t with $g" if $t && $t ne $g; # internal error
261              
262 6531         8539 $record->{grammar} = $grammar;
263 6531         9154 my $class = $record->{gedcom}{types}{$t};
264 6531 100       10446 bless $record, "Gedcom::$class" if $class;
265              
266 6531         7043 my $match = 1;
267              
268 6531         6816 for my $r (@{$record->{items}}) {
  6531         10441  
269 5580         7202 my $tag = $r->{tag};
270 5580         5848 my @i;
271             # print "- valid sub-items of $t: @{[keys %{$grammar->valid_items}]}\n";
272 5580         9511 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     9339 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     10423 next if $i->value && ($i->value =~ /^pointer || 0));
      100        
283              
284             # print "pushing\n";
285 5575         10184 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       8771 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       33 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         6295 my $m = 0;
308 5580         6991 for my $i (@i) {
309 5575 50       9357 last if $m = $self->parse($r, $i, @i > 1);
310             }
311              
312 5580 50 33     11111 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         12461 }
327              
328             sub collect_xrefs {
329 21861     21861 1 25072 my $self = shift;
330 21861         26571 my ($callback) = @_;
331 21861 100       39107 $self->{gedcom}{xrefs}{$self->{xref}} = $self if defined $self->{xref};
332 21861         23193 $_->collect_xrefs($callback) for @{$self->{items}};
  21861         38591  
333 21861         37019 $self
334             }
335              
336             sub resolve_xref {
337 5287     5287 1 11390 shift->{gedcom}->resolve_xref(@_);
338             }
339              
340             sub resolve {
341 15260     15260 1 20027 my $self = shift;
342             my @x = map {
343 15260         19569 ref($_)
344             ? $_
345 13819 100       25945 : do { my $x = $self->{gedcom}->resolve_xref($_); defined $x ? $x : () }
  1211 100       2716  
  1211         2754  
346             } @_;
347 15260 100       31593 wantarray ? @x : $x[0]
348             }
349              
350             sub resolve_xrefs {
351 22803     22803 1 24817 my $self = shift;
352 22803         26473 my ($callback) = @_;
353 22803 100       41317 if (my $xref = $self->{gedcom}->resolve_xref($self->{value})) {
354 2572         3381 $self->{value} = $xref;
355             }
356 22803         25811 $_->resolve_xrefs($callback) for @{$self->_items};
  22803         33051  
357 22803         38312 $self
358             }
359              
360             sub unresolve_xrefs {
361 15156     15156 1 18103 my $self = shift;;
362 15156         19030 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     58737 and exists $self->{value}{xref};
      66        
367 15156         17843 $_->unresolve_xrefs($callback) for @{$self->_items};
  15156         24114  
368 15156         27897 $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 78966 my $self = shift;
376 69989 100       125601 return 1 unless exists $self->{grammar};
377 53489         55696 my $ok = 1;
378             $self->{gedcom}{validate_callback}->($self)
379 53489 50       82674 if defined $self->{gedcom}{validate_callback};
380 53489         62703 my $grammar = $self->{grammar};
381 53489         54019 $I++;
382             print " " x $I . "validate_syntax(" .
383 53489 0       67337 (defined $grammar->{tag} ? $grammar->{tag} : "") . ")\n" if $D;
    50          
384 53489         66561 my $file = $self->{gedcom}{record}{file};
385             my $here = "$file:$self->{line}: $self->{tag}" .
386 53489 100       120473 (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     182652 !defined $grammar->{value};
      100        
391 53489         60949 my %counts;
392 53489         53897 for my $record (@{$self->_items}) {
  53489         86238  
393 69928 50       96868 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       134510 if $record->{level} > $self->{level} + 1;
397 69928         117010 $counts{$record->{tag}}++;
398 69928 100       101921 $ok = 0 unless $record->validate_syntax;
399             }
400 53489         89398 my $valid_items = $grammar->valid_items;
401 53489         221350 for my $tag (sort keys %$valid_items) {
402 563458         582343 for my $g (@{$valid_items->{$tag}}) {
  563458         763717  
403 637008         717494 my $min = $g->{min};
404 637008         667389 my $max = $g->{max};
405 637008   100     1215518 my $matches = delete $counts{$tag} || 0;
406 637008 100       1153395 my $msg = "$here has $matches $tag" . ($matches == 1 ? "" : "s");
407 637008 50       856034 print " " x $I . "$msg - min is $min max is $max\n" if $D;
408 637008 50       839342 $ok = 0, warn "$msg - minimum is $min\n" if $matches < $min;
409 637008 100 100     1174543 $ok = 0, warn "$msg - maximum is $max\n" if $matches > $max && $max;
410             }
411             }
412 53489         88790 for my $tag (keys %counts) {
413 60         367 for my $c ($self->tag_record($tag)) {
414 60 50       295 $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         54975 $I--;
422 53489         113235 $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 10222 my $self = shift;
439 8770 100 100     22487 return 1 unless $self->{tag} eq "INDI" || $self->{tag} eq "FAM";
440             # print "validating: "; $self->print; print $self->summary, "\n";
441 8405         9222 my $ok = 1;
442 8405         11293 my $xrefs = $self->{gedcom}{xrefs};
443 8405         11217 my $chk = $Check->{$self->{tag}};
444 8405         14054 for my $f (keys %$chk) {
445 19630         21209 my $found = 1;
446             RECORD:
447 19630         27838 for my $record ($self->tag_value($f)) {
448 15244         18028 $found = 0;
449 15244 100       26565 $record = $xrefs->{$record} unless ref $record;
450 15244 100       22645 if ($record) {
451 15240         16169 for my $back (@{$chk->{$f}}) {
  15240         21877  
452             # print "back $back\n";
453 17160         24222 for my $i ($record->tag_value($back)) {
454             # print "record is $i\n";
455 20400 100       36833 $i = $xrefs->{$i} unless ref $i;
456 20400 100 66     54783 if ($i && $i->{xref} eq $self->{xref}) {
457 15240         17291 $found = 1;
458             # print "found...\n";
459 15240         26237 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         20983 $ok;
477             }
478              
479             sub normalise_dates {
480 7578     7578 1 9366 my $self = shift;
481 7578 50       11343 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     8289 if( eval { Date::Manip->VERSION( 6 ) } &&
  7578         48573  
486 7578         56789 !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     14780 my $format = shift || "%A, %E %B %Y";
492 7578 100 66     26470 if (defined $self->{tag} && $self->{tag} =~ /^date$/i) {
493 906 50 33     2639 if (defined $self->{value} && $self->{value}) {
494             # print "date was $self->{value}\n";
495 906         2340 my @dates = split / or /, $self->{value};
496 906         1487 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       1664 next if $dt =~ /^AFT/;
504              
505             # Don't change the date if it is just < 7 digits.
506 900 100 66     3299 if ($dt !~ /^\s*(\d+)\s*$/ || length $1 > 6) {
507 642         1543 my $date = ParseDate($dt);
508 642         912972 my $d = UnixDate($date, $format);
509 642 100       356917 $dt = $d if $d;
510             }
511             }
512 906         2260 $self->{value} = join " or ", @dates;
513             # print "date is $self->{value}\n";
514             }
515             }
516 7578         8600 $_->normalise_dates($format) for @{$self->_items};
  7578         12874  
517 7578 100       13052 $self->delete_items if $self->level > 1;
518             }
519              
520             sub renumber {
521 13600     13600 1 15463 my $self = shift;
522 13600         16299 my ($args, $recurse) = @_;
523             # TODO - add the xref if there is supposed to be one
524 13600 100 100     33061 return if exists $self->{recursed} or not defined $self->{xref};
525             # we can't actually change the xrefs until the end
526 4496 100       8880 my $x = $self->{tag} eq "SUBM" ? "SUBM" : substr $self->{tag}, 0, 1;
527             $self->{new_xref} = $x . ++$args->{$self->{tag}}
528 4496 100       9084 unless exists $self->{new_xref};
529 4496 100 66     10406 return unless $recurse and not exists $self->{recursed};
530 1736         2463 $self->{recursed} = 1;
531 1736 100       3380 if ($self->{tag} eq "INDI") {
532 1121         1504 my @r = map { $self->$_() }
  6726         13998  
533             qw( fams famc spouse children parents siblings );
534 1121         2706 $_->renumber($args, 0) for @r;
535 1121         2191 $_->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__