File Coverage

blib/lib/XML/Tape.pm
Criterion Covered Total %
statement 54 70 77.1
branch 10 24 41.6
condition 2 6 33.3
subroutine 10 11 90.9
pod n/a
total 76 111 68.4


line stmt bran cond sub pod time code
1             #
2             # $Id: Tape.pm,v 1.7 2005/09/01 08:19:27 patrick Exp $
3             #
4              
5             =head1 NAME
6              
7             XML::Tape - module for the manipulation of XMLtape archives
8              
9             =head1 SYNOPSIS
10              
11             use XML::Tape qw(:all);
12              
13             $tape = tapeopen('tape.xml','w');
14             $tape->add_record("info:archive_id/1", "2005-05-31", $xml_record);
15             $tape->tapeclose();
16              
17             $tape = tapeopen('tape.xml','r');
18             while ($record = $tape->get_record()) {
19             printf "id: %s\n" , $record->getIdentifier;
20             printf "date: %s\n" , $record->getDate;
21             printf "xml: %s\n", $record->getRecord;
22             }
23             $tape->tapeclose();
24              
25             =head1 DESCRIPTION
26              
27             The XMLtape provides a write-once/read-many XML wrapper for a
28             collection of XML documents. The wrapper provides an easy
29             storage format for big collections of XML files which can be
30             processed with off the shelf tools and validated against a
31             schema. The XMLtape is typically used in digital preservation
32             projects.
33              
34             =cut
35             package XML::Tape;
36 1     1   79824 use strict;
  1         2  
  1         55  
37             require Exporter;
38 1     1   5 use vars qw($VERSION);
  1         1  
  1         684  
39              
40             ( $VERSION ) = '$Revision: 1.7 $ ' =~ /\$Revision:\s+([^\s]+)/;;
41              
42             @XML::Tape::ISA = qw(Exporter);
43             @XML::Tape::EXPORT_OK = qw(tapeopen);
44             %XML::Tape::EXPORT_TAGS = (all => [qw(tapeopen)]);
45             $XML::Tape::SCHEMA_LOCATION = 'http://purl.lanl.gov/aDORe/schemas/2005-08/XMLtape.xsd';
46              
47             =head1 FUNCTIONS
48              
49             =over 4
50              
51             =item tapeopen($filename, $mode, [, @admin])
52              
53             Filename is the location of an XMLtape file or an opened
54             IO::Handle.
55             When mode is 'r' this function opens a XMLtape for reading.
56             When mode is 'w' this function creates a new XMLtape on disk.
57             Optionally an array of strings can be provided which contain in
58             XML format metadata about the XMLtape. E.g.
59              
60             tapeopen(
61             "tape.xml",
62             "w"
63             "2005-05-31"
64             );
65              
66             Returns a XMLtape instance on success or undef on error.
67              
68             =cut
69             sub tapeopen {
70 2     2   2289 my ($filename, $mode, @admin) = @_;
71              
72 2 50 33     19 die "usage: tapeopen(\$filename, \$mode, [\@admin])" unless ($filename && $mode =~ /^r|w$/);
73              
74 2 100       7 if ($mode eq 'w') {
75 1         9 return new XML::Tape::Writer($filename,@admin);
76             }
77             else {
78 1         207 my $identifier = new XML::Tape::Identifier;
79 0         0 my $namespace = $identifier->identify($filename);
80 0 0       0 if ($namespace eq 'http://library.lanl.gov/2005-01/STB-RL/tape/') {
    0          
81 0         0 return new XML::Tape::Reader::v2005_01($filename);
82             }
83             elsif ($namespace eq 'http://library.lanl.gov/2005-08/aDORe/XMLtape/') {
84 0         0 return new XML::Tape::Reader($filename);
85             }
86             else {
87 0         0 die "unknown tape version $namespace";
88             }
89             }
90              
91 0         0 return undef;
92             }
93              
94             package XML::Tape::Writer;
95 1     1   1453 use IO::File;
  1         1816  
  1         1012  
