File Coverage

blib/lib/Gedcom/Item.pm
Criterion Covered Total %
statement 222 306 72.5
branch 129 198 65.1
condition 72 140 51.4
subroutine 19 27 70.3
pod 19 21 90.4
total 461 692 66.6


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   35 use strict;
  11         16  
  11         354  
11              
12             require 5.005;
13              
14             package Gedcom::Item;
15              
16 11     11   35 use Symbol;
  11         9  
  11         529  
17              
18 11     11   36 use vars qw($VERSION);
  11         11  
  11         32981  
19             $VERSION = "1.20";
20              
21             sub new {
22 9329     9329 1 7090 my $proto = shift;
23 9329   66     13942 my $class = ref($proto) || $proto;
24 9329         25672 my $self = {
25             level => -3,
26             file => "*",
27             line => 0,
28             items => [],
29             @_
30             };
31 9329         9369 bless $self, $class;
32 9329 100 66     26760 $self->read if $self->{file} && $self->{file} ne "*";
33 9329         9961 $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 11 my $self = shift;
58              
59             # $self->{fh} = FileHandle->new($self->{file})
60 8         50 my $fh = $self->{fh} = gensym;
61 8 50       364 open $fh, $self->{file} or die "Can't open file $self->{file}: $!\n";
62              
63             # try to determine encoding
64 8         19 my $encoding = "unknown";
65 8         12 my $bom = 0;
66 8         122 my $line1 = <$fh>;
67 8 50       32 if ($line1 =~ /^\xEF\xBB\xBF/) {
68 0         0 $encoding = "utf-8";
69 0         0 $bom = 1;
70             } else {
71 8         30 while (<$fh>) {
72 70 100       244 if (my ($char) = /\s*1\s+CHAR\s+(.*?)\s*$/i) {
73 8 50       27 $encoding = $char =~ /utf\W*8/i ? "utf-8" : $char;
74 8         37 last;
75             }
76             }
77             }
78              
79             # print "encoding is [$encoding]\n";
80 8 100       51 $self->{gedcom}->set_encoding($encoding) if $self->{gedcom};
81 8 50 33     27 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         26 binmode $fh;
87             }
88              
89             # find out how big the file is
90 8         23 seek($fh, 0, 2);
91 8         14 my $size = tell $fh;
92 8 50       23 seek($fh, $bom ? 3 : 0, 0); # skip BOM
93 8         24 $. = 0;
94              
95             # initial callback
96 8         15 my $callback = $self->{callback};;
97 8         11 my $title = "Reading";
98 8         16 my $txt1 = "Reading $self->{file}";
99 8         12 my $count = 0;
100             return undef
101 8 50 33     30 if $callback &&
102             !$callback->($title, $txt1, "Record $count", tell $fh, $size);
103              
104 8 100       79 $self->level($self->{grammar} ? -1 : -2);
105              
106 8         19 my $if = "$self->{file}.index";
107 8         10 my ($gf, $gc);
108 8 100 66     86 if ($self->{gedcom}{read_only} &&
      100        
      66        
109             defined ($gf = -M $self->{file}) &&
110             defined ($gc = -M $if) && $gc < $gf) {
111 1 50       16 if (! open I, $if) {
112 0         0 die "Can't open $if: $!";
113             } else {
114 1         4 my $g = $self->{gedcom}{grammar}->structure("GEDCOM");
115 1         13 while () {
116 146         217 my @vals = split /\|/;
117             my $record =
118             Gedcom::Record->new(
119             gedcom => $self->{gedcom},
120 146         256 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       236 $record->{xref} = $vals[1] if length $vals[1];
128 146 100       157 $record->{value} = $vals[2] if length $vals[2];
129 146         134 my $class = $self->{gedcom}{types}{$vals[0]};
130 146 100       196 bless $record, "Gedcom::$class" if $class;
131 146         86 push @{$self->{items}}, $record;
  146         356  
132             }
133 1 50       8 close I or warn "Can't close $if";
134             }
135             }
136              
137 8 100       9 unless (@{$self->{items}}) {
  8         28  
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         39 while (my $item = $self->next_item($self)) {
144 905 100       1121 if ($self->{grammar}) {
145 876         836 my $tag = $item->{tag};
146 876         2129 my @g = $self->{grammar}->item($tag);
147             # print "<$tag> => <@g>\n";
148 876 50       1107 if (@g) {
149 876         1917 $self->parse($item, $g[0]);
150 876         642 push @{$self->{items}}, $item;
  876         1088  
151 876         891 $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         21 push @{$self->{items}}, $item;
  29         41  
160 29         24 $count++;
161             }
162             return undef
163             if ref $item &&
164             $callback &&
165             !$callback->($title, $txt1, "Record $count line " . $item->{line},
166 905 50 33     3804 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     59 if ($self->{gedcom}{read_only} && defined $gf &&
      66        
      66        
177             (! defined $gc || $gc > $gf)) {
178 1 50       89 if (! open I, ">$if") {
179 0         0 warn "Can't open $if";
180             } else {
181 1         2 for my $item (@{$self->{items}}) {
  1         3  
182 146 100       118 print I join("|", map { $item->{$_} || "" }
  730         1187  
183             qw(tag xref value line cpos));
184 146         156 print I "\n";
185             }
186 1 50       57 close I or warn "Can't close $if";
187             }
188             }
189              
190 8         22 $self;
191             }
192              
193             sub add_items {
194 13376     13376 1 9429 my $self = shift;
195 13376         11176 my ($item, $parse) = @_;
196             # print "adding items to: "; $item->print;
197 13376 100 100     44671 if (!$parse &&
      66        
      66        
198             $item->{level} >= 0 &&
199             $self->{gedcom}{read_only} &&
200             $self->{gedcom}{grammar}) {
201             # print "ignoring items\n";
202 2378         2761 $self->skip_items($item);
203             } else {
204 10998 50 66     19797 if ($parse && $self->{gedcom}{read_only} && $self->{gedcom}{grammar}) {
      66        
205             # print "reading items\n";
206 3110 50       4170 if (defined $item->{cpos}) {
207 3110         4666 seek($self->{fh}, $item->{cpos}, 0);
208 3110         3485 $. = $item->{line};
209             }
210             }
211 10998         11006 $item->{items} = [];
212 10998         14801 while (my $next = $self->next_item($item)) {
213 8462 100       11410 unless (ref $next) {
214             # The grammar requires a single selection from its items
215 73         69 $item->{selection} = 1;
216 73         120 next;
217             }
218 8389         6932 my $level = $item->{level};
219 8389         5727 my $next_level = $next->{level};
220 8389 100 66     21184 if (!defined $next_level || $next_level <= $level) {
221 200         116 $self->{stored_item} = $next;
222             # print "stored ***********************************\n";
223 200         185 return;
224             } else {
225 8189 50       11167 warn "$self->{file}:$item->{line}: " .
226             "Can't add level $next_level to $level\n"
227             if $next_level > $level + 1;
228 8189         5040 push @{$item->{items}}, $next;
  8189         19736  
229             }
230             }
231 10798 100       20494 $item->{_items} = 1 unless $item->{gedcom}{read_only};
232             }
233             }
234              
235             sub skip_items {
236 2378     2378 0 1634 my $self = shift;
237 2378         1656 my ($item) = @_;
238 2378         2019 my $level = $item->{level};
239 2378         2814 my $cpos = $item->{cpos} = tell $self->{fh};
240             # print "skipping items to level $level at $item->{line}:$cpos\n";
241 2378         1666 my $fh = $self->{fh};
242 2378         5378 while (my $l = <$fh>) {
243 4522         3345 chomp $l;
244             # print "parsing <$l>\n";
245 4522 100       10625 if (my ($lev) = $l =~ /^\s*(\d+)/) {
246 4079 100       5627 if ($lev <= $level) {
247             # print "pushing <$l>\n";
248 2377         4815 seek($self->{fh}, $cpos, 0);
249 2377         2813 $.--;
250 2377         3469 last;
251             }
252             }
253 2145         3941 $cpos = tell $self->{fh};
254             }
255             }
256              
257             sub next_item {
258 20172     20172 1 14063 my $self = shift;
259 20172         14035 my ($item) = @_;
260 20172         19127 my $bpos = tell $self->{fh};
261 20172         17407 my $bline = $.;
262             # print "At $bpos:$bline\n";
263 20172         11757 my $rec;
264 20172         14264 my $fh = $self->{fh};
265 20172 100 33     54873 if ($rec = $self->{stored_item}) {
    100 66        
266 200         148 $self->{stored_item} = undef;
267             } elsif ((!$rec || !$rec->{level}) && (my $line = $self->next_text_line)) {
268             # TODO - tidy this up
269 18783         15646 my $line_number = $.;
270             # print "line $line_number is <$line>";
271 18783 100       161029 if (my ($structure) = $line =~ /^\s*(\w+): =\s*$/) {
    100          
    50          
272 29         40 $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     59872 if ($level eq "n" || $level > $item->{level}) {
331 9065 50       10928 unless ($rec) {
332 9065         12213 $rec = $self->new(line => $line_number);
333             $rec->{gedcom} = $self->{gedcom}
334 9065 100       17453 if $self->{gedcom}{grammar};
335             }
336 9065 100       19088 $rec->{level} = ($level eq "n" ? 0 : $level) if defined $level;
    50          
337 9065 50       13153 $rec->{xref} = $xref =~ /^\@(.+)\@$/ ? $1 : $xref
    100          
338             if defined $xref;
339 9065 100       13286 $rec->{tag} = $tag if defined $tag;
340 9065 100 66     17774 $value .= $space if defined $space && $self->{grammar};
341 9065 100 33     17928 $value .= $star if defined $star && $self->{grammar};
342 9065 100       22919 $value =~ s/[\r\n]+$// if defined $value;
343             # print STDERR "value: [$value]\n";
344 9065 100       29541 $rec->{value} = ($rec->{pointer} = $value =~ /^\@(.+)\@$/)
    100          
345             ? $1
346             : $value
347             if defined $value;
348 9065 100       11612 $rec->{min} = $min if defined $min;
349 9065 100       13567 $rec->{max} = $max if defined $max;
350             } else {
351             # print " -- pushing back\n";
352 9616         17308 seek($fh, $bpos, 0);
353 9616         11356 $. = $bline;
354             }
355             } elsif ($line =~ /^\s*[\[\|\]]\s*(?:\/\*.*\*\/\s*)?$/) {
356             # The grammar requires a single selection from its items.
357 73         157 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     65017 if $rec && defined $rec->{level} && ($rec->{level} > $item->{level});
      100        
369 20099         33967 $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 13659 my $self = shift;
381 19972         13557 my $line = "";
382 19972         13751 my $fh = $self->{fh};
383 19972   100     185931 $line = <$fh> until !defined $line || $line =~ /\S/;
384 19972         51401 $line;
385             }
386              
387             sub write {
388 7647     7647 1 4741 my $self = shift;
389 7647         5600 my ($fh, $level, $flush) = @_;
390 7647   100     9197 $level ||= 0;
391 7647         4325 my @p;
392 7647 100 66     23265 push @p, $level . " " x $level unless $flush || $level < 0;
393             push @p, "\@$self->{xref}\@" if defined $self->{xref} &&
394 7647 100 66     13689 length $self->{xref};
395 7647 100       12886 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     27582 length $self->{value};
    100          
    100          
402 7647         14557 $fh->print("@p");
403 7647 100       29426 $fh->print("\n") unless $level < 0;
404 7647         16793 for my $c (0 .. @{$self->_items} - 1) {
  7647         7339  
405 7640         13052 $self->{items}[$c]->write($fh, $level + 1, $flush);
406             $fh->print("\n") if $level < 0 &&
407 7640 100 100     11951 $c < @{$self->{items}} - 1;
  886         2672  
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 65558     65558 1 44210 my $self = shift;
495 65558         51618 my ($tag, $count) = @_;
496 65558 100 100     142871 if (wantarray && !$count) {
497 55487         34139 return grep { $_->{tag} eq $tag } @{$self->_items};
  336625         408397  
  55487         54195  
498             } else {
499 10071 100       11771 $count = 1 unless $count;
500 10071         5963 for my $c (@{$self->_items}) {
  10071         9879  
501 10555 100 100     37874 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 1 my $self = shift;
523              
524 1         3 my $i = "$self";
525 1         2 my @records = ($self->{gedcom}{record});
526              
527 1         4 while (@records) {
528 1         1 my $r = shift @records;
529 1         1 for (@{$r->_items}) {
  1         3  
530 8 100       15 return $r if $i eq "$_";
531 7         7 push @records, $r;
532             }
533             }
534              
535             undef
536 0         0 }
537              
538             sub delete {
539 1     1 1 1 my $self = shift;
540 1         6 my $parent = $self->parent;
541 1 50       3 return unless $parent;
542 1         4 $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         6 my $i = "$item";
550 3         3 my $n = 0;
551 3         3 for (@{$self->_items}) {
  3         5  
552 16 100       31 last if $i eq "$_";
553 13         8 $n++;
554             }
555              
556 3 50       3 return 0 unless $n < @{$self->{items}};
  3         10  
557              
558             # print "deleting item $n of $#{$self->{items}}\n";
559 3         3 splice @{$self->{items}}, $n, 1;
  3         7  
560 3 100       9 delete $self->{gedcom}{xrefs}{$item->{xref}} if defined $item->{xref};
561              
562 3         15 1
563             }
564              
565             for my $func (qw(level xref tag value pointer min max gedcom file line)) {
566 11     11   70 no strict "refs";
  11         16  
  11         2968  
567             *$func = sub {
568 31219     31219   25188 my $self = shift;
569 31219 100       35776 $self->{$func} = shift if @_;
570 31219         84537 $self->{$func}
571             }
572             }
573              
574             sub full_value {
575 66257     66257 1 42543 my $self = shift;
576 66257         53225 my $value = $self->{value};
577 66257 50       110125 $value =~ s/[\r\n]+$// if defined $value;
578 66257         38962 for my $item (@{$self->_items}) {
  66257         61173  
579 164 50       324 my $v = defined $item->{value} ? $item->{value} : "";
580 164         197 $v =~ s/[\r\n]+$//;
581 164 100       277 $value .= "\n$v" if $item->{tag} eq "CONT";
582 164 100       272 $value .= $v if $item->{tag} eq "CONC";
583             }
584             $value
585 66257         94264 }
586              
587             sub _items {
588 238766     238766   151976 my $self = shift;
589             $self->{gedcom}{record}->add_items($self, 1)
590 238766 100 100     367665 if !defined $self->{_items} && $self->{level} >= 0;
591 238766         157540 $self->{_items} = 1;
592             $self->{items}
593 238766         364475 }
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 1140 my $self = shift;
602 1758         1642 delete $self->{_items};
603 1758         3762 delete $self->{items};
604             }
605              
606             1;
607              
608             __END__