Replaced expressions of the form "$x = $x <op> $y" with "$x <op>= $y".
[koha.git] / C4 / SimpleMarc.pm
1 package C4::SimpleMarc;
2
3 # $Id$
4
5 # Routines for handling import of MARC data into Koha db
6
7 # Koha library project  www.koha.org
8
9 # Licensed under the GPL
10
11
12 # Copyright 2000-2002 Katipo Communications
13 #
14 # This file is part of Koha.
15 #
16 # Koha is free software; you can redistribute it and/or modify it under the
17 # terms of the GNU General Public License as published by the Free Software
18 # Foundation; either version 2 of the License, or (at your option) any later
19 # version.
20 #
21 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
22 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
23 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
24 #
25 # You should have received a copy of the GNU General Public License along with
26 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
27 # Suite 330, Boston, MA  02111-1307 USA
28
29 use strict;
30 use DBI;
31 require Exporter;
32 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
33
34 # set the version for version checking
35 $VERSION = 0.01;
36
37 =head1 NAME
38
39 C4::SimpleMarc - Functions for parsing MARC records and files
40
41 =head1 SYNOPSIS
42
43   use C4::SimpleMarc;
44
45 =head1 DESCRIPTION
46
47 This module provides functions for parsing MARC records and files.
48
49 =head1 FUNCTIONS
50
51 =over 2
52
53 =cut
54
55 @ISA = qw(Exporter);
56 @EXPORT = qw(
57         &extractmarcfields
58         &parsemarcfileformat
59         &taglabel
60         %tagtext
61         %tagmap
62 );
63
64 # FIXME - %tagtext and %tagmap are in both @EXPORT and @EXPORT_OK.
65 # They should be in one or the other, but not both (though preferably,
66 # things shouldn't get exported in the first place).
67 @EXPORT_OK   = qw(
68         %tagtext
69         %tagmap
70 );
71
72 #------------------------------------------------
73
74 #------------------
75 # Constants
76
77 # %tagtext maps MARC tags to descriptive names.
78 my %tagtext = (
79     'LDR' => 'Leader',
80     '001' => 'Control number',
81     '003' => 'Control number identifier',
82     '005' => 'Date and time of latest transaction',
83     '006' => 'Fixed-length data elements -- additional material characteristics',
84     '007' => 'Physical description fixed field',
85     '008' => 'Fixed length data elements',
86     '010' => 'LCCN',
87     '015' => 'National library CN',
88     '020' => 'ISBN',
89     '022' => 'ISSN',
90     '024' => 'Other standard ID',
91     '035' => 'System control number',
92     '037' => 'Source of acquisition',
93     '040' => 'Cataloging source',
94     '041' => 'Language code',
95     '043' => 'Geographic area code',
96     '043' => 'Publishing country code',
97     '050' => 'Library of Congress call number',
98     '055' => 'Canadian classification number',
99     '060' => 'National Library of Medicine call number',
100     '082' => 'Dewey decimal call number',
101     '100' => 'Main entry -- Personal name',
102     '110' => 'Main entry -- Corporate name',
103     '130' => 'Main entry -- Uniform title',
104     '240' => 'Uniform title',
105     '245' => 'Title statement',
106     '246' => 'Varying form of title',
107     '250' => 'Edition statement',
108     '256' => 'Computer file characteristics',
109     '260' => 'Publication, distribution, etc.',
110     '263' => 'Projected publication date',
111     '300' => 'Physical description',
112     '306' => 'Playing time',
113     '440' => 'Series statement / Added entry -- Title',
114     '490' => 'Series statement',
115     '500' => 'General note',
116     '504' => 'Bibliography, etc. note',
117     '505' => 'Formatted contents note',
118     '508' => 'Creation/production credits note',
119     '510' => 'Citation/references note',
120     '511' => 'Participant or performer note',
121     '520' => 'Summary, etc. note',
122     '521' => 'Target audience note (ie age)',
123     '530' => 'Additional physical form available note',
124     '538' => 'System details note',
125     '586' => 'Awards note',
126     '600' => 'Subject added entry -- Personal name',
127     '610' => 'Subject added entry -- Corporate name',
128     '650' => 'Subject added entry -- Topical term',
129     '651' => 'Subject added entry -- Geographic name',
130     '656' => 'Index term -- Occupation',
131     '700' => 'Added entry -- Personal name',
132     '710' => 'Added entry -- Corporate name',
133     '730' => 'Added entry -- Uniform title',
134     '740' => 'Added entry -- Uncontrolled related/analytical title',
135     '800' => 'Series added entry -- Personal name',
136     '830' => 'Series added entry -- Uniform title',
137     '852' => 'Location',
138     '856' => 'Electronic location and access',
139 );
140
141 # tag, subfield, field name, repeats, striptrailingchars
142 # FIXME - What is this? Can it be explained without a semester-long
143 # course in MARC?
144
145 # XXX - Maps MARC (field, subfield) tuples to Koha database field
146 # names (presumably in 'biblioitems'). $tagmap{$field}->{$subfield} is
147 # an anonymous hash of the form
148 #       {
149 #               name    => "title",     # Name of Koha field
150 #               rpt     => 0,           # I don't know what this is, but
151 #                                       # it's not used.
152 #               striptrail => ',:;/-',  # Lists the set of characters that
153 #                                       # should be stripped from the end
154 #                                       # of the MARC field.
155 #       }
156
157 my %tagmap=(
158     '010'=>{'a'=>{name=> 'lccn',        rpt=>0, striptrail=>' '         }},
159     '015'=>{'a'=>{name=> 'lccn',        rpt=>0  }},
160     '020'=>{'a'=>{name=> 'isbn',        rpt=>0  }},
161     '022'=>{'a'=>{name=> 'issn',        rpt=>0  }},
162     '082'=>{'a'=>{name=> 'dewey',       rpt=>0  }},
163     '100'=>{'a'=>{name=> 'author',      rpt=>0, striptrail=>',:;/-'     }},
164     '245'=>{'a'=>{name=> 'title',       rpt=>0, striptrail=>',:;/'      },
165             'b'=>{name=> 'subtitle',    rpt=>0, striptrail=>',:;/'      }},
166     '260'=>{'a'=>{name=> 'place',       rpt=>0, striptrail=>',:;/-'     },
167             'b'=>{name=> 'publisher',   rpt=>0, striptrail=>',:;/-'     },
168             'c'=>{name=> 'year' ,       rpt=>0, striptrail=>'.,:;/-'    }},
169     '300'=>{'a'=>{name=> 'pages',       rpt=>0, striptrail=>',:;/-'     },
170             'c'=>{name=> 'size',        rpt=>0, striptrail=>',:;/-'     }},
171     '362'=>{'a'=>{name=> 'volume-number',       rpt=>0  }},
172     '440'=>{'a'=>{name=> 'seriestitle', rpt=>0, striptrail=>',:;/'      },
173             'v'=>{name=> 'volume-number',rpt=>0 }},
174     '490'=>{'a'=>{name=> 'seriestitle', rpt=>0, striptrail=>',:;/'      },
175             'v'=>{name=> 'volume-number',rpt=>0 }},
176     '700'=>{'a'=>{name=> 'addtional-author-illus',rpt=>1, striptrail=>',:;/'    }},
177     '5xx'=>{'a'=>{name=> 'notes',       rpt=>1  }},
178     '65x'=>{'a'=>{name=> 'subject',     rpt=>1, striptrail=>'.,:;/-'    }},
179 );
180
181
182 #------------------
183
184 =item extractmarcfields
185
186   $biblioitem = &extractmarcfields($marc_record);
187
188 C<$marc_record> is a reference-to-array representing a MARC record;
189 each element is a reference-to-hash specifying a MARC field (possibly
190 with subfields).
191
192 C<&extractmarcfields> translates C<$marc_record> into a Koha
193 biblioitem. C<$biblioitem> is a reference-to-hash whose keys are named
194 after fields in the biblioitems table of the Koha database.
195
196 =cut
197 #'
198 # FIXME - Throughout:
199 #       $foo->{bar}->[baz]->{quux}
200 # can be rewritten as
201 #       $foo->{bar}[baz]{quux}
202 sub extractmarcfields {
203     use strict;
204     # input
205     my (
206         $record,        # pointer to list of MARC field hashes.
207                         # Example: $record->[0]->{'tag'} = '100' # Author
208                         #       $record->[0]->{'subfields'}->{'a'} = subfieldvalue
209     )=@_;
210
211     # return
212     my $bib;            # pointer to hash of named output fields
213                         # Example: $bib->{'author'} = "Twain, Mark";
214
215     my $debug=0;
216
217     my (
218         $field,         # hash ref
219         $value,
220         $subfield,      # Marc subfield [a-z]
221         $fieldname,     # name of field "author", "title", etc.
222         $strip,         # chars to remove from end of field
223         $stripregex,    # reg exp pattern
224     );
225     my ($lccn, $isbn, $issn,
226         $publicationyear, @subjects, $subject,
227         $controlnumber,
228         $notes, $additionalauthors, $illustrator, $copyrightdate,
229         $s, $subdivision, $subjectsubfield,
230     );
231
232     print "<PRE>\n" if $debug;
233
234     if ( ref($record) eq "ARRAY" ) {
235         foreach $field (@$record) {
236
237             # Check each subfield in field
238             # FIXME - Would this code be more readable with
239             #   while (($subfieldname, $subfield) = each %{$field->{subfields}})
240             # ?
241             foreach $subfield ( keys %{$field->{subfields}} ) {
242                 # see if it is defined in our Marc to koha mapping table
243                 # FIXME - This if-clause takes up the entire loop.
244                 # This would be better rewritten as
245                 #       next unless defined($tagmap{...});
246                 # Then the body of the loop doesn't have to be
247                 # indented as much.
248                 if ( $fieldname=$tagmap{ $field->{'tag'} }->{$subfield}->{name} ) {
249                     # Yes, so keep the value
250                     if ( ref($field->{'subfields'}->{$subfield} ) eq 'ARRAY' ) {
251                         # if it was an array, just keep first element.
252                         $bib->{$fieldname}=$field->{'subfields'}->{$subfield}[0];
253                     } else {
254                         $bib->{$fieldname}=$field->{'subfields'}->{$subfield};
255                     } # if array
256                     print "$field->{'tag'} $subfield $fieldname=$bib->{$fieldname}\n" if $debug;
257                     # see if this field should have trailing chars dropped
258                     if ($strip=$tagmap{ $field->{'tag'} }->{$subfield}->{striptrail} ) {
259                         # FIXME - The next three lines can be rewritten as:
260                         #       $bib =~ s/[\Q$strip\E]+$//;
261                         $strip=~s//\\/; # backquote each char
262                         $stripregex='[ ' . $strip . ']+$';  # remove trailing spaces also
263                         $bib->{$fieldname}=~s/$stripregex//;
264                         # also strip leading spaces
265                         $bib->{$fieldname}=~s/^ +//;
266                     } # if strip
267                     print "Found subfield $field->{'tag'} $subfield " .
268                         "$fieldname = $bib->{$fieldname}\n" if $debug;
269                 } # if tagmap exists
270
271             } # foreach subfield
272
273             # Handle special fields and tags
274             if ($field->{'tag'} eq '001') {
275                 $bib->{controlnumber}=$field->{'indicator'};
276             }
277             if ($field->{'tag'} eq '015') {
278                 # FIXME - I think this can be rewritten as
279                 #       $field->{"subfields"}{"a"} =~ /^\s*C?(\S+)/ and
280                 #               $bib->{"lccn"} = $1;
281                 # This might break with invalid input, though.
282                 $bib->{lccn}=$field->{'subfields'}->{'a'};
283                 $bib->{lccn}=~s/^\s*//;
284                 $bib->{lccn}=~s/^C//;
285                 ($bib->{lccn}) = (split(/\s+/, $bib->{lccn}))[0];
286             }
287
288
289                 # FIXME - Fix indentation
290                 if ($field->{'tag'} eq '260') {
291
292                     $publicationyear=$field->{'subfields'}->{'c'};
293                     # FIXME - "\d\d\d\d" can be rewritten as "\d{4}"
294                     if ($publicationyear=~/c(\d\d\d\d)/) {
295                         $copyrightdate=$1;
296                     }
297                     if ($publicationyear=~/[^c](\d\d\d\d)/) {
298                         $publicationyear=$1;
299                     } elsif ($copyrightdate) {
300                         $publicationyear=$copyrightdate;
301                     } else {
302                         $publicationyear=~/(\d\d\d\d)/;
303                         $publicationyear=$1;
304                     }
305                 }
306                 if ($field->{'tag'} eq '700') {
307                     my $name=$field->{'subfields'}->{'a'};
308                     if ( defined($field->{'subfields'}->{'e'})
309                         and  $field->{'subfields'}->{'e'}=~/ill/) {
310                         $illustrator=$name;
311                     } else {
312                         $additionalauthors.="$name\n";
313                     }
314                 }
315                 if ($field->{'tag'} =~/^5/) {
316                     $notes.="$field->{'subfields'}->{'a'}\n";
317                 }
318                 if ($field->{'tag'} =~/65\d/) {
319                     my $sub;    # FIXME - Never used
320                     my $subject=$field->{'subfields'}->{'a'};
321                     $subject=~s/\.$//;
322                     print "Subject=$subject\n" if $debug;
323                     foreach $subjectsubfield ( 'x','y','z' ) {
324                       # FIXME - $subdivision is only used in this
325                       # loop. Make it 'my' here, rather than in the
326                       # entire function.
327                       # Ditto $subjectsubfield. Make it 'my' in the
328                       # 'foreach' statement.
329                       if ($subdivision=$field->{'subfields'}->{$subjectsubfield}) {
330                         if ( ref($subdivision) eq 'ARRAY' ) {
331                             foreach $s (@$subdivision) {
332                                 $s=~s/\.$//;
333                                 $subject.=" -- $s";
334                             } # foreach subdivision
335                         } else {
336                             $subdivision=~s/\.$//;
337                             $subject.=" -- $subdivision";
338                         } # if array
339                       } # if subfield exists
340                     } # foreach subfield
341                     print "Subject=$subject\n" if $debug;
342                     push @subjects, $subject;
343                 } # if tag 65x
344
345
346         } # foreach field
347         # FIXME - Why not do this up in the "Handle special fields and
348         # tags" section?
349         ($publicationyear       ) && ($bib->{publicationyear}=$publicationyear  );
350         ($copyrightdate         ) && ($bib->{copyrightdate}=$copyrightdate  );
351         ($additionalauthors     ) && ($bib->{additionalauthors}=$additionalauthors  );
352         ($illustrator           ) && ($bib->{illustrator}=$illustrator  );
353         ($notes                 ) && ($bib->{notes}=$notes  );
354         ($#subjects             ) && ($bib->{subject}=\@subjects  );
355                 # FIXME - This doesn't look right: for an array with
356                 # one element, $#subjects == 0, which is false. For an
357                 # array with 0 elements, $#subjects == -1, which is
358                 # true.
359
360         # Misc cleanup
361         if ($bib->{dewey}) {
362             $bib->{dewey}=~s/\///g;     # drop any slashes
363                                         # FIXME - Why? Don't the
364                                         # slashes mean something?
365                                         # The Dewey code is NOT a number,
366                                         # it's a string.
367         }
368
369         if ($bib->{lccn}) {
370            ($bib->{lccn}) = (split(/\s+/, $bib->{lccn}))[0]; # only keep first word
371         }
372
373         if ( $bib->{isbn} ) {
374             $bib->{isbn}=~s/[^\d]*//g;  # drop non-digits
375                         # FIXME - "[^\d]" can be rewritten as "\D"
376                         # FIXME - Does this include the check digit? If so,
377                         # it might be "X".
378         };
379
380         if ( $bib->{issn} ) {
381             $bib->{issn}=~s/^\s*//;
382             ($bib->{issn}) = (split(/\s+/, $bib->{issn}))[0];
383         };
384
385         if ( $bib->{'volume-number'} ) {
386             if ($bib->{'volume-number'}=~/(\d+).*(\d+)/ ) {
387                 $bib->{'volume'}=$1;
388                 $bib->{'number'}=$2;
389             } else {
390                 $bib->{volume}=$bib->{'volume-number'};
391             }
392             delete $bib->{'volume-number'};
393         } # if volume-number
394
395     } else {
396         # FIXME - Style: this sort of error-checking should really go
397         # closer to the actual test, e.g.:
398         #       if (ref($record) ne "ARRAY")
399         #       {
400         #               die "Not an array!"
401         #       }
402         # then the rest of the code which follows can assume that the
403         # input is good, and you don't have to indent as much.
404         print "Error: extractmarcfields: input ref $record is " .
405                 ref($record) . " not ARRAY. Contact sysadmin.\n";
406     }
407     print "</PRE>\n" if $debug;
408
409     return $bib;
410
411 } # sub extractmarcfields
412 #---------------------------------
413
414 #--------------------------
415
416 =item parsemarcfileformat
417
418   @records = &parsemarcfileformat($marc_data);
419
420 Parses the contents of a MARC file.
421
422 C<$marc_data> is a string, the contents of a MARC file.
423 C<&parsemarcfileformat> parses this string into individual MARC
424 records and returns them.
425
426 C<@records> is an array of references-to-hash. Each element is a MARC
427 record; its keys are the MARC tags.
428
429 =cut
430 #'
431 # Parse MARC data in file format with control-character separators
432 #   May be multiple records.
433 # FIXME - Is the input ever likely to be more than a few Kb? If so, it
434 # might be worth changing this function to take a (read-only)
435 # reference-to-string, to avoid unnecessary copying.
436 sub parsemarcfileformat {
437     use strict;
438     # Input is one big text string
439     my $data=shift;
440     # Output is list of records.  Each record is list of field hashes
441     my @records;
442
443     my $splitchar=chr(29);      # \c]
444     my $splitchar2=chr(30);     # \c^
445     my $splitchar3=chr(31);     # \c_
446     my $debug=0;
447     my $record;
448     foreach $record (split(/$splitchar/, $data)) {
449         my @record;
450         my $directory=0;
451         my $tagcounter=0;
452         my %tag;
453         my $field;
454
455         my $leader=substr($record,0,24);
456         print "<pre>parse Leader:$leader</pre>\n" if $debug;
457         push (@record, {
458                 'tag' => 'LDR',
459                 'indicator' => $leader ,
460         } );
461
462         $record=substr($record,24);
463         foreach $field (split(/$splitchar2/, $record)) {
464             my %field;
465             my $tag;
466             my $indicator;
467             unless ($directory) {
468                 # If we didn't already find a directory, extract one.
469                 $directory=$field;
470                 my $itemcounter=1;
471                 my $counter2=0;
472                 my $item;
473                 my $length;
474                 my $start;
475                 while ($item=substr($directory,0,12)) {
476                     # Pull out location of first field
477                     $tag=substr($directory,0,3);
478                     $length=substr($directory,3,4);
479                     $start=substr($directory,7,6);
480
481                     # Bump to next directory entry
482                     $directory=substr($directory,12);
483                     $tag{$counter2}=$tag;
484                     $counter2++;
485                 }
486                 $directory=1;
487                 next;
488             }
489             $tag=$tag{$tagcounter};
490             $tagcounter++;
491             $field{'tag'}=$tag;
492             my @subfields=split(/$splitchar3/, $field);
493             $indicator=$subfields[0];
494             $field{'indicator'}=$indicator;
495             print "<pre>parse indicator:$indicator</pre>\n" if $debug;
496             my $firstline=1;
497             unless ($#subfields==0) {
498                 my %subfields;
499                 my @subfieldlist;
500                 my $i;
501                 for ($i=1; $i<=$#subfields; $i++) {
502                     my $text=$subfields[$i];
503                     my $subfieldcode=substr($text,0,1);
504                     my $subfield=substr($text,1);
505                     # if this subfield already exists, do array
506                     if ($subfields{$subfieldcode}) {
507                         my $subfieldlist=$subfields{$subfieldcode};
508                         if ( ref($subfieldlist) eq 'ARRAY' ) {
509                             # Already an array, add on to it
510                             print "$tag Adding to array $subfieldcode -- $subfield<br>\n" if $debug;
511                             @subfieldlist=@$subfieldlist;
512                             push (@subfieldlist, $subfield);
513                         } else {
514                             # Change simple value to array
515                             print "$tag Arraying $subfieldcode -- $subfield<br>\n" if $debug;
516                             @subfieldlist=($subfields{$subfieldcode}, $subfield);
517                         }
518                         # keep new array
519                         $subfields{$subfieldcode}=\@subfieldlist;
520                     } else {
521                         # subfield doesn't exist yet, keep simple value
522                         $subfields{$subfieldcode}=$subfield;
523                     }
524                 }
525                 $field{'subfields'}=\%subfields;
526             }
527             push (@record, \%field);
528         } # foreach field in record
529         push (@records, \@record);
530         # $counter++;
531     }
532     print "</pre>" if $debug;
533     return @records;
534 } # sub parsemarcfileformat
535
536 #----------------------------------------------
537
538 =item taglabel
539
540   $label = &taglabel($tag);
541
542 Converts a MARC tag (a three-digit number, or "LDR") and returns a
543 descriptive label.
544
545 Note that although the tag looks like a number, it is treated here as
546 a string. Be sure to use
547
548     $label = &taglabel("082");
549
550 and not
551
552     $label = &taglabel(082);    # <-- Invalid octal number!
553
554 =cut
555 #'
556 # FIXME - Does this function mean that %tagtext doesn't need to be
557 # exported?
558 sub taglabel {
559     my ($tag)=@_;
560
561     return $tagtext{$tag};
562
563 } # sub taglabel
564
565 1;
566
567 #---------------------------------------------
568 # $Log$
569 # Revision 1.7  2002/10/13 08:30:38  arensb
570 # Deleted unused variables.
571 # Removed trailing whitespace.
572 #
573 # Revision 1.6  2002/10/10 04:44:28  arensb
574 # Added whitespace to make the POD work.
575 #
576 # Revision 1.5  2002/10/07 00:51:22  arensb
577 # Added POD and some comments.
578 #
579 # Revision 1.4  2002/10/05 09:53:11  arensb
580 # Merged with arensb-context branch: use C4::Context->dbh instead of
581 # &C4Connect, and generally prefer C4::Context over C4::Database.
582 #
583 # Revision 1.3.2.1  2002/10/04 02:57:38  arensb
584 # Removed useless "use C4::Database;" line.
585 #
586 # Revision 1.3  2002/08/14 18:12:52  tonnesen
587 # Added copyright statement to all .pl and .pm files
588 #
589 # Revision 1.2  2002/07/02 20:30:15  tonnesen
590 # Merged SimpleMarc.pm over from rel-1-2
591 #
592 # Revision 1.1.2.4  2002/06/28 14:36:47  amillar
593 # Fix broken logic on illustrator vs. add'l author
594 #
595 # Revision 1.1.2.3  2002/06/26 20:54:32  tonnesen
596 # use warnings breaks on perl 5.005...
597 #
598 # Revision 1.1.2.2  2002/06/26 15:52:55  amillar
599 # Fix display of marc tag labels and indicators
600 #
601 # Revision 1.1.2.1  2002/06/26 07:27:35  amillar
602 # Moved acqui.simple MARC handling to new module SimpleMarc.pm
603 #
604 __END__
605
606 =back
607
608 =head1 AUTHOR
609
610 Koha Developement team <info@koha.org>
611
612 =cut