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   58 use strict;
  11         15  
  11         382  
11              
12             require 5.005;
13              
14             package Gedcom::Item;
15              
16 11     11   47 use Symbol;
  11         13  
  11         533  
17              
18 11     11   49 use vars qw($VERSION);
  11         13  
  11         35364  
19             $VERSION = "1.21";
20              
21             sub new {
22 9329     9329 1 11154 my $proto = shift;
23 9329   66     17240 my $class = ref($proto) || $proto;
24 9329         35072 my $self = {
25             level => -3,
26             file => "*",
27             line => 0,
28             items => [],
29             @_
30             };
31 9329         14032 bless $self, $class;
32 9329 100 66     25331 $self->read if $self->{file} && $self->{file} ne "*";
33 9329         12755 $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 41 my $self = shift;
58              
59             # $self->{fh} = FileHandle->new($self->{file})
60 8         45 my $fh = $self->{fh} = gensym;
61 8 50       525 open $fh, $self->{file} or die "Can't open file $self->{file}: $!\n";
62              
63             # try to determine encoding
64 8         35 my $encoding = "unknown";
65 8         18 my $bom = 0;
66 8         207 my $line1 = <$fh>;
67 8 50       49 if ($line1 =~ /^\xEF\xBB\xBF/) {
68 0         0 $encoding = "utf-8";
69 0         0 $bom = 1;
70             } else {
71 8         41 while (<$fh>) {
72 70 100       271 if (my ($char) = /\s*1\s+CHAR\s+(.*?)\s*$/i) {
73 8 50       31 $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       74 $self->{gedcom}->set_encoding($encoding) if $self->{gedcom};
81 8 50 33     34 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         70 binmode $fh;
87             }
88              
89             # find out how big the file is
90 8         62 seek($fh, 0, 2);
91 8         29 my $size = tell $fh;
92 8 50       59 seek($fh, $bom ? 3 : 0, 0); # skip BOM
93 8         39 $. = 0;
94              
95             # initial callback
96 8         26 my $callback = $self->{callback};;
97 8         14 my $title = "Reading";
98 8         20 my $txt1 = "Reading $self->{file}";
99 8         15 my $count = 0;
100             return undef
101 8 50 33     34 if $callback &&
102             !$callback->($title, $txt1, "Record $count", tell $fh, $size);
103              
104 8 100       106 $self->level($self->{grammar} ? -1 : -2);
105              
106 8         34 my $if = "$self->{file}.index";
107 8         15 my ($gf, $gc);
108 8 100 66     95 if ($self->{gedcom}{read_only} &&
      100        
      66        
109             defined ($gf = -M $self->{file}) &&
110             defined ($gc = -M $if) && $gc < $gf) {
111 1 50       25 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         20 while () {
116 146         284 my @vals = split /\|/;
117             my $record =
118             Gedcom::Record->new(
119             gedcom => $self->{gedcom},
120 146         293 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       267 $record->{xref} = $vals[1] if length $vals[1];
128 146 100       196 $record->{value} = $vals[2] if length $vals[2];
129 146         259 my $class = $self->{gedcom}{types}{$vals[0]};
130 146 100       232 bless $record, "Gedcom::$class" if $class;
131 146         140 push @{$self->{items}}, $record;
  146         392  
132             }
133 1 50       14 close I or warn "Can't close $if";
134             }
135             }
136              
137 8 100       16 unless (@{$self->{items}}) {
  8         35  
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         45 while (my $item = $self->next_item($self)) {
144 905 100       1514 if ($self->{grammar}) {
145 876         1125 my $tag = $item->{tag};
146 876         2416 my @g = $self->{grammar}->item($tag);
147             # print "<$tag> => <@g>\n";
148 876 50       1502 if (@g) {
149 876         2504 $self->parse($item, $g[0]);
150 876         915 push @{$self->{items}}, $item;
  876         1348  
151 876         1182 $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         56 push @{$self->{items}}, $item;
  29         44  
160 29         34 $count++;
161             }
162             return undef
163             if ref $item &&
164             $callback &&
165             !$callback->($title, $txt1, "Record $count line " . $item->{line},
166 905 50 33     4194 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     60 if ($self->{gedcom}{read_only} && defined $gf &&
      66        
      66        
177             (! defined $gc || $gc > $gf)) {
178 1 50       82 if (! open I, ">$if") {
179 0         0 warn "Can't open $if";
180             } else {
181 1         3 for my $item (@{$self->{items}}) {
  1         3  
182 146 100       164 print I join("|", map { $item->{$_} || "" }
  730         1311  
183             qw(tag xref value line cpos));
184 146         236 print I "\n";
185             }
186 1 50       40 close I or warn "Can't close $if";
187             }
188             }
189              
190 8         29 $self;
191             }
192              
193             sub add_items {
194 13376     13376 1 14685 my $self = shift;
195 13376         18382 my ($item, $parse) = @_;
196             # print "adding items to: "; $item->print;
197 13376 100 100     45266 if (!$parse &&
      100        
      66        
198             $item->{level} >= 0 &&
199             $self->{gedcom}{read_only} &&
200             $self->{gedcom}{grammar}) {
201             # print "ignoring items\n";
202 2378         4198 $self->skip_items($item);
203             } else {
204 10998 50 100     23618 if ($parse && $self->{gedcom}{read_only} && $self->{gedcom}{grammar}) {
      66        
205             # print "reading items\n";
206 3110 50       4867 if (defined $item->{cpos}) {
207 3110         19807 seek($self->{fh}, $item->{cpos}, 0);
208 3110         8826 $. = $item->{line};
209             }
210             }
211 10998         18379 $item->{items} = [];
212 10998         19989 while (my $next = $self->next_item($item)) {
213 8462 100       14577 unless (ref $next) {
214             # The grammar requires a single selection from its items
215 73         122 $item->{selection} = 1;
216 73         127 next;
217             }
218 8389         10847 my $level = $item->{level};
219 8389         9264 my $next_level = $next->{level};
220 8389 100 66     21436 if (!defined $next_level || $next_level <= $level) {
221 200         215 $self->{stored_item} = $next;
222             # print "stored ***********************************\n";
223 200         260 return;
224             } else {
225 8189 50       13536 warn "$self->{file}:$item->{line}: " .
226             "Can't add level $next_level to $level\n"
227             if $next_level > $level + 1;
228 8189         8095 push @{$item->{items}}, $next;
  8189         23914  
229             }
230             }
231 10798 100       27312 $item->{_items} = 1 unless $item->{gedcom}{read_only};
232             }
233             }
234              
235             sub skip_items {
236 2378     2378 0 2495 my $self = shift;
237 2378         2683 my ($item) = @_;
238 2378         3043 my $level = $item->{level};
239 2378         4344 my $cpos = $item->{cpos} = tell $self->{fh};
240             # print "skipping items to level $level at $item->{line}:$cpos\n";
241 2378         2559 my $fh = $self->{fh};
242 2378         5813 while (my $l = <$fh>) {
243 4522         5323 chomp $l;
244             # print "parsing <$l>\n";
245 4522 100       11816 if (my ($lev) = $l =~ /^\s*(\d+)/) {
246 4079 100       6410 if ($lev <= $level) {
247             # print "pushing <$l>\n";
248 2377         23342 seek($self->{fh}, $cpos, 0);
249 2377         6617 $.--;
250 2377         5355 last;
251             }
252             }
253 2145         4628 $cpos = tell $self->{fh};
254             }
255             }
256              
257             sub next_item {
258 20172     20172 1 22574 my $self = shift;
259 20172         23980 my ($item) = @_;
260 20172         29729 my $bpos = tell $self->{fh};
261 20172         26803 my $bline = $.;
262             # print "At $bpos:$bline\n";
263 20172         18988 my $rec;
264 20172         21705 my $fh = $self->{fh};
265 20172 100 33     55943 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         28099 my $line_number = $.;
270             # print "line $line_number is <$line>";
271 18783 100       182777 if (my ($structure) = $line =~ /^\s*(\w+): =\s*$/) {
    100          
    50          
272 29         58 $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     59356 if ($level eq "n" || $level > $item->{level}) {
331 9065 50       13707 unless ($rec) {
332 9065         16802 $rec = $self->new(line => $line_number);
333             $rec->{gedcom} = $self->{gedcom}
334 9065 100       20849 if $self->{gedcom}{grammar};
335             }
336 9065 100       20698 $rec->{level} = ($level eq "n" ? 0 : $level) if defined $level;
    50          
337 9065 50       15537 $rec->{xref} = $xref =~ /^\@(.+)\@$/ ? $1 : $xref
    100          
338             if defined $xref;
339 9065 100       23653 $rec->{tag} = $tag if defined $tag;
340 9065 100 100     22671 $value .= $space if defined $space && $self->{grammar};
341 9065 100 66     22384 $value .= $star if defined $star && $self->{grammar};
342 9065 100       28655 $value =~ s/[\r\n]+$// if defined $value;
343             # print STDERR "value: [$value]\n";
344 9065 100       34687 $rec->{value} = ($rec->{pointer} = $value =~ /^\@(.+)\@$/)
    100          
345             ? $1
346             : $value
347             if defined $value;
348 9065 100       14374 $rec->{min} = $min if defined $min;
349 9065 100       16483 $rec->{max} = $max if defined $max;
350             } else {
351             # print " -- pushing back\n";
352 9616         88820 seek($fh, $bpos, 0);
353 9616         25989 $. = $bline;
354             }
355             } elsif ($line =~ /^\s*[\[\|\]]\s*(?:\/\*.*\*\/\s*)?$/) {
356             # The grammar requires a single selection from its items.
357 73         206 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     67282 if $rec && defined $rec->{level} && ($rec->{level} > $item->{level});
      100        
369 20099         43640 $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 21555 my $self = shift;
381 19972         20848 my $line = "";
382 19972         21536 my $fh = $self->{fh};
383 19972   100     236139 $line = <$fh> until !defined $line || $line =~ /\S/;
384 19972         64614 $line;
385             }
386              
387             sub write {
388 7647     7647 1 7572 my $self = shift;
389 7647         9197 my ($fh, $level, $flush) = @_;
390 7647   100     10250 $level ||= 0;
391 7647         6948 my @p;
392 7647 50       16367 push @p, $level . " " x ($flush ? 0 : $level) unless $level < 0;
    100          
393             push @p, "\@$self->{xref}\@" if defined $self->{xref} &&
394 7647 100 66     14687 length $self->{xref};
395 7647 100       14085 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     24737 length $self->{value};
    100          
    100          
402 7647         19100 $fh->print("@p");
403 7647 100       41601 $fh->print("\n") unless $level < 0;
404 7647         28839 for my $c (0 .. @{$self->_items} - 1) {
  7647         9264  
405 7640         19090 $self->{items}[$c]->write($fh, $level + 1, $flush);
406             $fh->print("\n") if $level < 0 &&
407 7640 100 100     12829 $c < @{$self->{items}} - 1;
  886         2294  
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 68786 my $self = shift;
495 65936         84234 my ($tag, $count) = @_;
496 65936 100 100     134303 if (wantarray && !$count) {
497 55859         56358 return grep { $_->{tag} eq $tag } @{$self->_items};
  338989         570769  
  55859         68791  
498             } else {
499 10077 100       13556 $count = 1 unless $count;
500 10077         9672 for my $c (@{$self->_items}) {
  10077         12318  
501 10567 100 100     35183 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 3 my $self = shift;
523              
524 1         2 my $i = "$self";
525 1         4 my @records = ($self->{gedcom}{record});
526              
527 1         4 while (@records) {
528 1         1 my $r = shift @records;
529 1         2 for (@{$r->_items}) {
  1         4  
530 8 100       15 return $r if $i eq "$_";
531 7         11 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         5 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 4 my $self = shift;
547 3         4 my ($item) = @_;
548              
549 3         7 my $i = "$item";
550 3         10 my $n = 0;
551 3         4 for (@{$self->_items}) {
  3         5  
552 16 100       25 last if $i eq "$_";
553 13         15 $n++;
554             }
555              
556 3 50       6 return 0 unless $n < @{$self->{items}};
  3         6  
557              
558             # print "deleting item $n of $#{$self->{items}}\n";
559 3         4 splice @{$self->{items}}, $n, 1;
  3         7  
560 3 100       7 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   82 no strict "refs";
  11         20  
  11         3108  
567             *$func = sub {
568 31219     31219   53079 my $self = shift;
569 31219 100       39926 $self->{$func} = shift if @_;
570 31219         86256 $self->{$func}
571             }
572             }
573              
574             sub full_value {
575 66527     66527 1 70111 my $self = shift;
576 66527         78452 my $value = $self->{value};
577 66527 50       148990 $value =~ s/[\r\n]+$// if defined $value;
578 66527         69833 for my $item (@{$self->_items}) {
  66527         80814  
579 164 50       345 my $v = defined $item->{value} ? $item->{value} : "";
580 164         263 $v =~ s/[\r\n]+$//;
581 164 100       293 $value .= "\n$v" if $item->{tag} eq "CONT";
582 164 100       306 $value .= $v if $item->{tag} eq "CONC";
583             }
584             $value
585 66527         116778 }
586              
587             sub _items {
588 239414     239414   245798 my $self = shift;
589             $self->{gedcom}{record}->add_items($self, 1)
590 239414 100 100     408934 if !defined $self->{_items} && $self->{level} >= 0;
591 239414         247021 $self->{_items} = 1;
592             $self->{items}
593 239414         425801 }
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 2012 my $self = shift;
602 1758         3330 delete $self->{_items};
603 1758         3958 delete $self->{items};
604             }
605              
606             1;
607              
608             __END__