File Coverage

blib/lib/MAB2/Record/Base.pm
Criterion Covered Total %
statement 174 202 86.1
branch 50 66 75.7
condition 6 9 66.6
subroutine 15 20 75.0
pod 0 10 0.0
total 245 307 79.8


line stmt bran cond sub pod time code
1             package MAB2::Record::Base;
2             our $VERSION = '0.03';
3              
4 2     2   1559 use Encode::MAB2;
  2         6  
  2         117  
5              
6             =head1 NAME
7              
8             MAB2::Record::Base - Access an MAB2 record
9              
10             =head1 SYNOPSIS
11              
12             use MAB2::Record::Base;
13              
14             # Constructor
15             my $mab2raw = "00296nM2.01200024 k001 1000016-1\c^002a19890418".
16             "\c^004 20010812\c^028b1000016-1\c^029 HK00158537\c^030 aa1dc".
17             "|m\c^036aIT\c^066 |\c^070 9002\c^070aHBZ\c^800 Accademia Na".
18             "zionale di San Luca \c^810 Accademia di San Luca
19             "ccademia Nazionale di San Luca>\c^850aReale Accademia di San Lu".
20             "ca \c^852a45335-3\c^\c]";
21             my $mab2 = MAB2::Record::Base->new($mab2raw);
22             # $mab2 now blessed into MAB2::Record::gkd because it is a gkd record
23              
24             # various representations:
25             print $mab2->id; # just the ID
26             print $mab2->readable; # quite readable
27             print $mab2->as_string; # the raw string we put into it
28             print $mab2->dump; # only useful for debugging the module itself
29              
30              
31             =head1 DESCRIPTION
32              
33             C is the common base class for all classes
34             implementing MAB2 record types:
35              
36             MAB2::Record::gkd
37             MAB2::Record::lokal
38             MAB2::Record::pnd
39             MAB2::Record::swd
40             MAB2::Record::titel
41              
42             The constructor C takes a raw MAB2 record as argument and returns
43             an object which is blessed into one of the five above listed classes.
44             Some level of proficiency in dealing with MAB2 records is needed for
45             the user of this module for further processing of the objects. It is
46             recommended to use C to get acquainted with the raw format
47             of the created objects.
48              
49             For illustration purpose, here is the Data::Dumper output of the full
50             object into which the sample record from the SYNOPSIS section is
51             transformed:
52              
53             $VAR1 = bless( [
54             '...',
55             undef,
56             [
57             {
58             'nicht_benutzt' => [
59             ' '
60             ],
61             'datenanfangsadresse' => [
62             '00024'
63             ],
64             'satztyp' => [
65             'k',
66             'Koerperschaftsnamensatz (MAB-GKD)'
67             ],
68             'versionsangabe' => [
69             'M2.0'
70             ],
71             'satzstatus' => [
72             'n',
73             'neuer Datensatz'
74             ],
75             'indikatorlaenge' => [
76             '1'
77             ],
78             'satzlaenge' => [
79             '00296'
80             ],
81             'teilfeldkennungslaenge' => [
82             '2'
83             ]
84             },
85             [
86             [
87             '001',
88             ' ',
89             '1000016-1',
90             'identifikationsnummer des datensatzes'
91             ],
92             [
93             '002',
94             'a',
95             '19890418',
96             'datum der ersterfassung / fremddatenuebernahme'
97             ],
98             [
99             '004',
100             ' ',
101             '20010812',
102             'erstellungsdatum des austauschsatzes'
103             ],
104             ...
105             [
106             '810',
107             ' ',
108             'Accademia di San Luca
109             Luca>',
110             '1. verweisungsform zum namen der koerperschaft'
111             ],
112             [
113             '850',
114             'a',
115             'Reale Accademia di San Luca ',
116             '1. frueherer, zeitweiser oder spaeterer name der koerper
117             schaft'
118             ],
119             [
120             '852',
121             'a',
122             '45335-3',
123             'identifikationsnummer des 1. frueheren, zeitweisen oder
124             spaeteren namens'
125             ]
126             ]
127             ],
128             '...'
129             ], 'MAB2::Record::gkd' );
130              
131              
132             Please note that the object contains both the original string in its
133             own byte oriented encoding and all fields in Unicode. The conversion
134             is done by the C module.
135              
136             The normal way of accessing MAB2 records is through the use of either
137             the C or C class. The C
138             class binds an MAB2 file to an array and each record in the original
139             MAB2 file to an array element starting with element 0. The
140             C class binds to a hash with the MAB2 identifier as the
141             key.
142              
143             =head1 Overloading
144              
145             The tied objects have their stringifier overloaded to the
146             C method so that
147              
148             print $tie[1234];
149              
150             always prints the record as the unaltered original input record.
151              
152             =head1 SEE ALSO
153              
154             C, C, C
155              
156              
157             =cut
158              
159 2     2   12 use constant RAW => 0;
  2         5  
  2         153  
