File Coverage

blib/lib/MARC/Crosswalk/DublinCore.pm
Criterion Covered Total %
statement 53 63 84.1
branch 22 32 68.7
condition n/a
subroutine 10 11 90.9
pod 4 4 100.0
total 89 110 80.9


line stmt bran cond sub pod time code
1             package MARC::Crosswalk::DublinCore;
2            
3             =head1 NAME
4            
5             MARC::Crosswalk::DublinCore - Convert data between MARC and Dublin Core
6            
7             =head1 SYNOPSIS
8            
9             my $crosswalk = MARC::Crosswalk::DublinCore->new;
10            
11             # Convert a MARC record to Dublin Core (simple)
12             my $marc = MARC::Record->new_from_usmarc( $blob );
13             my $dc = $crosswalk->as_dublincore( $marc );
14            
15             # Convert simple DC to MARC
16             $marc = $crosswalk->as_marc( $dc );
17            
18             # Convert MARC to qualified DC instead
19             $crosswalk->qualified( 1 );
20             $dc = $crosswalk->as_dublincore( $marc );
21            
22             =head1 DESCRIPTION
23            
24             This module provides an implentation of the LOC's spec on how to convert
25             metadata between MARC and Dublin Core format. The spec for converting MARC to
26             Dublin Core is available at: http://www.loc.gov/marc/marc2dc.html, and from DC to
27             MARC: http://www.loc.gov/marc/dccross.html.
28            
29             NB: The conversion cannot be done in a round-trip manner. i.e. Doing a conversion
30             from MARC to DC, then trying to go back to MARC will not yield the original record.
31            
32             =head1 INSTALLATION
33            
34             To install this module via Module::Build:
35            
36             perl Build.PL
37             ./Build # or `perl Build`
38             ./Build test # or `perl Build test`
39             ./Build install # or `perl Build install`
40            
41             To install this module via ExtUtils::MakeMaker:
42            
43             perl Makefile.PL
44             make
45             make test
46             make install
47            
48             =cut
49            
50 3     3   152704 use strict;
  3         9  
  3         114  
51 3     3   15 use warnings;
  3         6  
  3         163  
52            
53 3     3   3243 use MARC::Record;
  3         56612  
  3         170  
54 3     3   32 use MARC::Field;
  3         8  
  3         70  
55 3     3   3153 use DublinCore::Record;
  3         29157  
  3         99  
56 3     3   30 use DublinCore::Element;
  3         9  
  3         16  
57            
58 3     3   99 use Carp qw( croak );
  3         7  
  3         8629  
