File Coverage

blib/lib/Gedcom/Item.pm
Criterion Covered Total %
statement 222 306 72.5
branch 130 200 65.0
condition 74 137 54.0
subroutine 19 27 70.3
pod 19 21 90.4
total 464 691 67.1


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   56 use strict;
  11         16  
  11         365  
11              
12             require 5.005;
13              
14             package Gedcom::Item;
15              
16 11     11   46 use Symbol;
  11         13  
  11         526  
17              
18 11     11   49 use vars qw($VERSION);
  11         24  
  11         36572  
19             $VERSION = "1.22";
20              
21             sub new {
22 9329     9329 1 11083 my $proto = shift;
23 9329   66     17007 my $class = ref($proto) || $proto;
24 9329         38597 my $self = {
25             level => -3,
26             file => "*",
27             line => 0,
28             items => [],
29             @_
30             };
31 9329         14567 bless $self, $class;
32 9329 100 66     25488 $self->read if $self->{file} && $self->{file} ne "*";
33 9329         13013 $self;
34             }
35              
36             sub copy {
37 0     0 1 0 my $self = shift;
38 0         0 my $item = $self->new;
39 0         0 for my $key (qw(level xref tag value pointer min max gedcom)) {
40 0 0       0 $item->{$key} = $self->{$key} if exists $self->{$key}
41             }
42 0         0 $item->{items} = [ map { $_->copy } @{$self->_items} ];
  0         0  
  0         0  
43 0         0 $item
44             }
45              
46             sub hash {
47 0     0 0 0 my $self = shift;
48 0         0 my $item = {};
49 0         0 for my $key (qw(level xref tag value pointer min max)) {
50 0 0       0 $item->{$key} = $self->{$key} if exists $self->{$key}
51             }
52 0         0 $item->{items} = [ map { $_->hash } @{$self->_items} ];
  0         0  
  0         0  
53 0         0 $item
54             }
55              
56             sub read {
57 8     8 1 39 my $self = shift;
58              
59             # $self->{fh} = FileHandle->new($self->{file})
60 8         52 my $fh = $self->{fh} = gensym;
61 8 50       518 open $fh, $self->{file} or die "Can't open file $self->{file}: $!\n";
62              
63             # try to determine encoding
64 8         36 my $encoding = "unknown";
65 8         14 my $bom = 0;
66 8         213 my $line1 = <$fh>;
67 8 50       47 if ($line1 =~ /^\xEF\xBB\xBF/) {
68 0         0 $encoding = "utf-8";
69 0         0 $bom = 1;
70             } else {
71 8         43 while (<$fh>) {
72 70 100       286 if (my ($char) = /\s*1\s+CHAR\s+(.*?)\s*$/i) {
73 8 50       72 $encoding = $char =~ /utf\W*8/i ? "utf-8" : $char;
74 8         21 last;
75             }
76             }
77             }
78              
79             # print "encoding is [$encoding]\n";
80 8 100       67 $self->{gedcom}->set_encoding($encoding) if $self->{gedcom};
81 8 50 33     37 if ($encoding eq "utf-8" && $] >= 5.8) {
82 0         0 binmode $fh, ":encoding(UTF-8)";
83 0         0 binmode STDOUT, ":encoding(UTF-8)";
84 0         0 binmode STDERR, ":encoding(UTF-8)";
85             } else {
86 8         69 binmode $fh;
87             }
88              
89             # find out how big the file is
90 8         63 seek($fh, 0, 2);
91 8         28 my $size = tell $fh;
92 8 50       54 seek($fh, $bom ? 3 : 0, 0); # skip BOM
93 8         40 $. = 0;
94              
95             # initial callback
96 8         26 my $callback = $self->{callback};;
97 8         14 my $title = "Reading";
98 8         22 my $txt1 = "Reading $self->{file}";
99 8         15 my $count = 0;
100             return undef
101 8 50 33     31 if $callback &&
102             !$callback->($title, $txt1, "Record $count", tell $fh, $size);
103              
104 8 100       94 $self->level($self->{grammar} ? -1 : -2);
105              
106 8         21 my $if = "$self->{file}.index";
107 8         19 my ($gf, $gc);
108 8 100 66     100 if ($self->{gedcom}{read_only} &&
      100        
      66        
109             defined ($gf = -M $self->{file}) &&
110             defined ($gc = -M $if) && $gc < $gf) {
111 1 50       26 if (! open I, $if) {
112 0         0 die "Can't open $if: $!";
113             } else {
114 1         6 my $g = $self->{gedcom}{grammar}->structure("GEDCOM");
115 1         18 while () {
116 146         271 my @vals = split /\|/;
117             my $record =
118             Gedcom::Record->new(
119             gedcom => $self->{gedcom},
120 146         269 tag => $vals[0],
121             line => $vals[3],
122             cpos => $vals[4],
123             grammar => $g->item($vals[0]),
124             fh => $fh,
125             level => 0,
126             );
127 146 100       252 $record->{xref} = $vals[1] if length $vals[1];
128 146 100       195 $record->{value} = $vals[2] if length $vals[2];
129 146         187 my $class = $self->{gedcom}{types}{$vals[0]};
130 146 100       223 bless $record, "Gedcom::$class" if $class;
131 146         134 push @{$self->{items}}, $record;
  146         381  
132             }
133 1 50       13 close I or warn "Can't close $if";
134             }
135             }
136              
137 8 100       19 unless (@{$self->{items}}) {
  8         32  
138             # $#{$self->{items}} = 20000;
139             # $#{$self->{items}} = -1;
140             # If we have a grammar, then we are reading a GEDCOM file and must use
141             # the grammar to verify what is being read.
142             # If we do not have a grammar, then that is what we are reading.
143 7         49 while (my $item = $self->next_item($self)) {
144 905 100       1553 if ($self->{grammar}) {
145 876         1151 my $tag = $item->{tag};
146 876         2381 my @g = $self->{grammar}->item($tag);
147             # print "<$tag> => <@g>\n";
148 876 50       1465 if (@g) {
149 876         2439 $self->parse($item, $g[0]);
150 876         870 push @{$self->{items}}, $item;
  876         1361  
151 876         1134 $count++;
152             } else {
153 0 0 0     0 $tag = "" unless defined $tag && length $tag;
154 0         0 warn "$self->{file}:$item->{line}: " .
155             "$tag is not a top level tag\n";
156             }
157             } else {
158             # just add the grammar item
159 29         27 push @{$self->{items}}, $item;
  29         41  
160 29         31 $count++;
161             }
162             return undef
163             if ref $item &&
164             $callback &&
165             !$callback->($title, $txt1, "Record $count line " . $item->{line},
166 905 50 33     3348 tell $fh, $size);
      33        
167             }
168             }
169              
170             # unless ($self->{gedcom}{read_only})
171             # {
172             # $self->{fh}->close or die "Can't close file $self->{file}: $!";
173             # delete $self->{fh};
174             # }
175              
176 8 100 66     62 if ($self->{gedcom}{read_only} && defined $gf &&
      66        
      66        
177             (! defined $gc || $gc > $gf)) {
178 1 50       119 if (! open I, ">$if") {
179 0         0 warn "Can't open $if";
180             } else {
181 1         3 for my $item (@{$self->{items}}) {
  1         5  
182 146 100       165 print I join("|", map { $item->{$_} || "" }
  730         1344  
183             qw(tag xref value line cpos));
184 146         223 print I "\n";
185             }
186 1 50       63 close I or warn "Can't close $if";
187             }
188             }
189              
190 8         31 $self;
191             }
192              
193             sub add_items {
194 13376     13376 1 14547 my $self = shift;
195 13376         17755 my ($item, $parse) = @_;
196             # print "adding items to: "; $item->print;
197 13376 100 100     44320 if (!$parse &&
      100        
      66        
198             $item->{level} >= 0 &&
199             $self->{gedcom}{read_only} &&
200             $self->{gedcom}{grammar}) {
201             # print "ignoring items\n";
202 2378         3885 $self->skip_items($item);
203             } else {
204 10998 50 100     23071 if ($parse && $self->{gedcom}{read_only} && $self->{gedcom}{grammar}) {
      66        
205             # print "reading items\n";
206 3110 50       5352 if (defined $item->{cpos}) {
207 3110         18813 seek($self->{fh}, $item->{cpos}, 0);
208 3110         8477 $. = $item->{line};
209             }
210             }
211 10998         17410 $item->{items} = [];
212 10998         20383 while (my $next = $self->next_item($item)) {
213 8462 100       14595 unless (ref $next) {
214             # The grammar requires a single selection from its items
215 73         102 $item->{selection} = 1;
216 73         136 next;
217             }
218 8389         11072 my $level = $item->{level};
219 8389         9485 my $next_level = $next->{level};
220 8389 100 66     21158 if (!defined $next_level || $next_level <= $level) {
221 200         218 $self->{stored_item} = $next;
222             # print "stored ***********************************\n";
223 200         253 return;
224             } else {
225 8189 50       12524 warn "$self->{file}:$item->{line}: " .
226             "Can't add level $next_level to $level\n"
227             if $next_level > $level + 1;
228 8189         8178 push @{$item->{items}}, $next;
  8189         23855  
229             }
230             }
231 10798 100       27206 $item->{_items} = 1 unless $item->{gedcom}{read_only};
232             }
233             }
234              
235             sub skip_items {
236 2378     2378 0 2512 my $self = shift;
237 2378         2719 my ($item) = @_;
238 2378         2774 my $level = $item->{level};
239 2378         4305 my $cpos = $item->{cpos} = tell $self->{fh};
240             # print "skipping items to level $level at $item->{line}:$cpos\n";
241 2378         2802 my $fh = $self->{fh};
242 2378         5788 while (my $l = <$fh>) {
243 4522         5423 chomp $l;
244             # print "parsing <$l>\n";
245 4522 100       12245 if (my ($lev) = $l =~ /^\s*(\d+)/) {
246 4079 100       6167 if ($lev <= $level) {
247             # print "pushing <$l>\n";
248 2377         22051 seek($self->{fh}, $cpos, 0);
249 2377         5970 $.--;
250 2377         5146 last;
251             }
252             }
253 2145         4569 $cpos = tell $self->{fh};
254             }
255             }
256              
257             sub next_item {
258 20172     20172 1 22963 my $self = shift;
259 20172         24157 my ($item) = @_;
260 20172         29005 my $bpos = tell $self->{fh};
261 20172         26299 my $bline = $.;
262             # print "At $bpos:$bline\n";
263 20172         18986 my $rec;
264 20172         21689 my $fh = $self->{fh};
265 20172 100 33     53786 if ($rec = $self->{stored_item}) {
    100 66        
266 200         216 $self->{stored_item} = undef;
267             } elsif ((!$rec || !$rec->{level}) && (my $line = $self->next_text_line)) {
268             # TODO - tidy this up
269 18783         28525 my $line_number = $.;
270             # print "line $line_number is <$line>";
271 18783 100       183436 if (my ($structure) = $line =~ /^\s*(\w+): =\s*$/) {
    100          
    50          
272 29         52 $rec = $self->new(level => -1,
273             structure => $structure,
274             line => $line_number);
275             # print "found structure $structure\n";
276             } elsif (my ($level, $xref, $tag, $value, $space, $min, $max, $star) =
277             $line =~ /^\s* # optional whitespace at start
278             ((?:\+?\d+)|n) # start level
279             \s* # optional whitespace
280             (?: # xref
281             (@?@) # text in @?@
282             \s+ # whitespace
283             )? # optional
284             (?: # tag
285             (?!<<) # don't match a type
286             ([\w\s\[\]\|<>]+?) # non greedy
287             \s+ # whitespace
288             )? # optional
289             (?: # value
290             ( #
291             (?: # one of
292             @??@? # text element - non greedy
293             | # or
294             \[\s* # start list
295             (?: #
296             @?<.*>@? # text element
297             \s*\|?\s* # optionally delimited
298             )+ # one or more
299             \] # end list
300             ) #
301             ) #
302             (\s+) # whitespace
303             )?? # optional - non greedy
304             (?: # value
305             \{ # open brace
306             (\d+) # min
307             : # :
308             (\d+|M) # max
309             \*? # optional *
310             [\}\]] # close brace or bracket
311             )? # optional
312             (\*?\s*) # optional * and ws at end
313             $/x)
314             # $line =~ /^\s* # optional whitespace at start
315             # (\d+) # start level
316             # \s* # optional whitespace
317             # (?: # xref
318             # (@.*@) # text in @@
319             # \s+ # whitespace
320             # )? # optional
321             # (\w+) # tag
322             # \s* # whitespace
323             # (?: # value
324             # (@?.*?@?) # text element - non greedy
325             # \s+ # whitespace
326             # )?? # optional - non greedy
327             # \s*$/x) # optional whitespace at end
328             {
329             # print "found $level below $item->{level}\n";
330 18681 100 100     59208 if ($level eq "n" || $level > $item->{level}) {
331 9065 50       13386 unless ($rec) {
332 9065         16388 $rec = $self->new(line => $line_number);
333             $rec->{gedcom} = $self->{gedcom}
334 9065 100       20170 if $self->{gedcom}{grammar};
335             }
336 9065 100       20179 $rec->{level} = ($level eq "n" ? 0 : $level) if defined $level;
    50          
337 9065 50       15362 $rec->{xref} = $xref =~ /^\@(.+)\@$/ ? $1 : $xref
    100          
338             if defined $xref;
339 9065 100       15719 $rec->{tag} = $tag if defined $tag;
340 9065 100 100     23257 $value .= $space if defined $space && $self->{grammar};
341 9065 100 66     21938 $value .= $star if defined $star && $self->{grammar};
342 9065 100       28468 $value =~ s/[\r\n]+$// if defined $value;
343             # print STDERR "value: [$value]\n";
344 9065 100       34682 $rec->{value} = ($rec->{pointer} = $value =~ /^\@(.+)\@$/)
    100          
345             ? $1
346             : $value
347             if defined $value;
348 9065 100       14286 $rec->{min} = $min if defined $min;
349 9065 100       14618 $rec->{max} = $max if defined $max;
350             } else {
351             # print " -- pushing back\n";
352 9616         89138 seek($fh, $bpos, 0);
353 9616         26409 $. = $bline;
354             }
355             } elsif ($line =~ /^\s*[\[\|\]]\s*(?:\/\*.*\*\/\s*)?$/) {
356             # The grammar requires a single selection from its items.
357 73         194 return "selection";
358             } else {
359 0         0 chomp $line;
360 0         0 my $file = $self->{file};
361 0         0 die "\n$file:$line_number: Can't parse line: $line\n";
362             }
363             }
364              
365             # print "\ncomparing "; $item->print;
366             # print "with "; $rec->print if $rec;
367             $self->add_items($rec)
368 20099 100 66     67320 if $rec && defined $rec->{level} && ($rec->{level} > $item->{level});
      100        
