File Coverage

blib/lib/Gedcom/Item.pm
Criterion Covered Total %
statement 222 306 72.5
branch 129 198 65.1
condition 76 140 54.2
subroutine 19 27 70.3
pod 19 21 90.4
total 465 692 67.2


line stmt bran cond sub pod time code
1             # Copyright 1998-2013, Paul Johnson (paul@pjcj.net)
2              
3             # This software is free. It is licensed under the same terms as Perl itself.
4              
5             # The latest version of this software should be available from my homepage:
6             # http://www.pjcj.net
7              
8             # documentation at __END__
9              
10 11     11   64 use strict;
  11         24  
  11         392  
11              
12             require 5.005;
13              
14             package Gedcom::Item;
15              
16 11     11   55 use Symbol;
  11         15  
  11         528  
17              
18 11     11   53 use vars qw($VERSION);
  11         18  
  11         32279  
19             $VERSION = "1.20";
20              
21             sub new {
22 9329     9329 1 11220 my $proto = shift;
23 9329   66     16560 my $class = ref($proto) || $proto;
24 9329         33021 my $self = {
25             level => -3,
26             file => "*",
27             line => 0,
28             items => [],
29             @_
30             };
31 9329         14931 bless $self, $class;
32 9329 100 66     26031 $self->read if $self->{file} && $self->{file} ne "*";
33 9329         14155 $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 18 my $self = shift;
58              
59             # $self->{fh} = FileHandle->new($self->{file})
60 8         41 my $fh = $self->{fh} = gensym;
61 8 50       489 open $fh, $self->{file} or die "Can't open file $self->{file}: $!\n";
62              
63             # try to determine encoding
64 8         32 my $encoding = "unknown";
65 8         17 my $bom = 0;
66 8         150 my $line1 = <$fh>;
67 8 50       43 if ($line1 =~ /^\xEF\xBB\xBF/) {
68 0         0 $encoding = "utf-8";
69 0         0 $bom = 1;
70             } else {
71 8         38 while (<$fh>) {
72 70 100       317 if (my ($char) = /\s*1\s+CHAR\s+(.*?)\s*$/i) {
73 8 50       36 $encoding = $char =~ /utf\W*8/i ? "utf-8" : $char;
74 8         46 last;
75             }
76             }
77             }
78              
79             # print "encoding is [$encoding]\n";
80 8 100       75 $self->{gedcom}->set_encoding($encoding) if $self->{gedcom};
81 8 50 33     38 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         34 binmode $fh;
87             }
88              
89             # find out how big the file is
90 8         32 seek($fh, 0, 2);
91 8         19 my $size = tell $fh;
92 8 50       27 seek($fh, $bom ? 3 : 0, 0); # skip BOM
93 8         30 $. = 0;
94              
95             # initial callback
96 8         18 my $callback = $self->{callback};;
97 8         15 my $title = "Reading";
98 8         22 my $txt1 = "Reading $self->{file}";
99 8         16 my $count = 0;
100             return undef
101 8 50 33     25 if $callback &&
102             !$callback->($title, $txt1, "Record $count", tell $fh, $size);
103              
104 8 100       88 $self->level($self->{grammar} ? -1 : -2);
105              
106 8         22 my $if = "$self->{file}.index";
107 8         17 my ($gf, $gc);
108 8 100 66     103 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         5 my $g = $self->{gedcom}{grammar}->structure("GEDCOM");
115 1         12 while () {
116 146         289 my @vals = split /\|/;
117             my $record =
118             Gedcom::Record->new(
119             gedcom => $self->{gedcom},
120 146         287 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       206 $record->{value} = $vals[2] if length $vals[2];
129 146         184 my $class = $self->{gedcom}{types}{$vals[0]};
130 146 100       238 bless $record, "Gedcom::$class" if $class;
131 146         146 push @{$self->{items}}, $record;
  146         370  
132             }
133 1 50       33 close I or warn "Can't close $if";
134             }
135             }
136              
137 8 100       21 unless (@{$self->{items}}) {
  8         29  
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         43 while (my $item = $self->next_item($self)) {
144 905 100       1570 if ($self->{grammar}) {
145 876         1147 my $tag = $item->{tag};
146 876         2219 my @g = $self->{grammar}->item($tag);
147             # print "<$tag> => <@g>\n";
148 876 50       1509 if (@g) {
149 876         2325 $self->parse($item, $g[0]);
150 876         989 push @{$self->{items}}, $item;
  876         1446  
151 876         1227 $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         32 push @{$self->{items}}, $item;
  29         46  
160 29         33 $count++;
161             }
162             return undef
163             if ref $item &&
164             $callback &&
165             !$callback->($title, $txt1, "Record $count line " . $item->{line},
166 905 50 33     3578 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     56 if ($self->{gedcom}{read_only} && defined $gf &&
      66        
      66        
177             (! defined $gc || $gc > $gf)) {
178 1 50       130 if (! open I, ">$if") {
179 0         0 warn "Can't open $if";
180             } else {
181 1         4 for my $item (@{$self->{items}}) {
  1         4  
182 146 100       181 print I join("|", map { $item->{$_} || "" }
  730         1412  
183             qw(tag xref value line cpos));
184 146         252 print I "\n";
185             }
186 1 50       69 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 14810 my $self = shift;
195 13376         18116 my ($item, $parse) = @_;
196             # print "adding items to: "; $item->print;
197 13376 100 100     44495 if (!$parse &&
      100        
      66        
198             $item->{level} >= 0 &&
199             $self->{gedcom}{read_only} &&
200             $self->{gedcom}{grammar}) {
201             # print "ignoring items\n";
202 2378         3759 $self->skip_items($item);
203             } else {
204 10998 50 100     23348 if ($parse && $self->{gedcom}{read_only} && $self->{gedcom}{grammar}) {
      66        
205             # print "reading items\n";
206 3110 50       5048 if (defined $item->{cpos}) {
207 3110         6007 seek($self->{fh}, $item->{cpos}, 0);
208 3110         4862 $. = $item->{line};
209             }
210             }
211 10998         16638 $item->{items} = [];
212 10998         18259 while (my $next = $self->next_item($item)) {
213 8462 100       14155 unless (ref $next) {
214             # The grammar requires a single selection from its items
215 73         110 $item->{selection} = 1;
216 73         131 next;
217             }
218 8389         11020 my $level = $item->{level};
219 8389         9640 my $next_level = $next->{level};
220 8389 100 66     20427 if (!defined $next_level || $next_level <= $level) {
221 200         232 $self->{stored_item} = $next;
222             # print "stored ***********************************\n";
223 200         278 return;
224             } else {
225 8189 50       13212 warn "$self->{file}:$item->{line}: " .
226             "Can't add level $next_level to $level\n"
227             if $next_level > $level + 1;
228 8189         8651 push @{$item->{items}}, $next;
  8189         21802  
229             }
230             }
231 10798 100       23424 $item->{_items} = 1 unless $item->{gedcom}{read_only};
232             }
233             }
234              
235             sub skip_items {
236 2378     2378 0 2664 my $self = shift;
237 2378         2760 my ($item) = @_;
238 2378         2884 my $level = $item->{level};
239 2378         3806 my $cpos = $item->{cpos} = tell $self->{fh};
240             # print "skipping items to level $level at $item->{line}:$cpos\n";
241 2378         2677 my $fh = $self->{fh};
242 2378         5768 while (my $l = <$fh>) {
243 4522         5607 chomp $l;
244             # print "parsing <$l>\n";
245 4522 100       11883 if (my ($lev) = $l =~ /^\s*(\d+)/) {
246 4079 100       6923 if ($lev <= $level) {
247             # print "pushing <$l>\n";
248 2377         5977 seek($self->{fh}, $cpos, 0);
249 2377         3808 $.--;
250 2377         4227 last;
251             }
252             }
253 2145         4647 $cpos = tell $self->{fh};
254             }
255             }
256              
257             sub next_item {
258 20172     20172 1 23549 my $self = shift;
259 20172         24773 my ($item) = @_;
260 20172         28412 my $bpos = tell $self->{fh};
261 20172         26631 my $bline = $.;
262             # print "At $bpos:$bline\n";
263 20172         19952 my $rec;
264 20172         23228 my $fh = $self->{fh};
265 20172 100 33     52005 if ($rec = $self->{stored_item}) {
    100 66        
266 200         242 $self->{stored_item} = undef;
267             } elsif ((!$rec || !$rec->{level}) && (my $line = $self->next_text_line)) {
268             # TODO - tidy this up
269 18783         26268 my $line_number = $.;
270             # print "line $line_number is <$line>";
271 18783 100       175835 if (my ($structure) = $line =~ /^\s*(\w+): =\s*$/) {
    100          
    50          
272 29         55 $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     61205 if ($level eq "n" || $level > $item->{level}) {
331 9065 50       14030 unless ($rec) {
332 9065         15249 $rec = $self->new(line => $line_number);
333             $rec->{gedcom} = $self->{gedcom}
334 9065 100       19231 if $self->{gedcom}{grammar};
335             }
336 9065 100       20700 $rec->{level} = ($level eq "n" ? 0 : $level) if defined $level;
    50          
337 9065 50       16172 $rec->{xref} = $xref =~ /^\@(.+)\@$/ ? $1 : $xref
    100          
338             if defined $xref;
339 9065 100       16840 $rec->{tag} = $tag if defined $tag;
340 9065 100 100     23890 $value .= $space if defined $space && $self->{grammar};
341 9065 100 66     23418 $value .= $star if defined $star && $self->{grammar};
342 9065 100       30924 $value =~ s/[\r\n]+$// if defined $value;
343             # print STDERR "value: [$value]\n";
344 9065 100       36699 $rec->{value} = ($rec->{pointer} = $value =~ /^\@(.+)\@$/)
    100          
345             ? $1
346             : $value
347             if defined $value;
348 9065 100       14774 $rec->{min} = $min if defined $min;
349 9065 100       14967 $rec->{max} = $max if defined $max;
350             } else {
351             # print " -- pushing back\n";
352 9616         23585 seek($fh, $bpos, 0);
353 9616         15825 $. = $bline;
354             }
355             } elsif ($line =~ /^\s*[\[\|\]]\s*(?:\/\*.*\*\/\s*)?$/) {
356             # The grammar requires a single selection from its items.
357 73         201 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     64945 if $rec && defined $rec->{level} && ($rec->{level} > $item->{level});
      100        
369 20099         39500 $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 23207 my $self = shift;
381 19972         22162 my $line = "";
382 19972         21493 my $fh = $self->{fh};
383 19972   100     177603 $line = <$fh> until !defined $line || $line =~ /\S/;
384 19972         57522 $line;
385             }
386              
387             sub write {
388 7647     7647 1 8527 my $self = shift;
389 7647         10418 my ($fh, $level, $flush) = @_;
390 7647   100     11802 $level ||= 0;
391 7647         7980 my @p;
392 7647 100 66     24678 push @p, $level . " " x $level unless $flush || $level < 0;
393             push @p, "\@$self->{xref}\@" if defined $self->{xref} &&
394 7647 100 66     17086 length $self->{xref};
395 7647 100       16060 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     27651 length $self->{value};
    100          
    100          
402 7647         21127 $fh->print("@p");
403 7647 100       48344 $fh->print("\n") unless $level < 0;
404 7647         32726 for my $c (0 .. @{$self->_items} - 1) {
  7647         10803  
405 7640         21534 $self->{items}[$c]->write($fh, $level + 1, $flush);
406             $fh->print("\n") if $level < 0 &&
407 7640 100 100     14519 $c < @{$self->{items}} - 1;
  886         2593  
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 73220 my $self = shift;
495 65558         88205 my ($tag, $count) = @_;
496 65558 100 100     141012 if (wantarray && !$count) {
497 55487         59165 return grep { $_->{tag} eq $tag } @{$self->_items};
  336625         608811  
  55487         74200  
498             } else {
499 10071 100       14900 $count = 1 unless $count;
500 10071         10721 for my $c (@{$self->_items}) {
  10071         14038  
501 10555 100 100     41209 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         3 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         6 for (@{$r->_items}) {
  1         3  
530 8 100       17 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 3 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         5 my ($item) = @_;
548              
549 3         7 my $i = "$item";
550 3         4 my $n = 0;
551 3         3 for (@{$self->_items}) {
  3         5  
552 16 100       28 last if $i eq "$_";
553 13         16 $n++;
554             }
555              
556 3 50       4 return 0 unless $n < @{$self->{items}};
  3         7  
557              
558             # print "deleting item $n of $#{$self->{items}}\n";
559 3         3 splice @{$self->{items}}, $n, 1;
  3         16  
560 3 100       11 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   87 no strict "refs";
  11         27  
  11         2819  
567             *$func = sub {
568 31219     31219   42843 my $self = shift;
569 31219 100       43996 $self->{$func} = shift if @_;
570 31219         91185 $self->{$func}
571             }
572             }
573              
574             sub full_value {
575 66257     66257 1 74420 my $self = shift;
576 66257         86774 my $value = $self->{value};
577 66257 50       136093 $value =~ s/[\r\n]+$// if defined $value;
578 66257         72373 for my $item (@{$self->_items}) {
  66257         84893  
579 164 50       360 my $v = defined $item->{value} ? $item->{value} : "";
580 164         294 $v =~ s/[\r\n]+$//;
581 164 100       307 $value .= "\n$v" if $item->{tag} eq "CONT";
582 164 100       319 $value .= $v if $item->{tag} eq "CONC";
583             }
584             $value
585 66257         121089 }
586              
587             sub _items {
588 238766     238766   262493 my $self = shift;
589             $self->{gedcom}{record}->add_items($self, 1)
590 238766 100 100     417136 if !defined $self->{_items} && $self->{level} >= 0;
591 238766         261270 $self->{_items} = 1;
592             $self->{items}
593 238766         441035 }
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 2117 my $self = shift;
602 1758         2461 delete $self->{_items};
603 1758         4173 delete $self->{items};
604             }
605              
606             1;
607              
608             __END__