File Coverage

blib/lib/Gedcom/Record.pm
Criterion Covered Total %
statement 269 317 84.8
branch 129 182 70.8
condition 67 98 68.3
subroutine 29 34 85.2
pod 16 21 76.1
total 510 652 78.2


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   39 use strict;
  11         15  
  11         428  
11              
12             require 5.005;
13              
14             package Gedcom::Record;
15              
16 11     11   44 use vars qw($VERSION @ISA $AUTOLOAD);
  11         12  
  11         679  
17             $VERSION = "1.20";
18             @ISA = qw( Gedcom::Item );
19              
20 11     11   47 use Carp;
  11         11  
  11         673  
21 11     11   591 BEGIN { eval "use Date::Manip" } # We'll use this if it is available
  11     11   4500  
  11         1041064  
  11         1516  
22              
23 11     11   84 use Gedcom::Item 1.20;
  11         187  
  11         227  
24 11     11   4349 use Gedcom::Comparison 1.20;
  11         139  
  11         356  
25              
26             BEGIN
27             {
28 11     11   4940 use subs keys %Gedcom::Funcs;
  11         191  
  11         778  
29 11     11   13711 *tag_record = \&Gedcom::Item::get_item;
30 11         18 *delete_record = \&Gedcom::Item::delete_item;
31 11         941 *get_record = \&record;
32             }
33              
34       0     sub DESTROY {}
35              
36             sub AUTOLOAD {
37 26     26   224 my ($self) = @_; # don't change @_ because of the goto
38 26         39 my $func = $AUTOLOAD;
39             # print "autoloading $func\n";
40 26         150 $func =~ s/^.*:://;
41 26 50       111 carp "Undefined subroutine $func called" unless $Gedcom::Funcs{lc $func};
42 11     11   48 no strict "refs";
  11         14  
  11         30055  
43             *$func = sub {
44 3113     3113   33780 my $self = shift;
45 3113         2390 my ($count) = @_;
46 3113         1785 my $v;
47             # print "[[ $func ]]\n";
48 3113 100       3049 if (wantarray) {
49             return map {
50 3106         4942 $_ &&
51 3091 100 66     4102 do { $v = $_->full_value; defined $v && length $v ? $v : $_ }
  3091 50       4376  
  3091         12686  
52             } $self->record([$func, $count]);
53             } else {
54 7         36 my $r = $self->record([$func, $count]);
55             return $r &&
56 7   33     52 do { $v = $r->full_value; defined $v && length $v ? $v : $r }
57             }
58 26         325 };
59 26         82 goto &$func
60             }
61              
62             sub record {
63 3219     3219 1 2047 my $self = shift;
64 3219         2843 my @records = ($self);
65 3219 100       3020 for my $func (map { ref() ? $_ : split } @_) {
  3314         5622  
66 3322         2261 my $count = 0;
67 3322 100       6309 ($func, $count) = @$func if ref $func eq "ARRAY";
68 3322 50       4101 if (ref $func) {
69 0         0 warn "Invalid record of type ", ref $func, " requested";
70 0         0 return undef;
71             }
72 3322         3399 my $record = $Gedcom::Funcs{lc $func};
73 3322 50       3812 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         2575 @records = map { $_->tag_record($record, $count) } @records;
  3315         4801  
82              
83             # fams and famc need to be resolved
84 3322 50 33     10569 @records = map { $self->resolve($_->{value}) } @records
  0         0  
85             if $record eq "FAMS" || $record eq "FAMC";
86             }
87 3219 100       4789 wantarray ? @records : $records[0]
88             }
89              
90             sub get_value {
91 103     103 1 28301 my $self = shift;
92 103 100       217 if (wantarray) {
93 96 50       154 return map { my $v = $_->full_value; defined $v and length $v ? $v : () }
  82 50       147  
  82         289  
94             $self->record(@_);
95             } else {
96 7         23 my $record = $self->record(@_);
97 7   66     41 return $record && $record->full_value;
98             }
99             }
100              
101             sub tag_value {
102 62111     62111 0 40328 my $self = shift;
103 62111 100       58179 if (wantarray) {
104 52042 50       72246 return map { my $v = $_->full_value; defined $v and length $v ? $v : () }
  52995 50       67613  
  52995         136438  
105             $self->tag_record(@_);
106             } else {
107 10069         13201 my $record = $self->tag_record(@_);
108 10069   33     20431 return $record && $record->full_value;
109             }
110             }
111              
112             sub add_record {
113 80     80 0 67 my $self = shift;
114 80         155 my (%args) = @_;
115              
116 80 50       135 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         181 );
123              
124 80 50       232 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         52 my $grammar = $g[0];
129 80         85 for my $g (@g) {
130             # print "testing $args{tag} ", $args{val} // "undef", " against ",
131             # $g->{value} // "undef", "\n";
132 82 100       107 if ($args{tag} eq "NOTE") {
133 6 100 66     53 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         3 $grammar = $g;
137 5         5 last;
138             }
139             } else {
140 76 100 66     280 if (( defined $args{val} && $g->{value}) ||
      100        
      100        
141             (!defined $args{val} && !$g->{value})) {
142             # print "match\n";
143 67         45 $grammar = $g;
144 67         66 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         56 push @{$self->{items}}, $record;
  80         112  
154              
155 80         191 $record
156             }
157              
158             sub add {
159 62     62 1 59 my $self = shift;
160 62         54 my ($xref, $val);
161 62 100 66     227 if (@_ > 1 && ref $_[-1] ne "ARRAY") {
162 59         52 $val = pop;
163 59 100       193 if (UNIVERSAL::isa($val, "Gedcom::Record")) {
164 6         6 $xref = $val;
165 6         7 $val = undef;
166             }
167             }
168              
169 62 100       69 my @funcs = map { ref() ? $_ : split } @_;
  64         189  
170 62 100       152 $funcs[-1] = [$funcs[-1], 0] unless ref $funcs[-1];
171 62         48 push @{$funcs[-1]}, { xref => $xref, val => $val };
  62         186  
172 62         113 my $record = $self->get_and_create(@funcs);
173              
174 62 100       91 if (defined $xref) {
175 6         10 $record->{value} = $xref->{xref};
176 6         10 $self->{gedcom}{xrefs}{$xref->{xref}} = $xref;
177             }
178              
179 62 100       83 if (defined $val) {
180 53         64 $record->{value} = $val;
181             }
182              
183             $record
184 62         231 }
185              
186             sub set {
187 1     1 1 3 my $self = shift;
188 1         1 my $val = pop;
189              
190 1 50       2 my @funcs = map { ref() ? $_ : split } @_;
  1         7  
191 1         2 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 54 my $self = shift;
205 63         75 my @funcs = @_;
206             # use DDS; print "get_and_create: " , Dump \@funcs;
207              
208 63         51 my $rec = $self;
209 63         130 for my $f (0 .. $#funcs) {
210 72         85 my ($func, $count, $args) = ($funcs[$f], 1);
211 72 50       116 $args = {} unless defined $args;
212 72 100       176 ($func, $count, $args) = @$func if ref $func eq "ARRAY";
213 72         77 $count--;
214              
215 72 50       87 if (ref $func) {
216 0         0 warn "Invalid record of type ", ref $func, " requested";
217 0         0 return undef;
218             }
219              
220 72         115 my $record = $Gedcom::Funcs{lc $func};
221 72 50       93 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         153 my @records = $rec->tag_record($record);
232              
233 72 100       108 if ($count < 0) {
    100          
234 61         156 $rec = $rec->add_record(tag => $record, %$args);
235             } elsif ($#records < $count) {
236 7         6 my $new;
237             $new = $rec->add_record(tag => $record, %$args)
238 7         27 for (0 .. @records - $count);
239 7         12 $rec = $new;
240             } else {
241 4         9 $rec = $records[$count];
242             }
243             }
244              
245             $rec
246 63         75 }
247              
248             sub parse {
249             # print "parsing\n";
250 6531     6531 1 4341 my $self = shift;
251 6531         5194 my ($record, $grammar, $test) = @_;
252 6531   50     13139 $test ||= 0;
253              
254             # print "checking "; $record->print();
255             # print "against "; $grammar->print();
256             # print "test is $test\n";
257              
258 6531         5084 my $t = $record->{tag};
259 6531         4859 my $g = $grammar->{tag};
260 6531 50 33     16152 die "Can't match $t with $g" if $t && $t ne $g; # internal error
261              
262 6531         5486 $record->{grammar} = $grammar;
263 6531         6032 my $class = $record->{gedcom}{types}{$t};
264 6531 100       8308 bless $record, "Gedcom::$class" if $class;
265              
266 6531         4079 my $match = 1;
267              
268 6531         4108 for my $r (@{$record->{items}}) {
  6531         8074  
269 5580         4373 my $tag = $r->{tag};
270 5580         3272 my @i;
271             # print "- valid sub-items of $t: @{[keys %{$grammar->valid_items}]}\n";
272 5580         7939 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     7276 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     7634 next if $i->value && ($i->value =~ /^pointer || 0));
      100        
283              
284             # print "pushing\n";
285 5575         6668 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       7208 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         3718 my $m = 0;
308 5580         4405 for my $i (@i) {
309 5575 50       6500 last if $m = $self->parse($r, $i, @i > 1);
310             }
311              
312 5580 50 33     9915 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         10722 }
327              
328             sub collect_xrefs {
329 21861     21861 1 12954 my $self = shift;
330 21861         13012 my ($callback) = @_;
331 21861 100       29986 $self->{gedcom}{xrefs}{$self->{xref}} = $self if defined $self->{xref};
332 21861         11884 $_->collect_xrefs($callback) for @{$self->{items}};
  21861         29835  
333 21861         24867 $self
334             }
335              
336             sub resolve_xref {
337 5287     5287 1 8020 shift->{gedcom}->resolve_xref(@_);
338             }
339              
340             sub resolve {
341 15260     15260 1 10595 my $self = shift;
342             my @x = map {
343 15260         12812 ref($_)
344             ? $_
345 13819 100       22881 : do { my $x = $self->{gedcom}->resolve_xref($_); defined $x ? $x : () }
  1211 100       2013  
  1211         2107  
346             } @_;
347 15260 100       28243 wantarray ? @x : $x[0]
348             }
349              
350             sub resolve_xrefs {
351 22803     22803 1 14974 my $self = shift;
352 22803         15294 my ($callback) = @_;
353 22803 100       36992 if (my $xref = $self->{gedcom}->resolve_xref($self->{value})) {
354 2572         2139 $self->{value} = $xref;
355             }
356 22803         15243 $_->resolve_xrefs($callback) for @{$self->_items};
  22803         28365  
357 22803         30151 $self
358             }
359              
360             sub unresolve_xrefs {
361 15156     15156 1 9100 my $self = shift;;
362 15156         9431 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     53594 and exists $self->{value}{xref};
      66        
367 15156         8673 $_->unresolve_xrefs($callback) for @{$self->_items};
  15156         17313  
368 15156         17188 $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 45811 my $self = shift;
376 69989 100       99540 return 1 unless exists $self->{grammar};
377 53489         33114 my $ok = 1;
378             $self->{gedcom}{validate_callback}->($self)
379 53489 50       68106 if defined $self->{gedcom}{validate_callback};
380 53489         41410 my $grammar = $self->{grammar};
381 53489         30465 $I++;
382             print " " x $I . "validate_syntax(" .
383 53489 0       56661 (defined $grammar->{tag} ? $grammar->{tag} : "") . ")\n" if $D;
    50          
384 53489         45650 my $file = $self->{gedcom}{record}{file};
385             my $here = "$file:$self->{line}: $self->{tag}" .
386 53489 100       101825 (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     213045 !defined $grammar->{value};
      100        
391 53489         36262 my %counts;
392 53489         33294 for my $record (@{$self->_items}) {
  53489         73376  
393 69928 50       78924 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       112860 if $record->{level} > $self->{level} + 1;
397 69928         81695 $counts{$record->{tag}}++;
398 69928 100       73069 $ok = 0 unless $record->validate_syntax;
399             }
400 53489         74101 my $valid_items = $grammar->valid_items;
401 53489         184589 for my $tag (sort keys %$valid_items) {
402 563458         326167 for my $g (@{$valid_items->{$tag}}) {
  563458         526864  
403 637008         427012 my $min = $g->{min};
404 637008         385245 my $max = $g->{max};
405 637008   100     1176774 my $matches = delete $counts{$tag} || 0;
406 637008 100       875880 my $msg = "$here has $matches $tag" . ($matches == 1 ? "" : "s");
407 637008 50       664560 print " " x $I . "$msg - min is $min max is $max\n" if $D;
408 637008 50       668701 $ok = 0, warn "$msg - minimum is $min\n" if $matches < $min;
409 637008 100 100     1066825 $ok = 0, warn "$msg - maximum is $max\n" if $matches > $max && $max;
410             }
411             }
412 53489         63703 for my $tag (keys %counts) {
413 60         310 for my $c ($self->tag_record($tag)) {
414 60 50       238 $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         32005 $I--;
422 53489         102893 $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 6003 my $self = shift;
439 8770 100 100     20617 return 1 unless $self->{tag} eq "INDI" || $self->{tag} eq "FAM";
440             # print "validating: "; $self->print; print $self->summary, "\n";
441 8405         5241 my $ok = 1;
442 8405         7668 my $xrefs = $self->{gedcom}{xrefs};
443 8405         7399 my $chk = $Check->{$self->{tag}};
444 8405         10054 for my $f (keys %$chk) {
445 19630         11818 my $found = 1;
446             RECORD:
447 19630         19951 for my $record ($self->tag_value($f)) {
448 15244         10120 $found = 0;
449 15244 100       22006 $record = $xrefs->{$record} unless ref $record;
450 15244 100       18872 if ($record) {
451 15240         8957 for my $back (@{$chk->{$f}}) {
  15240         16359  
452             # print "back $back\n";
453 17160         17852 for my $i ($record->tag_value($back)) {
454             # print "record is $i\n";
455 20400 100       27726 $i = $xrefs->{$i} unless ref $i;
456 20400 100 66     57334 if ($i && $i->{xref} eq $self->{xref}) {
457 15240         9826 $found = 1;
458             # print "found...\n";
459 15240         22033 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         18984 $ok;
477             }
478              
479             sub normalise_dates {
480 7578     7578 1 4927 my $self = shift;
481 7578 50       9464 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     5130 if( eval { Date::Manip->VERSION( 6 ) } &&
  7578         37457  
486 7578         47572 !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     19319 my $format = shift || "%A, %E %B %Y";
492 7578 100 66     26921 if (defined $self->{tag} && $self->{tag} =~ /^date$/i) {
493 906 50 33     2055 if (defined $self->{value} && $self->{value}) {
494             # print "date was $self->{value}\n";
495 906         1858 my @dates = split / or /, $self->{value};
496 906         978 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       1378 next if $dt =~ /^AFT/;
504              
505             # Don't change the date if it is just < 7 digits.
506 900 100 66     3185 if ($dt !~ /^\s*(\d+)\s*$/ || length $1 > 6) {
507 642         1355 my $date = ParseDate($dt);
508 642         758760 my $d = UnixDate($date, $format);
509 642 100       218265 $dt = $d if $d;
510             }
511             }
512 906         1766 $self->{value} = join " or ", @dates;
513             # print "date is $self->{value}\n";
514             }
515             }
516 7578         4758 $_->normalise_dates($format) for @{$self->_items};
  7578         11081  
517 7578 100       10046 $self->delete_items if $self->level > 1;
518             }
519              
520             sub renumber {
521 13600     13600 1 8744 my $self = shift;
522 13600         9165 my ($args, $recurse) = @_;
523             # TODO - add the xref if there is supposed to be one
524 13600 100 100     33341 return if exists $self->{recursed} or not defined $self->{xref};
525             # we can't actually change the xrefs until the end
526 4496 100       6777 my $x = $self->{tag} eq "SUBM" ? "SUBM" : substr $self->{tag}, 0, 1;
527             $self->{new_xref} = $x . ++$args->{$self->{tag}}
528 4496 100       7579 unless exists $self->{new_xref};
529 4496 100 66     10879 return unless $recurse and not exists $self->{recursed};
530 1736         1606 $self->{recursed} = 1;
531 1736 100       2999 if ($self->{tag} eq "INDI") {
532 1121         1223 my @r = map { $self->$_() }
  6726         12292  
533             qw( fams famc spouse children parents siblings );
534 1121         2368 $_->renumber($args, 0) for @r;
535 1121         1850 $_->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__