369 20099         41838 $rec;
370             }
371              
372             sub next_line {
373 0     0 1 0 my $self = shift;
374 0         0 my $fh = $self->{fh};
375 0         0 my $line = <$fh>;
376 0         0 $line;
377             }
378              
379             sub next_text_line {
380 19972     19972 1 21251 my $self = shift;
381 19972         20555 my $line = "";
382 19972         20840 my $fh = $self->{fh};
383 19972   100     235413 $line = <$fh> until !defined $line || $line =~ /\S/;
384 19972         64017 $line;
385             }
386              
387             sub write {
388 7647     7647 1 7490 my $self = shift;
389 7647         9326 my ($fh, $level, $flush) = @_;
390 7647   100     10687 $level ||= 0;
391 7647         7024 my @p;
392 7647 50       16659 push @p, $level . " " x ($flush ? 0 : $level) unless $level < 0;
    100          
393             push @p, "\@$self->{xref}\@" if defined $self->{xref} &&
394 7647 100 66     14360 length $self->{xref};
395 7647 100       14804 push @p, $self->{tag} if $level >= 0;
396             push @p, ref $self->{value}
397             ? "\@$self->{value}{xref}\@"
398             : $self->resolve_xref($self->{value})
399             ? "\@$self->{value}\@"
400             : $self->{value} if defined $self->{value} &&
401 7647 100 100     25074 length $self->{value};
    100          
    100          
402 7647         19207 $fh->print("@p");
403 7647 100       42473 $fh->print("\n") unless $level < 0;
404 7647         30334 for my $c (0 .. @{$self->_items} - 1) {
  7647         9247  
405 7640         18910 $self->{items}[$c]->write($fh, $level + 1, $flush);
406             $fh->print("\n") if $level < 0 &&
407 7640 100 100     13017 $c < @{$self->{items}} - 1;
  886         2383  
408             }
409             }
410              
411             sub write_xml {
412 0     0 1 0 my $self = shift;
413 0         0 my ($fh, $level) = @_;
414              
415 0 0 0     0 return if $self->{tag} && $self->{tag} =~ /^(CON[CT]|TRLR)$/;
416              
417 0         0 my $spaced = 0;
418 0         0 my $events = 0;
419              
420 0 0       0 $level = 0 unless $level;
421 0         0 my $indent = " " x $level;
422              
423 0   0     0 my $tag = $level >= 0 && $self->{tag};
424              
425             my $value = $self->{value}
426             ? ref $self->{value}
427             ? $self->{value}{xref}
428 0 0       0 : $self->full_value
    0          
429             : undef;
430 0 0       0 $value =~ s/\s+$// if defined $value;
431              
432 0         0 my $sub_items = @{$self->_items};
  0         0  
433              
434 0         0 my $p = "";
435 0 0       0 if ($tag) {
436             $tag = $events &&
437             defined $self->{gedcom}{types}{$self->{tag}} &&
438             $self->{gedcom}{types}{$self->{tag}} eq "Event"
439             ? "EVEN"
440 0 0 0     0 : $self->{tag};
441              
442 0 0       0 $tag = "GED" if $tag eq "GEDCOM";
443              
444 0         0 $p .= $indent;
445 0         0 $p .= "<$tag";
446              
447 0 0 0     0 if ($tag eq "EVEN") {
    0 0        
    0          
448 0         0 $p .= qq( EV="$self->{tag}");
449             } elsif ($tag =~ /^(FAM[SC]|HUSB|WIFE|CHIL|SUBM|NOTE)$/ &&
450             defined $value &&
451             $self->resolve_xref($self->{value})) {
452 0         0 $p .= qq( REF="$value");
453 0         0 $value = undef;
454 0 0       0 $tag = undef unless $sub_items;
455             } elsif ($self->{xref}) {
456 0         0 $p .= qq( ID="$self->{xref}");
457             }
458              
459 0 0 0     0 $p .= "/" unless defined $value || $tag;
460 0         0 $p .= ">";
461             $p .= "\n"
462             if $sub_items ||
463             (!$spaced &&
464 0 0 0     0 (!(defined $value || $tag) || $tag eq "EVEN" || $self->{xref}));
      0        
      0        
465             }
466              
467 0 0       0 if (defined $value) {
468 0 0 0     0 $p .= "$indent " if $spaced || $sub_items;
469 0         0 $p .= $value;
470 0 0 0     0 $p .= "\n" if $spaced || $sub_items;
471             }
472              
473 0         0 $fh->print($p);
474              
475 0         0 for my $c (0 .. $sub_items - 1) {
476 0         0 $self->{items}[$c]->write_xml($fh, $level + 1);
477             }
478              
479 0 0       0 if ($tag) {
480 0 0 0     0 $fh->print($indent) if $spaced || $sub_items;
481 0         0 $fh->print("\n");
482             }
483             }
484              
485             sub print {
486 0     0 1 0 my $self = shift;
487 0         0 for my $v (qw( level xref tag value min max )) {
488 0 0       0 print($v, ": ", $self->{$v}, " ") if defined $self->{$v};
489             }
490 0         0 print "\n";
491             }
492              
493             sub get_item {
494 65936     65936 1 66495 my $self = shift;
495 65936         82487 my ($tag, $count) = @_;
496 65936 100 100     131292 if (wantarray && !$count) {
497 55859         53305 return grep { $_->{tag} eq $tag } @{$self->_items};
  338989         530748  
  55859         67365  
498             } else {
499 10077 100       13477 $count = 1 unless $count;
500 10077         9444 for my $c (@{$self->_items}) {
  10077         12499  
501 10567 100 100     34508 return $c if $c->{tag} eq $tag && !--$count;
502             }
503             }
504             undef
505 0         0 }
506              
507             sub get_child {
508             # NOTE - This function is deprecated - use get_item instead
509 0     0 1 0 my $self = shift;
510 0         0 my ($t) = @_;
511 0         0 my ($tag, $count) = $t =~ /^_?(\w+?)(\d*)$/;
512 0         0 $self->get_item($tag, $count);
513             }
514              
515             sub get_children {
516             # NOTE - This function is deprecated - use get_item instead
517 0     0 1 0 my $self = shift;
518 0         0 $self->get_item(@_)
519             }
520              
521             sub parent {
522 1     1 1 2 my $self = shift;
523              
524 1         4 my $i = "$self";
525 1         3 my @records = ($self->{gedcom}{record});
526              
527 1         3 while (@records) {
528 1         2 my $r = shift @records;
529 1         2 for (@{$r->_items}) {
  1         4  
530 8 100       15 return $r if $i eq "$_";
531 7         10 push @records, $r;
532             }
533             }
534              
535             undef
536 0         0 }
537              
538             sub delete {
539 1     1 1 2 my $self = shift;
540 1         6 my $parent = $self->parent;
541 1 50       3 return unless $parent;
542 1         5 $parent->delete_item($self);
543             }
544              
545             sub delete_item {
546 3     3 1 5 my $self = shift;
547 3         4 my ($item) = @_;
548              
549 3         6 my $i = "$item";
550 3         4 my $n = 0;
551 3         4 for (@{$self->_items}) {
  3         7  
552 16 100       28 last if $i eq "$_";
553 13         14 $n++;
554             }
555              
556 3 50       4 return 0 unless $n < @{$self->{items}};
  3         6  
557              
558             # print "deleting item $n of $#{$self->{items}}\n";
559 3         3 splice @{$self->{items}}, $n, 1;
  3         6  
560 3 100       8 delete $self->{gedcom}{xrefs}{$item->{xref}} if defined $item->{xref};
561              
562 3         14 1
563             }
564              
565             for my $func (qw(level xref tag value pointer min max gedcom file line)) {
566 11     11   84 no strict "refs";
  11         19  
  11         3307  
567             *$func = sub {
568 31219     31219   39287 my $self = shift;
569 31219 100       39626 $self->{$func} = shift if @_;
570 31219         84692 $self->{$func}
571             }
572             }
573              
574             sub full_value {
575 66527     66527 1 67750 my $self = shift;
576 66527         75167 my $value = $self->{value};
577 66527 50       123051 $value =~ s/[\r\n]+$// if defined $value;
578 66527         66101 for my $item (@{$self->_items}) {
  66527         76641  
579 164 50       343 my $v = defined $item->{value} ? $item->{value} : "";
580 164         293 $v =~ s/[\r\n]+$//;
581 164 100       333 $value .= "\n$v" if $item->{tag} eq "CONT";
582 164 100       286 $value .= $v if $item->{tag} eq "CONC";
583             }
584             $value
585 66527         114543 }
586              
587             sub _items {
588 239414     239414   237949 my $self = shift;
589             $self->{gedcom}{record}->add_items($self, 1)
590 239414 100 100     389613 if !defined $self->{_items} && $self->{level} >= 0;
591 239414         241331 $self->{_items} = 1;
592             $self->{items}
593 239414         411331 }
594              
595             sub items {
596 0     0 1 0 my $self = shift;
597 0         0 @{$self->_items}
  0         0  
598             }
599              
600             sub delete_items {
601 1758     1758 1 1927 my $self = shift;
602 1758         2198 delete $self->{_items};
603 1758         5175 delete $self->{items};
604             }
605              
606             1;
607              
608             __END__