160 2     2   10 use constant INTERNALS => 1; # maybe nonsense: sometimes recno,
  2         5  
  2         88  
161             # sometimes id, whatever the *caller*
162             # wants to have there
163 2     2   9 use constant STRUCT => 2;
  2         4  
  2         80  
164 2     2   11 use constant DUMPVALUE => 3;
  2         3  
  2         80  
165              
166 2     2   10 use strict;
  2         4  
  2         63  
167 2     2   1602 use overload '""' => "as_string";
  2         1084  
  2         15  
168              
169 2     2   10530 use Dumpvalue;
  2         12115  
  2         6282  
170             our $DV = Dumpvalue->new(unctrl => "quote");
171              
172             our $DEBUG;
173             $DEBUG = 1 unless defined $DEBUG;
174             our $NAMESPACE = "MAB2::Record";
175             my $KDocs;
176             my $RDocs;
177             our(%type2pack) = qw(
178             h titel
179             y titel
180             u titel
181             v titel
182             p pnd
183             t pnd
184             k gkd
185             w gkd
186             r swd
187             s swd
188             x swd
189             l lokal
190             e lokal
191             z lokal
192             );
193              
194             {
195             local $/;
196             my $strdocs = ;
197             close DATA;
198             ($KDocs, $RDocs) = __PACKAGE__->parsedoc($strdocs);
199             }
200              
201             {
202             my %seen;
203             for my $pack (grep !$seen{$_}++, values %type2pack) {
204             my $req = "MAB2/Record/$pack.pm";
205             require $req;
206             }
207             }
208              
209             sub parsedoc {
210 12     12 0 25 my $self = shift;
211 12         15 my $strdocs = shift;
212              
213 12         328 $strdocs =~ s/ ^ .*?\n (?=\d ) //sx; # remove header
214              
215 12         2353 my @docs = $strdocs =~ /\G(\d.*?\n)(?=\d|$)/sgc; # split into subdocuments
216 12         50 my @kennungdocs;
217             my @realrecdocs;
218 12         25 for my $doc (@docs) {
219 1162         7960 $doc =~ s/\s+\z//;
220 1162 100       4242 if ($doc =~ /^\d\d?\s/) {
    100          
    100          
221 16         39 push @kennungdocs, [$doc];
222             } elsif ($doc =~ /^\d\d\d-/) {
223 148         211 next;
224             } elsif ($doc =~ /^\d--/) {
225 28         45 next;
226             } else {
227 970         2255 push @realrecdocs, [$doc];
228             }
229             }
230 12         26 my %seen = ();
231 12         34 for my $k (0..$#kennungdocs) {
232 16         26 my $kdoc = $kennungdocs[$k];
233 16         21 my $doc = $kdoc->[0];
234 16         59 my($line1,$kexplain) = $doc =~ /(^[^\n]+)(?:\n(.+))?/s;
235             # print "line1: $line1\n";
236 16         63 my($start,$to,$name) = $line1 =~ m{ ^ (\d+) (.{8}) \s+ (.*) }x; #
237 16         21 my $length = 0;
238 16 100       51 if ($to =~ /-\s(\d+)/) {
239 8         22 $length = $1 - $start;
240             }
241 16         18 $length++; # 0->1, 4->5 :-)
242 16         23 $name = lc $name;
243 16         35 $name =~ s/[^a-z0-9_]/_/g;
244 16 50       58 die if $seen{$name}++;
245             # print "start: $start\n";
246             # print "name: $name\n";
247             # print "kexplain: $kexplain\n" if defined $kexplain;
248 16         18 my %kexplain;
249 16 100 66     52 if ($kexplain && length $kexplain) {
250 4         463 my @code = $kexplain =~ /\G\s+([a-z])\s=\s(.*?)(?=\n\s+[a-z]\s=\s|$)/sgc;
251 4         34 %kexplain = @code;
252 4         14 for my $e (keys %kexplain) {
253 44         78 $kexplain{$e} =~ s/^\s+//;
254 44         108 $kexplain{$e} =~ s/\s+$//;
255 44         213 $kexplain{$e} =~ s/\s+/ /gs;
256             # print "ex $e: $kexplain{$e}\n";
257             }
258             }
259 16         32 $kdoc->[1] = $start;
260 16         38 $kdoc->[2] = $length;
261 16         28 $kdoc->[3] = $name; # all uppercase hurts
262 16         40 $kdoc->[4] = \%kexplain;
263             }
264 12         22 %seen = ();
265 12         45 local $| = 1;
266 12         26 for my $r (0..$#realrecdocs) {
267 970         1305 my $rdoc = $realrecdocs[$r];
268 970         1281 my $doc = $rdoc->[0];
269             # print "========>\n", $doc, "\n<========";
270              
271             # very different from above, because "line1" can be more than one line
272 970         5358 my($line1,$rexplain) = $doc =~ /^((?:[^\n]|\n(?!\n))+)(?:\n\n(.+))?/s;
273 970         1680 $line1 =~ s/^\s+//;
274 970         2396 $line1 =~ s/\s+$//;
275 970         4702 $line1 =~ s/\s+/ /g;
276 970         2511 $line1 =~ s/^(\d+)\s+//;
277 970         1628 my($codenr) = $1;
278 970 50       2871 die "seeing again $codenr???" if $seen{$codenr}++;
279 970 100       1501 if ($rexplain) {
280             # $rexplain =~ s/^\s+Indikator:\s+//g;
281 628         2103 $rexplain =~ s/^\s+//g;
282             } else {
283 342         430 $rexplain = "";
284             }
285             # print "self[$self]codenr[$codenr]rexplain[$rexplain]\n" if defined $rexplain;
286 970         1708 $rdoc->[1] = $codenr;
287 970         1271 $rdoc->[2] = undef;
288 970         1490 $rdoc->[3] = $line1;
289 970         1989 $rdoc->[4] = $rexplain; # XXX this needs to become more useful
290             # than just plain text
291             }
292 12         22 my $end = $#realrecdocs;
293 12         28 for my $r (0..$end) {
294 970 100 100     3285 next unless $realrecdocs[$r][4] && $realrecdocs[$r][4] eq "...";
295 22         46 my $after_yadda = $realrecdocs[$r+1][1];
296             # print "Found >>...<< in $realrecdocs[$r][1], need to fill upto $after_yadda";
297              
298             # Ich will vielleicht eine Zahl in diesem Text hochzaehlen
299 22 100       140 if (my($foundnumber) = $realrecdocs[$r][3] =~ /(\d+)/) {
300 18         22 my $step = 1;
301 18         30 my $rr3 = $realrecdocs[$r][3];
302 18 100       95 if ($rr3 eq "ZUSAETZLICHE ANGABEN ZUR 2. VERWEISUNGSFORM") {
    100          
    100          
    100          
    100          
    100          
    50          
303 2         4 $step = 2;
304             } elsif ($rr3 eq "IDENTIFIKATIONSNUMMER DES 2. FRUEHEREN, ZEITWEISEN ODER SPAETEREN NAMENS DER KOERPERSCHAFT") {
305             # gkd
306 2         4 $step = 3;
307             } elsif ($rr3 eq "ERLAEUTERUNGEN ZUR 2. SCHLAGWORTKETTE") {
308 6         9 $step = 5;
309             } elsif ($rr3 eq "KOERPERSCHAFT, BEI DER DIE 2. PERSON BESCHAEFTIGT IST") {
310 2         8 $step = 4;
311             } elsif ($rr3 eq "IDENTIFIKATIONSNUMMER DES KOERPERSCHAFTSNAMENSATZES DER 2. KOERPERSCHAFT") {
312 2         4 $step = 2;
313             } elsif ($rr3 eq "ZUSAETZE ZUM 2. PARALLELSACHTITEL") {
314 2         13 $step = 4;
315             } elsif ($rr3 eq "SACHTITEL DER 2. NE") {
316 2         4 $step = 6;
317             }
318 18         33 my $before_yadda = $realrecdocs[$r][1];
319 18         35 for my $offset (1..$step) {
320             # warn "offset[$offset]";
321 72         100 my $first = $before_yadda + $offset;
322 72         91 my $blueprint = $first - $step;
323             # warn "first[$first]blueprint[$blueprint]";
324 72         78 my $blueprintrec;
325 72         100 for my $rr (@realrecdocs) {
326             # warn "DEBUG: rr1[$rr->[1]]";
327 7792 100       15455 next unless $rr->[1] == $blueprint;
328 66         71 $blueprintrec = $rr;
329 66         85 last;
330             }
331 72 100       148 next unless $blueprintrec;
332 66 50       173 die "Unexpected blueprintrec3[$blueprintrec->[3]]"
333             unless $blueprintrec->[3] =~ /2/;
334 66         105 my $sprintf = $blueprintrec->[3];
335 66         182 $sprintf =~ s/2/%d/;
336 66         94 my $foundnumber = 2;
337 66         158 for (my $nr = $first; $nr<$after_yadda; $nr+=$step) {
338 694         3528 push @realrecdocs, [
339             ">>>generated<<<",
340             sprintf("%03d", $nr),
341             undef,
342             sprintf($sprintf,++$foundnumber),
343             undef
344             ];
345             }
346             }
347             } else {
348 4         14 for my $i ($realrecdocs[$r][1]+1..$after_yadda-1) { # $after_yadda (sans -1) XXX
349 16         72 push @realrecdocs, [
350             ">>>same as $realrecdocs[$r][1]<<<",
351             sprintf("%03d", $i),
352             undef,
353             $realrecdocs[$r][3],
354             undef
355             ];
356             }
357             }
358             }
359             # Now realrecdocs is unsorted, but we prefer it as a hash anyway
360 12         16 my %realrecdocs;
361 12         28 for my $rdoc (@realrecdocs) {
362 1680         2938 $realrecdocs{$rdoc->[1]} = $rdoc;
363             }
364              
365 12         261 return(\@kennungdocs,\%realrecdocs);
366             }
367              
368             sub new {
369 2     2 0 5283 my($me,$raw,$key) = @_;
370 2   33     14 my $self = bless [$raw,$key], ref $me || $me;
371 2 50       7 if ( my $pack = $self->_class() ) { # was $struct->[0]{satztyp}[0]
372 2         8 bless $self, "MAB2::Record::$pack";
373             } else {
374 0         0 die "Couldn't determine class.";
375             }
376 2         6 $self;
377             }
378              
379             sub as_string {
380 1     1 0 9 my($self) = @_;
381 1         53 $self->[RAW];
382             }
383              
384             sub readable {
385 2     2 0 5 my($self) = @_;
386 2         10 $self->_struct;
387 2         2 my @m;
388 2         3 my $base = $self->[STRUCT][0];
389 2         3 my $cont = $self->[STRUCT][1];
390 2         15 for my $k (sort keys %$base) {
391 16         20 my $v;
392 16 100       17 if (@{$base->{$k}}>1) {
  16         42  
393 4         6 $v = sprintf "%s (%s)", @{$base->{$k}};
  4         15  
394             } else {
395 12         17 $v = $base->{$k}[0];
396             }
397 16         58 push @m, sprintf "%-25s: %s", $k, $v;
398             }
399 2         7 for my $sr (@$cont) {
400 26         40 my $print = sprintf "%3s %1s %s [%s]", map { Dumpvalue::unctrl($_); } @$sr;
  104         576  
401 26         240 if (0 && $print =~ /[^\040-\177]/) {
402             $print .= sprintf("\n=%s\n=%s",
403             Encode::encode("ascii",$sr->[2],Encode::FB_XMLCREF()),
404             $sr->[2],
405             );
406             }
407 26         49 push @m, $print;
408             }
409 2         23 join "\n", @m;
410             }
411              
412             sub dump {
413 0     0 0 0 my($self) = @_;
414 0         0 require Data::Dumper;
415 0         0 $Data::Dumper::Indent = 1;
416 0         0 $self->_struct;
417 0         0 my $x = Data::Dumper::Dumper($self);
418 0         0 $x =~ s/\[\n\s+/[/g;
419 0         0 $x =~ s/\n\s+\]/]/g;
420 0         0 $x =~ s/',\n\s+'/', '/g;
421 0         0 $x;
422             }
423              
424             sub _class {
425 2     2   4 my $self = shift;
426 2         39 my $type = substr($self->[RAW],23,1);
427 2 50       7 warn "ALERT: type[$type]" unless exists $type2pack{$type};
428 2         8 $type2pack{$type};
429             }
430              
431             sub id {
432 0     0 0 0 my $self = shift;
433 0         0 my $id;
434 0         0 if (0) { # 228 secunden fuer Datei 12 (Keywords) ohne debug
435             # my $struct = $self->_struct;
436             # $id = $struct->[1][0][0] eq "001" ? $struct->[1][0][2] : die;
437             } else { # 67 secs fuer gleiche Arbeit, 852 secs fuer 01
438 0         0 my $raw = $self->as_string;
439 0         0 ($id) = substr($raw,28) =~ m/([^\c^\c]]+)/;
440             }
441             # warn "id[$id]";
442             # die Dumpvalue::unctrl("id1[$id1]id2[$id2]") unless $id1 eq $id2;
443 0         0 $id;
444             }
445              
446             sub _struct {
447 2     2   3 my $self = shift;
448 2 50       6 return $self->[STRUCT] if $self->[STRUCT];
449 2         3 my $struct;
450 2 50       9 if ($DEBUG) {
451 2         11 $self->[DUMPVALUE] = $DV->stringify($self->[RAW]);
452             }
453 2         142 my $derec = Encode::decode("MAB2",$self->[RAW]);
454 2         9986 pos $derec = 0;
455 2         8 for my $k (@$KDocs) {
456 16         31 my $re = "."x$k->[2];
457 16 50       198 $struct->[0]{$k->[3]}[0] = $1 if $derec =~ /\G($re)/gc;
458             ##########^ 0=base/kennungsdocs
459 16 50       38 if ($DEBUG) {
460 16 100       14 $struct->[0]{$k->[3]}[1] = $k->[4]{$1} if %{$k->[4]};
  16         66  
461             }
462             }
463             warn "ALERT: Datenanfangsadresse nicht 24!" unless
464 2 50       9 (my $daa = $struct->[0]{datenanfangsadresse}[0]) == 24;
465             # ^^^^^^^^^
466              
467             # avoid using stringdata in numeric context, because it turns on IOK
468             # or something and the next print prints "24" instead of "00024"
469              
470             # strp = structpart of the record
471 2         46 my(@strp) = $derec =~ / \G (\d\d\d) (.) ([^\c^]+) (?: \c] | \c^ )/xgc; #
472 2         6 my(@str);
473 2         6 while (@strp) {
474 26 50       57 die "Invalid strp" unless @strp >=3;
475 26         63 my $str = [ splice @strp, 0, 3 ];
476 26 50       60 if ($DEBUG) {
477             # die Bezeichnung des Feldes im "real" Record. Da dort alles
478             # Uppercase ist, muessen wir lc nehmen, sonst erschlaegt uns das
479 26 50       90 $str->[3] = $str->[0] ? lc $self->segmentname($str->[0]) : "UNDEF";
480             }
481 26         71 push @str, $str;
482             }
483 2         4 $struct->[1] = \@str;
484 2         7 $self->[STRUCT] = $struct;
485             }
486              
487             sub segmentname {
488 18     18 0 20 my $self = shift;
489 18         27 my $rec = shift;
490 18         90 $RDocs->{$rec}[3];
491             }
492              
493             sub subrecords {
494 0     0 0   my($self) = shift;
495 0           $self->_struct;
496 0           @{$self->[STRUCT][1]};
  0            
497             }
498              
499             sub subrecords_ref {
500 0     0 0   my($self) = shift;
501 0           $self->_struct;
502 0           $self->[STRUCT][1];
503             }
504              
505             sub date_004 {
506 0     0 0   my($self) = @_;
507 0           my $sr = $self->subrecords_ref;
508 0           for my $i (0..$#$sr) {
509 0 0         next unless $sr->[$i][0] eq "004";
510 0           return $sr->[$i][2];
511             }
512             }
513              
514             1;
515              
516             # segm000.txt
517             __DATA__