96              
97             sub new {
98 1     1   3 my ($pkg, $filename,@admin) = @_;
99 1         2 my $fh;
100              
101 1 50 33     5 if (ref $filename && $filename->isa('Tie::Handle')) {
102 0         0 $fh = $filename;
103             }
104             else {
105 1         10 $fh = new IO::File;
106 1 50       46 $fh->open("> $filename") || return undef;
107             }
108              
109 1         115 my $obj = bless {
110             fh => $fh ,
111             init => 0,
112             recnum => 0 ,
113             } , $pkg;
114 1 50       5 $obj->add_admin(@admin) if (@admin > 0);
115 1         6 return $obj;
116             }
117              
118             sub init {
119 1     1   2 my ($this) = shift;
120 1         2 my $fh = $this->{fh};
121 1 50       4 die "init: not allowed at this stage" unless $this->{init} == 0;
122 1         14 print $fh "\n";
123 1         4 print $fh "";
124 1         3 $this->{init}++;
125             }
126              
127             sub add_admin {
128 0     0   0 my ($this,@admin) = @_;
129 0         0 my $fh = $this->{fh};
130 0 0       0 $this->init() unless ($this->{init});
131 0 0       0 die "add_admin: not allowed at this stage" unless $this->{recnum} == 0;
132 0         0 foreach (@admin) {
133 0         0 printf $fh "%s", $_;
134             }
135             }
136              
137             =item $tape->add_record($identifier, $date, $record [, @admin])
138              
139             Add a XML document to the XMLtape with identifier $identifier, date stamp
140             $date and XML string representation $record. Optionally
141             an array of strings can be provided which contain in
142             XML format metadata about the record.
143              
144             Returns true on success undef on error.
145              
146             =cut
147             sub add_record {
148 6     6   25 my ($this, $identifier, $date, $record, @admin) = @_;
149 6         7 my $fh = $this->{fh};
150 6 100       16 $this->init() unless ($this->{init});
151              
152 6         8 print $fh "";
153 6         7 print $fh "";
154 6         10 print $fh "" , &escape($identifier) , "";
155 6         12 print $fh "" , &escape($date) , "";
156 6         13 foreach my $admin (@admin) {
157 0         0 print $fh "" , $admin , "";
158             }
159 6         9 print $fh "";
160 6         8 print $fh "" , $record , "";
161 6         8 print $fh "";
162              
163 6         10 $this->{recnum}++;
164              
165 6         11 return 1;
166             }
167              
168             =item $tape->tapeclose
169              
170             Closes the XMLtape.
171              
172             Returns true on success undef on error.
173              
174             =cut
175             sub tapeclose {
176 1     1   257 my ($this) = shift;
177 1         2 my $fh = $this->{fh};
178 1 50       6 $this->init() unless ($this->{init});
179 1         2 print $fh "";
180 1         8 $fh->close;
181             }
182              
183             sub escape {
184 12     12   12 my $str = shift;
185 12         14 $str =~ s/&/&/g;
186 12         13 $str =~ s/
187 12         13 $str =~ s/>/>/g;
188 12         17 $str =~ s/'/'/g;
189 12         12 $str =~ s/"/"/g;
190 12         19 return $str;
191             }
192              
193             package XML::Tape::Identifier;
194 1     1   662 use XML::Parser;
  0         0  
  0         0  
