| 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__ |