59            
60             our $VERSION = '0.02';
61            
62             my %leader06_lut = (
63             a => 'Text',
64             c => 'Text',
65             d => 'Text',
66             t => 'Text',
67             e => 'Image',
68             f => 'Image',
69             g => 'Image',
70             k => 'Image',
71             i => 'Sound',
72             j => 'Sound',
73             # m => 'No Type Provided',
74             # o => 'No Type Provided',
75             # p => 'No Type Provided',
76             # r => 'No Type Provided'
77             );
78            
79             my %leader07_lut = (
80             c => 'Collection',
81             s => 'Collection',
82             p => 'Collection'
83             );
84            
85             my @dc_qualified = (
86             {
87             tag => 245,
88             dc => { name => 'Title' }
89             },
90             ( map +{
91             tag => $_,
92             dc => { name => 'Title', qualifier => 'Alternative' }
93             }, ( 130, 210, 240, 242, 246, 730, 740 ) ),
94             ( map +{
95             tag => $_,
96             dc => { name => 'Creator' }
97             }, ( 100, 110, 111, 700, 710, 711, 720 ) ),
98             ( map +{
99             tag => $_,
100             indicators => [ undef, 0 ],
101             dc => { name => 'Subject', scheme => 'LCSH' }
102             }, ( 600, 610, 611, 630, 650 ) ),
103             ( map +{
104             tag => $_,
105             indicators => [ undef, 2 ],
106             dc => { name => 'Subject', scheme => 'MeSH' }
107             }, ( 600, 610, 611, 630, 650 ) ),
108             {
109             tag => '050',
110             dc => { name => 'Subject', scheme => 'LCC' }
111             },
112             {
113             tag => '082',
114             dc => { name => 'Subject', scheme => 'DDC' }
115             },
116             {
117             tag => '080',
118             dc => { name => 'Subject', scheme => 'UDC' }
119             },
120             ( map +{
121             tag => $_,
122             dc => { name => 'Description' }
123             }, grep { $_ !~ /^(505|506|520|530|540|546)$/ } 500..599 ),
124             {
125             tag => 505,
126             indicators => [ 3, undef ],
127             dc => { name => 'Description', qualifier => 'tableOfContents' }
128             },
129             {
130             tag => 520,
131             dc => { name => 'Description', qualifier => 'Abstract' }
132             },
133             {
134             tag => 260,
135             subfields => 'ab',
136             dc => { name => 'Publisher' }
137             },
138             {
139             tag => 260,
140             subfields => 'cg',
141             dc => { name => 'Date', qualifier => 'Created' }
142             },
143             {
144             tag => 533,
145             subfields => 'd',
146             dc => { name => 'Date', qualifier => 'Created' }
147             },
148             {
149             tag => 260,
150             subfields => 'c',
151             dc => { name => 'Date', qualifier => 'Issued' }
152             },
153             {
154             tag => '008',
155             code => sub {
156             return substr( shift, 7, 4 );
157             },
158             dc => { name => 'Date', qualifier => 'Issued' }
159             },
160             {
161             tag => 'Leader',
162             code => sub { return $leader06_lut{ substr( shift, 6, 1 ) }; },
163             dc => { name => 'Type', scheme => 'DCMI Type Vocabulary' }
164             },
165             {
166             tag => 'Leader',
167             code => sub { return $leader07_lut{ substr( shift, 7, 1 ) }; },
168             dc => { name => 'Type', scheme => 'DCMI Type Vocabulary' }
169             },
170             {
171             tag => 655,
172             subfield_eq => [ '2', 'dct' ],
173             dc => { name => 'Type', scheme => 'DCMI Type Vocabulary' }
174             },
175             {
176             tag => 865,
177             subfields => 'q',
178             dc => { name => 'Format', scheme => 'IMT' }
179             },
180             {
181             tag => 300,
182             subfields => 'a',
183             dc => { name => 'Format', qualifier => 'Extent' }
184             },
185             {
186             tag => 533,
187             subfields => 'e',
188             dc => { name => 'Format', qualifier => 'Extent' }
189             },
190             {
191             tag => 340,
192             subfields => 'a',
193             dc => { name => 'Format', qualifier => 'Medium' }
194             },
195             {
196             tag => 856,
197             subfields => 'u',
198             dc => { name => 'Identifier', scheme => 'URI' }
199             },
200             {
201             tag => 786,
202             subfields => 'o',
203             dc => { name => 'Source', scheme => 'URI' }
204             },
205             {
206             tag => '008',
207             code => sub {
208             return substr( shift, 35, 3 );
209             },
210             dc => { name => 'Language', scheme => 'ISO 639-2' }
211             },
212             {
213             tag => '041',
214             dc => { name => 'Language', scheme => 'ISO 639-2' }
215             },
216             {
217             tag => '546',
218             dc => { name => 'Language', scheme => 'RFC 1766' }
219             },
220             {
221             tag => 775,
222             dc => { name => 'Relation', qualifier => 'isVersionOf' }
223             },
224             {
225             tag => 786,
226             subfields => 'nt',
227             dc => { name => 'Relation', qualifier => 'isVersionOf' }
228             },
229             {
230             tag => 775,
231             dc => { name => 'Relation', qualifier => 'isVersionOf', scheme => 'URI' }
232             },
233             {
234             tag => 786,
235             subfields => 'o',
236             dc => { name => 'Relation', qualifier => 'isVersionOf', scheme => 'URI' }
237             },
238             {
239             tag => 775,
240             subfields => 'nt',
241             dc => { name => 'Relation', qualifier => 'hasVersion' }
242             },
243             {
244             tag => 775,
245             subfields => 'o',
246             dc => { name => 'Relation', qualifier => 'hasVersion', scheme => 'URI' }
247             },
248             {
249             tag => 785,
250             subfields => 'nt',
251             dc => { name => 'Relation', qualifier => 'isReplaceBy' }
252             },
253             {
254             tag => 785,
255             subfields => 'o',
256             dc => { name => 'Relation', qualifier => 'isReplaceBy', scheme => 'URI' }
257             },
258             {
259             tag => 780,
260             subfields => 'nt',
261             dc => { name => 'Relation', qualifier => 'Replaces' }
262             },
263             {
264             tag => 780,
265             subfields => 'o',
266             dc => { name => 'Relation', qualifier => 'Replaces', scheme => 'URI' }
267             },
268             {
269             tag => 538,
270             dc => { name => 'Relation', qualifier => 'Requires' }
271             },
272             {
273             tag => 773,
274             subfields => 'nt',
275             dc => { name => 'Relation', qualifier => 'isPartOf' }
276             },
277             ( map +{
278             tag => $_,
279             dc => { name => 'Relation', qualifier => 'isPartOf' }
280             }, ( 760, 440, 490, 800, 810, 811, 830 ) ),
281             {
282             tag => 760,
283             dc => { name => 'Relation', qualifier => 'isPartOf', scheme => 'URI' }
284             },
285             {
286             tag => 773,
287             subfields => 'o',
288             dc => { name => 'Relation', qualifier => 'isPartOf', scheme => 'URI' }
289             },
290             {
291             tag => 774,
292             subfields => 'nt',
293             dc => { name => 'Relation', qualifier => 'hasPart' }
294             },
295             {
296             tag => 774,
297             subfields => 'o',
298             dc => { name => 'Relation', qualifier => 'hasPart', scheme => 'URI' }
299             },
300             {
301             tag => 510,
302             dc => { name => 'Relation', qualifier => 'isReferencedBy' }
303             },
304             {
305             tag => 776,
306             subfields => 'nt',
307             dc => { name => 'Relation', qualifier => 'isFormatOf' }
308             },
309             {
310             tag => 530,
311             dc => { name => 'Relation', qualifier => 'isFormatOf' }
312             },
313             {
314             tag => 776,
315             subfields => 'o',
316             dc => { name => 'Relation', qualifier => 'isFormatOf', scheme => 'URI' }
317             },
318             {
319             tag => 530,
320             subfields => 'u',
321             dc => { name => 'Relation', qualifier => 'isFormatOf', scheme => 'URI' }
322             },
323             {
324             tag => 776,
325             subfields => 'nt',
326             dc => { name => 'Relation', qualifier => 'hasFormat' }
327             },
328             {
329             tag => 530,
330             dc => { name => 'Relation', qualifier => 'hasFormat' }
331             },
332             {
333             tag => 776,
334             subfields => 'o',
335             dc => { name => 'Relation', qualifier => 'hasFormat', scheme => 'URI' }
336             },
337             {
338             tag => 530,
339             subfields => 'u',
340             dc => { name => 'Relation', qualifier => 'hasFormat', scheme => 'URI' }
341             },
342             ( map +{
343             tag => $_,
344             dc => { name => 'Coverage', qualifier => 'Spacial' }
345             }, ( 522, 651, 255, 752 ) ),
346             {
347             tag => 650,
348             subfields => 'z',
349             dc => { name => 'Coverage', qualifier => 'Spacial' }
350             },
351             ( map +{
352             tag => $_,
353             subfields => 'c',
354             dc => { name => 'Coverage', qualifier => 'Spacial', scheme => 'ISO 3166' }
355             }, ( '043', '044' ) ),
356             {
357             tag => 651,
358             subfield_eq => [ '2', 'tgn' ],
359             dc => { name => 'Coverage', qualifier => 'Spacial', scheme => 'TGN' }
360             },
361             {
362             tag => 513,
363             subfields => 'b',
364             dc => { name => 'Coverage', qualifier => 'Temporal' }
365             },
366             {
367             tag => '033',
368             subfields => 'a',
369             dc => { name => 'Coverage', qualifier => 'Temporal' }
370             },
371             ( map +{
372             tag => $_,
373             dc => { name => 'Rights' }
374             }, ( 506, 540 ) )
375            
376             );
377            
378             my @dc_simple = (
379             {
380             tag => 245,
381             dc => { name => 'Title' }
382             },
383             ( map +{
384             tag => $_,
385             dc => { name => 'Creator' }
386             }, ( 100, 110, 111, 700, 710, 711, 720 ) ),
387             ( map +{
388             tag => $_,
389             dc => { name => 'Subject' }
390             }, ( 600, 610, 611, 630, 650, 653 ) ),
391             ( map +{
392             tag => $_,
393             dc => { name => 'Description' }
394             }, grep { $_ !~ /^(506|530|540|546)$/ } 500..599 ),
395             {
396             tag => 260,
397             subfields => 'ab',
398             dc => { name => 'Publisher' }
399             },
400             {
401             tag => 'Leader',
402             code => sub { return $leader06_lut{ substr( shift, 6, 1 ) }; },
403             dc => { name => 'Type' }
404             },
405             {
406             tag => 'Leader',
407             code => sub { return $leader07_lut{ substr( shift, 7, 1 ) }; },
408             dc => { name => 'Type' }
409             },
410             {
411             tag => 655,
412             dc => { name => 'Type' }
413             },
414             {
415             tag => 856,
416             subfields => 'q',
417             dc => { name => 'Format' }
418             },
419             {
420             tag => 856,
421             subfields => 'u',
422             dc => { name => 'Identifier' }
423             },
424             {
425             tag => 786,
426             subfields => 'ot',
427             dc => { name => 'Source' }
428             },
429             {
430             tag => '008',
431             code => sub {
432             return substr( shift, 35, 3 );
433             },
434             dc => { name => 'Language' }
435             },
436             {
437             tag => 546,
438             dc => { name => 'Language' }
439             },
440             {
441             tag => 530,
442             dc => { name => 'Relation' }
443             },
444             ( map +{
445             tag => $_,
446             subfields => 'ot',
447             dc => { name => 'Relation' }
448             }, ( 760..787 ) ),
449             ( map +{
450             tag => $_,
451             dc => { name => 'Coverage' }
452             }, ( 651, 752 ) ),
453             ( map +{
454             tag => $_,
455             dc => { name => 'Rights' }
456             }, ( 506, 540 ) )
457             );
458            
459             my @marc_qualified;
460             my @marc_simple;
461            
462             =head1 METHODS
463            
464             =head2 new( %options )
465            
466             Creates a new crosswalk object. You can pass the "qualified" option (true/false) as
467             well.
468            
469             # DC Simple
470             $crosswalk = MARC::Crosswalk::DublinCore->new;
471            
472             # DC Qualified
473             $crosswalk = MARC::Crosswalk::DublinCore->new( qualified => 1 );
474            
475             =cut
476            
477             sub new {
478 3     3 1 40 my $class = shift;
479 3         10 my %options = @_;
480 3         7 my $self = {};
481            
482 3         10 bless $self, $class;
483            
484 3 50       20 $self->qualified( 1 ) if $options{ qualified };
485            
486 3         13 return $self;
487             }
488            
489             =head2 qualified( $qualified )
490            
491             Allows you to specify if qualified Dublin Core should be used in
492             the input or output. Defaults to false (DC simple).
493            
494             # DC Simple
495             $crosswalk->qualified( 0 );
496            
497             # DC Qualified
498             $crosswalk->qualified( 1 );
499            
500             =cut
501            
502             sub qualified {
503 5     5 1 10108 my $self = shift;
504 5         9 my $qualified = @_;
505            
506 5 100       23 $self->{ _QUALIFIED } = $qualified if @_;
507            
508 5         27 return $self->{ _QUALIFIED };
509             }
510            
511             =head2 as_dublincore( $marc )
512            
513             convert a MARC::Record to a DublinCore::Record.
514            
515             =cut
516            
517             sub as_dublincore {
518 2     2 1 6727 my $self = shift;
519 2         5 my $marc = shift;
520            
521 2 50       40 croak( 'Input is not a MARC::Record!' ) unless $marc->isa( 'MARC::Record' );
522            
523 2 100       10 my $rules = $self->qualified ? \@dc_qualified : \@dc_simple;
524 2         19 my $dc = DublinCore::Record->new;
525            
526 2         109 for my $rule ( @$rules ) {
527 337 100       43133 for my $field ( $rule->{ tag } eq 'Leader' ? $marc->leader : $marc->field( $rule->{ tag } ) ) {
528 30 50       2995 next unless defined $field;
529            
530 30 100       145 my $content = ref $field ? $field->as_string( $rule->{ subfields } ) : $field;
531            
532 30 50       739 if( $rule->{ subfield_eq } ) {
533 0         0 my @eq = @{ $rule->{ subfield_eq } };
  0         0  
534 0         0 while( @eq ) {
535 0 0       0 $content = undef unless $field->subfield( shift( @eq ) ) eq shift( @eq );
536             }
537             }
538 30 100       76 if( $rule->{ indicators } ) {
539 6         271 for( 0, 1 ) {
540 12 100       434 next unless defined $rule->{ indicators }->[ $_ ];
541 6 100       29 $content = undef unless $field->indicator( $_ + 1 ) == $rule->{ indicators }->[ $_ ];
542             }
543             }
544            
545 30 100       136 if( $rule->{ code } ) {
546 7         54 $content = $rule->{ code }->( $content );
547             }
548            
549 30 100       77 if( $content ) {
550 24         104 my $element = DublinCore::Element->new( $rule->{ dc } );
551 24         1130 $content =~ s/^\s+|\s+$//;
552 24         72 $element->content( $content );
553 24         1006 $dc->add( $element );
554             }
555             }
556             }
557            
558 2         243 return $dc;
559             }
560            
561             =head2 as_marc( $dublincore )
562            
563             convert a DublinCore::Record to a MARC::Record. NB: Not yet implemented.
564            
565             =cut
566            
567             sub as_marc {
568 0     0 1   my $self = shift;
569 0           my $dc = shift;
570            
571 0 0         croak( 'Input is not a DublinCore::Record!' ) unless $dc->isa( 'DublinCore::Record' );
572            
573 0 0         my $rules = $self->qualified ? \@marc_qualified : \@marc_simple;
574 0           my $marc = MARC::Record->new;
575            
576 0           croak( 'Not implemented.' );
577             }
578            
579             =head1 TODO
580            
581             =over 4
582            
583             =item * Implement as_marc()
584            
585             =item * add tests
586            
587             =back
588            
589             =head1 SEE ALSO
590            
591             =over 4
592            
593             =item * http://www.loc.gov/marc/marc2dc.html
594            
595             =item * http://www.loc.gov/marc/dccross.html
596            
597             =item * MARC::Record
598            
599             =item * DublinCore::Record
600            
601             =back
602            
603             =head1 AUTHOR
604            
605             =over 4
606            
607             =item * Brian Cassidy Ebricas@cpan.orgE
608            
609             =back
610            
611             =head1 COPYRIGHT AND LICENSE
612            
613             Copyright 2005 by Brian Cassidy
614            
615             This library is free software; you can redistribute it and/or modify
616             it under the same terms as Perl itself.
617            
618             =cut
619            
620             1;