195              
196             sub new {
197             my $pkg = shift;
198             return bless {} , $pkg;
199             }
200              
201             sub identify {
202             my ($this,$filename) = @_;
203             my $parser = new XML::Parser(Namespaces => 1,
204             Handlers => {
205             Start => sub { $this->handle_start(@_); },
206             });
207             eval {
208             $parser->parsefile($filename);
209             };
210             return $this->{namespace};
211             }
212              
213             sub handle_start {
214             my ($this, $xp, $elem, %attr) = @_;
215             $this->{namespace} = $xp->namespace($elem);
216             die "ok";
217             }
218              
219             package XML::Tape::Reader;
220             # NS version http://library.lanl.gov/2005-08/aDORe/XMLtape/
221             use XML::Parser;
222             use IO::File;
223              
224             $XML::Tape::Reader::BUFF_SIZE = 1024;
225              
226             sub new {
227             my ($pkg, $filename,%options) = @_;
228             my $obj = bless {} , $pkg;
229             my $fh;
230              
231             if (ref $filename && $filename->isa('Tie::Handle')) {
232             $fh = $filename;
233             }
234             else {
235             $fh = new IO::File;
236             $fh->open("< $filename") || return undef;
237             }
238              
239             $obj->{fh} = $fh; # XML file handle
240             $obj->{records} = []; # Temporary storage for XML::Tape::Record
241             $obj->{admins} = []; # Temporary storage for XML::Tape::Admin
242             $obj->{curr} = undef; # Current record to be read
243             $obj->{parse_init} = 0; # Flag to indicate if we started reading XML
244             $obj->{parse_done} = 0; # Flag to indicate if we still reading XML
245             $obj->{parser} = undef; # XML::Parser
246             $obj->{parsernb} = undef; # XML::Parser::ExpatNB
247             $obj->{nav} = {}; # Hash to navigate in the XML record
248              
249             return $obj;
250             }
251              
252             =item $tape->get_admin()
253              
254             Reads one XMLtape admin section. Returns an instance of XML::Tape::Admin on success
255             or undef when no more XMLtape admin sections are available.
256              
257             =cut
258             sub get_admin {
259             my ($this) = shift;
260              
261             $this->parse() until ( ( scalar @{$this->{records}} ) || ( $this->{parse_done} ) );
262              
263             return shift( @{$this->{admins}} );
264             }
265              
266             =item $tape->get_record()
267              
268             Reads one XMLtape record section. Returns an instance of XML::Tape::Record on success
269             or undef when no more records are available.
270              
271             =cut
272             sub get_record {
273             my ($this) = shift;
274              
275             # Parse the XML until we read a new record or the parse is done...
276             $this->parse() until ( ( scalar @{$this->{records}} ) || ( $this->{parse_done} ) );
277              
278             return shift( @{$this->{records}} );
279             }
280              
281             sub tapeclose {
282             my ($this) = shift;
283             $this->{fh}->close;
284             }
285              
286             sub parse_init {
287             my ($this) = shift;
288              
289             $this->{parser} = new XML::Parser( Handlers => {
290             Start => sub { $this->handle_start(@_); },
291             Char => sub { $this->handle_char(@_); },
292             Comment => sub { $this->handle_comment(@_); },
293             Proc => sub { $this->handle_proc(@_); },
294             CdataStart => sub { $this->handle_cdata_start(@_); },
295             CdataEnd => sub { $this->handle_cdata_end(@_); },
296             End => sub { $this->handle_end(@_); },
297             Final => sub { $this->handle_final(@_); },
298             });
299              
300             $this->{parsernb} = $this->{parser}->parse_start();
301              
302             return undef unless $this->{parsernb};
303              
304             $this->{parse_init} = 1;
305              
306             return 1;
307             }
308              
309             sub parse {
310             my ($this) = shift;
311              
312             unless ($this->{parse_init}) {
313             $this->parse_init() || return undef;
314             }
315              
316             if (defined $this->{fh}) {
317             my $buffer;
318              
319             # Read a chunk of XML...
320             read($this->{fh}, $buffer, $XML::Tape::Reader::BUFF_SIZE);
321            
322             # If the buffer isn't empty then, parse it
323             # otherwise we reached the end of the file...
324             if (length $buffer) {
325             $this->{parsernb}->parse_more($buffer);
326             }
327             else {
328             $this->{parsernb}->parse_done();
329             $this->{parse_done} = 1;
330             }
331             }
332             }
333              
334             sub handle_start {
335             my ($this, $xp, $elem, %attr) = @_;
336              
337             if (0) {}
338             elsif ($this->{nav}->{in_record}) {
339             $this->{curr}->addRecordXML($xp->original_string);
340             }
341             elsif ($this->{nav}->{in_record_admin}) {
342             $this->{curr}->addAdminXML($xp->original_string);
343             }
344             elsif ($this->{nav}->{in_tape_admin}) {
345             $this->{curr}->addAdminXML($xp->original_string);
346             }
347              
348             if (0) {}
349             elsif ($xp->depth == 1 && $elem =~ /^(\w+:)?tapeAdmin$/) {
350             $this->{nav}->{in_tape_admin} = 1;
351             $this->{curr} = XML::Tape::Admin->new();
352             }
353             elsif ($xp->depth == 1 && $elem =~ /^(\w+:)?tapeRecord$/) {
354             $this->{curr} = XML::Tape::Record->new();
355             }
356             elsif ($xp->depth == 2 && $elem =~ /^(\w+:)?tapeRecordAdmin$/) {
357             $this->{nav}->{in_tape_record_admin} = 1;
358             }
359             elsif ($this->{nav}->{in_tape_record_admin} == 1 && $elem =~ /^(\w+:)?identifier$/) {
360             $this->{nav}->{in_record_identifier} = 1;
361             }
362             elsif ($this->{nav}->{in_tape_record_admin} == 1 && $elem =~ /^(\w+:)?date$/) {
363             $this->{nav}->{in_record_date} = 1;
364             }
365             elsif ($this->{nav}->{in_tape_record_admin} == 1 && $elem =~ /^(\w+:)?recordAdmin$/) {
366             $this->{nav}->{in_record_admin} = 1;
367             $this->{curr}->pushAdmin();
368             }
369             elsif ($xp->depth == 2 && $elem =~ /^(\w+:)?record$/) {
370             $this->{nav}->{in_record} = 1;
371             $this->{curr}->setStartByte($xp->current_byte + length $xp->original_string);
372             }
373             }
374              
375             sub handle_end {
376             my ($this, $xp, $elem, %attr) = @_;
377              
378             if (0) {}
379             elsif ($xp->depth == 1 && $elem =~ /^(\w+:)?tapeAdmin$/) {
380             $this->{nav}->{in_tape_admin} = 0;
381             push(@{$this->{admins}}, $this->{curr});
382             }
383             elsif ($xp->depth == 1 && $elem =~ /^(\w+:)?tapeRecord$/) {
384             push(@{$this->{records}}, $this->{curr});
385             }
386             elsif ($xp->depth == 2 && $elem =~ /^(\w+:)?tapeRecordAdmin$/) {
387             $this->{nav}->{in_tape_record_admin} = 0;
388             }
389             elsif ($this->{nav}->{in_tape_record_admin} == 1 && $elem =~ /^(\w+:)?identifier$/) {
390             $this->{nav}->{in_record_identifier} = 0;
391             }
392             elsif ($this->{nav}->{in_tape_record_admin} == 1 && $elem =~ /^(\w+:)?date$/) {
393             $this->{nav}->{in_record_date} = 0;
394             }
395             elsif ($this->{nav}->{in_tape_record_admin} == 1 && $elem =~ /^(\w+:)?recordAdmin$/) {
396             $this->{nav}->{in_record_admin} = 0;
397             }
398             elsif ($xp->depth == 2 && $elem =~ /^(\w+:)?record$/) {
399             $this->{nav}->{in_record} = 0;
400             $this->{curr}->setEndByte($xp->current_byte);
401             }
402              
403             if (0) {}
404             elsif ($this->{nav}->{in_record}) {
405             $this->{curr}->addRecordXML($xp->original_string);
406             }
407             elsif ($this->{nav}->{in_record_admin}) {
408             $this->{curr}->addAdminXML($xp->original_string);
409             }
410             elsif ($this->{nav}->{in_tape_admin}) {
411             $this->{curr}->addAdminXML($xp->original_string);
412             }
413             }
414              
415             sub handle_char {
416             my ($this, $xp, $data) = @_;
417              
418             if (0) {}
419             elsif ($this->{nav}->{in_tape_admin}) {
420             $this->{curr}->addAdminXML($xp->original_string);
421             }
422             elsif ($this->{nav}->{in_record}) {
423             $this->{curr}->addRecordXML($xp->original_string);
424             }
425             elsif ($this->{nav}->{in_record_identifier}) {
426             $this->{curr}->addIdentifier($data);
427             }
428             elsif ($this->{nav}->{in_record_date}) {
429             $this->{curr}->addDate($data);
430             }
431             }
432              
433             sub handle_comment {
434             my ($this, $xp, $data) = @_;
435              
436             if (0) {}
437             elsif ($this->{nav}->{in_tape_admin}) {
438             $this->{curr}->addAdminXML($xp->original_string);
439             }
440             elsif ($this->{nav}->{in_record}) {
441             $this->{curr}->addRecordXML($xp->original_string);
442             }
443             }
444              
445             sub handle_proc {
446             my ($this, $xp) = @_;
447              
448             if (0) {}
449             elsif ($this->{nav}->{in_tape_admin}) {
450             $this->{curr}->addAdminXML($xp->original_string);
451             }
452             elsif ($this->{nav}->{in_record}) {
453             $this->{curr}->addRecordXML($xp->original_string);
454             }
455             }
456              
457             sub handle_cdata_start {
458             my ($this, $xp) = @_;
459              
460             if (0) {}
461             elsif ($this->{nav}->{in_tape_admin}) {
462             $this->{curr}->addAdminXML($xp->original_string);
463             }
464             elsif ($this->{nav}->{in_record}) {
465             $this->{curr}->addRecordXML($xp->original_string);
466             }
467             }
468              
469             sub handle_cdata_end {
470             my ($this, $xp) = @_;
471              
472             if (0) {}
473             elsif ($this->{nav}->{in_tape_admin}) {
474             $this->{curr}->addAdminXML($xp->original_string);
475             }
476             elsif ($this->{nav}->{in_record}) {
477             $this->{curr}->addRecordXML($xp->original_string);
478             }
479             }
480              
481             sub handle_final {
482             return 1;
483             }
484              
485             package XML::Tape::Reader::v2005_01;
486             # Old tape reader keeping for backwards compatibility reasons;
487             # NS version http://library.lanl.gov/2005-01/STB-RL/tape/
488             use XML::Parser;
489             use IO::File;
490              
491             $XML::Tape::Reader::BUFF_SIZE = 1024;
492              
493             sub new {
494             my ($pkg, $filename,%options) = @_;
495             my $obj = bless {} , $pkg;
496             my $fh;
497              
498             if (ref $filename && $filename->isa('Tie::Handle')) {
499             $fh = $filename;
500             }
501             else {
502             $fh = new IO::File;
503             $fh->open("< $filename") || return undef;
504             }
505              
506             $obj->{fh} = $fh; # XML file handle
507             $obj->{records} = []; # Temporary storage for XML::Tape::Record
508             $obj->{admins} = []; # Temporary storage for XML::Tape::Admin
509             $obj->{curr} = undef; # Current record to be read
510             $obj->{parse_init} = 0; # Flag to indicate if we started reading XML
511             $obj->{parse_done} = 0; # Flag to indicate if we still reading XML
512             $obj->{parser} = undef; # XML::Parser
513             $obj->{parsernb} = undef; # XML::Parser::ExpatNB
514             $obj->{nav} = {}; # Hash to navigate in the XML record
515              
516             return $obj;
517             }
518              
519             sub get_admin {
520             my ($this) = shift;
521              
522             $this->parse() until ( ( scalar @{$this->{records}} ) || ( $this->{parse_done} ) );
523              
524             return shift( @{$this->{admins}} );
525             }
526              
527             sub get_record {
528             my ($this) = shift;
529              
530             # Parse the XML until we read a new record or the parse is done...
531             $this->parse() until ( ( scalar @{$this->{records}} ) || ( $this->{parse_done} ) );
532              
533             return shift( @{$this->{records}} );
534             }
535              
536             sub tapeclose {
537             my ($this) = shift;
538             $this->{fh}->close;
539             }
540              
541             sub parse_init {
542             my ($this) = shift;
543              
544             $this->{parser} = new XML::Parser( Handlers => {
545             Start => sub { $this->handle_start(@_); },
546             Char => sub { $this->handle_char(@_); },
547             Comment => sub { $this->handle_comment(@_); },
548             Proc => sub { $this->handle_proc(@_); },
549             CdataStart => sub { $this->handle_cdata_start(@_); },
550             CdataEnd => sub { $this->handle_cdata_end(@_); },
551             End => sub { $this->handle_end(@_); },
552             Final => sub { $this->handle_final(@_); },
553             });
554              
555             $this->{parsernb} = $this->{parser}->parse_start();
556              
557             return undef unless $this->{parsernb};
558              
559             $this->{parse_init} = 1;
560              
561             return 1;
562             }
563              
564             sub parse {
565             my ($this) = shift;
566              
567             unless ($this->{parse_init}) {
568             $this->parse_init() || return undef;
569             }
570              
571             if (defined $this->{fh}) {
572             my $buffer;
573              
574             # Read a chunk of XML...
575             read($this->{fh}, $buffer, $XML::Tape::Reader::BUFF_SIZE);
576              
577             # If the buffer isn't empty then, parse it
578             # otherwise we reached the end of the file...
579             if (length $buffer) {
580             $this->{parsernb}->parse_more($buffer);
581             }
582             else {
583             $this->{parsernb}->parse_done();
584             $this->{parse_done} = 1;
585             }
586             }
587             }
588              
589             sub handle_start {
590             my ($this, $xp, $elem, %attr) = @_;
591              
592             if (0) {}
593             elsif ($this->{nav}->{in_record}) {
594             $this->{curr}->addRecordXML($xp->original_string);
595             }
596             elsif ($this->{nav}->{in_record_admin}) {
597             $this->{curr}->addAdminXML($xp->original_string);
598             }
599             elsif ($this->{nav}->{in_tape_admin}) {
600             $this->{curr}->addAdminXML($xp->original_string);
601             }
602              
603             if (0) {}
604             elsif ($xp->depth == 1 && $elem =~ /^(\w+:)?tape-admin$/) {
605             $this->{nav}->{in_tape_admin} = 1;
606             $this->{curr} = XML::Tape::Admin->new();
607             }
608             elsif ($xp->depth == 1 && $elem =~ /^(\w+:)?tape-record$/) {
609             $this->{curr} = XML::Tape::Record->new();
610             }
611             elsif ($xp->depth == 2 && $elem =~ /^(\w+:)?tape-record-admin$/) {
612             $this->{nav}->{in_tape_record_admin} = 1;
613             }
614             elsif ($this->{nav}->{in_tape_record_admin} == 1 && $elem =~ /^(\w+:)?identifier$/) {
615             $this->{nav}->{in_record_identifier} = 1;
616             }
617             elsif ($this->{nav}->{in_tape_record_admin} == 1 && $elem =~ /^(\w+:)?date$/) {
618             $this->{nav}->{in_record_date} = 1;
619             }
620             elsif ($this->{nav}->{in_tape_record_admin} == 1 && $elem =~ /^(\w+:)?record-admin$/) {
621             $this->{nav}->{in_record_admin} = 1;
622             $this->{curr}->pushAdmin();
623             }
624             elsif ($xp->depth == 2 && $elem =~ /^(\w+:)?record$/) {
625             $this->{nav}->{in_record} = 1;
626             $this->{curr}->setStartByte($xp->current_byte + length $xp->original_string);
627             }
628             }
629              
630             sub handle_end {
631             my ($this, $xp, $elem, %attr) = @_;
632              
633             if (0) {}
634             elsif ($xp->depth == 1 && $elem =~ /^(\w+:)?tape-admin$/) {
635             $this->{nav}->{in_tape_admin} = 0;
636             push(@{$this->{admins}}, $this->{curr});
637             }
638             elsif ($xp->depth == 1 && $elem =~ /^(\w+:)?tape-record$/) {
639             push(@{$this->{records}}, $this->{curr});
640             }
641             elsif ($xp->depth == 2 && $elem =~ /^(\w+:)?tape-record-admin$/) {
642             $this->{nav}->{in_tape_record_admin} = 0;
643             }
644             elsif ($this->{nav}->{in_tape_record_admin} == 1 && $elem =~ /^(\w+:)?identifier$/) {
645             $this->{nav}->{in_record_identifier} = 0;
646             }
647             elsif ($this->{nav}->{in_tape_record_admin} == 1 && $elem =~ /^(\w+:)?date$/) {
648             $this->{nav}->{in_record_date} = 0;
649             }
650             elsif ($this->{nav}->{in_tape_record_admin} == 1 && $elem =~ /^(\w+:)?record-admin$/) {
651             $this->{nav}->{in_record_admin} = 0;
652             }
653             elsif ($xp->depth == 2 && $elem =~ /^(\w+:)?record$/) {
654             $this->{nav}->{in_record} = 0;
655             $this->{curr}->setEndByte($xp->current_byte);
656             }
657              
658             if (0) {}
659             elsif ($this->{nav}->{in_record}) {
660             $this->{curr}->addRecordXML($xp->original_string);
661             }
662             elsif ($this->{nav}->{in_record_admin}) {
663             $this->{curr}->addAdminXML($xp->original_string);
664             }
665             elsif ($this->{nav}->{in_tape_admin}) {
666             $this->{curr}->addAdminXML($xp->original_string);
667             }
668             }
669              
670             sub handle_char {
671             my ($this, $xp, $data) = @_;
672              
673             if (0) {}
674             elsif ($this->{nav}->{in_tape_admin}) {
675             $this->{curr}->addAdminXML($xp->original_string);
676             }
677             elsif ($this->{nav}->{in_record}) {
678             $this->{curr}->addRecordXML($xp->original_string);
679             }
680             elsif ($this->{nav}->{in_record_identifier}) {
681             $this->{curr}->addIdentifier($data);
682             }
683             elsif ($this->{nav}->{in_record_date}) {
684             $this->{curr}->addDate($data);
685             }
686             }
687              
688             sub handle_comment {
689             my ($this, $xp, $data) = @_;
690              
691             if (0) {}
692             elsif ($this->{nav}->{in_tape_admin}) {
693             $this->{curr}->addAdminXML($xp->original_string);
694             }
695             elsif ($this->{nav}->{in_record}) {
696             $this->{curr}->addRecordXML($xp->original_string);
697             }
698             }
699              
700             sub handle_proc {
701             my ($this, $xp) = @_;
702              
703             if (0) {}
704             elsif ($this->{nav}->{in_tape_admin}) {
705             $this->{curr}->addAdminXML($xp->original_string);
706             }
707             elsif ($this->{nav}->{in_record}) {
708             $this->{curr}->addRecordXML($xp->original_string);
709             }
710             }
711              
712             sub handle_cdata_start {
713             my ($this, $xp) = @_;
714              
715             if (0) {}
716             elsif ($this->{nav}->{in_tape_admin}) {
717             $this->{curr}->addAdminXML($xp->original_string);
718             }
719             elsif ($this->{nav}->{in_record}) {
720             $this->{curr}->addRecordXML($xp->original_string);
721             }
722             }
723              
724             sub handle_cdata_end {
725             my ($this, $xp) = @_;
726              
727             if (0) {}
728             elsif ($this->{nav}->{in_tape_admin}) {
729             $this->{curr}->addAdminXML($xp->original_string);
730             }
731             elsif ($this->{nav}->{in_record}) {
732             $this->{curr}->addRecordXML($xp->original_string);
733             }
734             }
735              
736             sub handle_final {
737             return 1;
738             }
739              
740              
741             =back
742              
743             =head1 XML::Tape::Admin METHODS
744              
745             =over 4
746              
747             =item $admin->getRecord()
748              
749             Returns a XML string representation of a XMLtape administrative record.
750              
751             =back
752              
753             =cut
754             package XML::Tape::Admin;
755              
756             sub new {
757             my ($pkg) = shift;
758             return bless {
759             adminXML => undef ,
760             } , $pkg;
761             }
762              
763             sub addAdminXML {
764             my ($this, $str) = @_;
765             $this->{adminXML} .= $str;
766             }
767              
768             sub getRecord {
769             my ($this) = @_;
770             return $this->{adminXML};
771             }
772              
773             =head1 XML::Tape::Record METHODS
774              
775             =over 4
776              
777             =item $record->getIdentifier()
778              
779             Returns the record identifier as string.
780              
781             =item $record->getDate()
782              
783             Returns the record datestamp as string.
784              
785             =item $record->getAdmin()
786              
787             Returns an ARRAY of administrative records
788              
789             =item $record->getRecord()
790              
791             Returns a XML string representation of a XMLtape record.
792              
793             =item $record->getStartByte()
794              
795             Returns the start byte position of the record in the XMLtape
796              
797             =item $record->getEndByte()
798              
799             Returns the end byte positorion of the record in the XMLtape
800              
801             =back
802              
803             =cut
804             package XML::Tape::Record;
805              
806             sub new {
807             my ($pkg) = shift;
808             return bless {
809             startByte => 0 ,
810             endByte => 0 ,
811             recordXML => undef,
812             identifier => undef,
813             date => undef,
814             admin => [],
815             } , $pkg;
816             }
817              
818             sub setStartByte {
819             my ($this,$num) = @_;
820             $this->{startByte} = $num;
821             }
822              
823             sub getStartByte {
824             my ($this) = @_;
825             return $this->{startByte};
826             }
827              
828             sub setEndByte {
829             my ($this,$num) = @_;
830             $this->{endByte} = $num;
831             }
832              
833             sub getEndByte {
834             my ($this) = @_;
835             return $this->{endByte};
836             }
837              
838             sub addRecordXML {
839             my ($this, $str) = @_;
840             $this->{recordXML} .= $str;
841             }
842              
843             sub getRecord {
844             my ($this) = @_;
845             return $this->{recordXML};
846             }
847              
848             sub addIdentifier {
849             my ($this, $str) = @_;
850             $this->{identifier} .= $str;
851             }
852              
853             sub getIdentifier {
854             my ($this) = @_;
855             return $this->{identifier};
856             }
857              
858             sub addDate {
859             my ($this, $str) = @_;
860             $this->{date} .= $str;
861             }
862              
863             sub getDate {
864             my ($this) = @_;
865             return $this->{date};
866             }
867              
868             sub pushAdmin {
869             my ($this) = @_;
870             push(@{$this->{admin}},'');
871             }
872              
873             sub addAdminXML {
874             my ($this, $xml) = @_;
875             my $num = @{$this->{admin}};
876             $this->{admin}->[$num-1] .= $xml;
877             }
878              
879             sub getAdmin {
880             my ($this) = @_;
881             return $this->{admin};
882             }
883              
884             1;
885              
886             =head1 FURTHER INFORMATION
887            
888             'File-based storage of Digital Objects and constituent datastreams: XMLtapes and Internet Archive ARC files'
889             http://arxiv.org/abs/cs.DL/0503016
890            
891             'The multi-faceted use of the OAI-PMH in the LANL Repository'
892             http://yar.sourceforge.net/jcdl2004-submitted-draft.pdf
893              
894             =head1 BUGS
895              
896             UTF-8 encoding is mandatory.
897             Doesn't check for UTF-8 encoding.
898              
899             =head1 CREDITS
900              
901             XMLtape archives were developed by the Digital Library Research & Prototyping
902             team at Los Alamos National Laboratory.
903              
904             XML parsing in the module was inspired by Robert Hanson's XML::RAX module.
905              
906             =head1 SEE ALSO
907              
908             L
909              
910             In bin/oaitape you'll find an example of a OAI-PMH interface on XML::Tape
911              
912             =head1 AUTHOR
913              
914             Patrick Hochstenbach
915              
916             =cut
917              
918             1;