MT 1587 : CSV export for cart and shelves, with the ability to define different expor...
[koha.git] / C4 / Record.pm
1 package C4::Record;
2 #
3 # Copyright 2006 (C) LibLime
4 # Joshua Ferraro <jmf@liblime.com>
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it under the
9 # terms of the GNU General Public License as published by the Free Software
10 # Foundation; either version 2 of the License, or (at your option) any later
11 # version.
12 #
13 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
14 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License along with
18 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
19 # Suite 330, Boston, MA  02111-1307 USA
20 #
21 #
22 use strict;# use warnings; #FIXME: turn off warnings before release
23
24 # please specify in which methods a given module is used
25 use MARC::Record; # marc2marcxml, marcxml2marc, html2marc, changeEncoding
26 use MARC::File::XML; # marc2marcxml, marcxml2marc, html2marcxml, changeEncoding
27 use MARC::Crosswalk::DublinCore; # marc2dcxml
28 use Biblio::EndnoteStyle;
29 use Unicode::Normalize; # _entity_encode
30 use XML::LibXSLT;
31 use XML::LibXML;
32 use C4::Biblio; #marc2bibtex
33 use C4::Csv; #marc2csv
34 use Text::CSV; #marc2csv
35
36 use vars qw($VERSION @ISA @EXPORT);
37
38 # set the version for version checking
39 $VERSION = 3.00;
40
41 @ISA = qw(Exporter);
42
43 # only export API methods
44
45 @EXPORT = qw(
46   &marc2endnote
47   &marc2marc
48   &marc2marcxml
49   &marcxml2marc
50   &marc2dcxml
51   &marc2modsxml
52   &marc2bibtex
53   &marc2csv
54
55   &html2marcxml
56   &html2marc
57   &changeEncoding
58 );
59
60 =head1 NAME
61
62 C4::Record - MARC, MARCXML, DC, MODS, XML, etc. Record Management Functions and API
63
64 =head1 SYNOPSIS
65
66 New in Koha 3.x. This module handles all record-related management functions.
67
68 =head1 API (EXPORTED FUNCTIONS)
69
70 =head2 marc2marc - Convert from one flavour of ISO-2709 to another
71
72 =over 4
73
74 my ($error,$newmarc) = marc2marc($marc,$to_flavour,$from_flavour,$encoding);
75
76 Returns an ISO-2709 scalar
77
78 =back
79
80 =cut
81
82 sub marc2marc {
83         my ($marc,$to_flavour,$from_flavour,$encoding) = @_;
84         my $error = "Feature not yet implemented\n";
85         return ($error,$marc);
86 }
87
88 =head2 marc2marcxml - Convert from ISO-2709 to MARCXML
89
90 =over 4
91
92 my ($error,$marcxml) = marc2marcxml($marc,$encoding,$flavour);
93
94 Returns a MARCXML scalar
95
96 =over 2
97
98 C<$marc> - an ISO-2709 scalar or MARC::Record object
99
100 C<$encoding> - UTF-8 or MARC-8 [UTF-8]
101
102 C<$flavour> - MARC21 or UNIMARC
103
104 C<$dont_entity_encode> - a flag that instructs marc2marcxml not to entity encode the xml before returning (optional)
105
106 =back
107
108 =back
109
110 =cut
111
112 sub marc2marcxml {
113         my ($marc,$encoding,$flavour,$dont_entity_encode) = @_;
114         my $error; # the error string
115         my $marcxml; # the final MARCXML scalar
116
117         # test if it's already a MARC::Record object, if not, make it one
118         my $marc_record_obj;
119         if ($marc =~ /^MARC::Record/) { # it's already a MARC::Record object
120                 $marc_record_obj = $marc;
121         } else { # it's not a MARC::Record object, make it one
122                 eval { $marc_record_obj = MARC::Record->new_from_usmarc($marc) }; # handle exceptions
123
124                 # conversion to MARC::Record object failed, populate $error
125                 if ($@) { $error .="\nCreation of MARC::Record object failed: ".$MARC::File::ERROR };
126         }
127         # only proceed if no errors so far
128         unless ($error) {
129
130                 # check the record for warnings
131                 my @warnings = $marc_record_obj->warnings();
132                 if (@warnings) {
133                         warn "\nWarnings encountered while processing ISO-2709 record with title \"".$marc_record_obj->title()."\":\n";
134                         foreach my $warn (@warnings) { warn "\t".$warn };
135                 }
136                 unless($encoding) {$encoding = "UTF-8"}; # set default encoding
137                 unless($flavour) {$flavour = C4::Context->preference("marcflavour")}; # set default MARC flavour
138
139                 # attempt to convert the record to MARCXML
140                 eval { $marcxml = $marc_record_obj->as_xml_record($flavour) }; #handle exceptions
141
142                 # record creation failed, populate $error
143                 if ($@) {
144                         $error .= "Creation of MARCXML failed:".$MARC::File::ERROR;
145                         $error .= "Additional information:\n";
146                         my @warnings = $@->warnings();
147                         foreach my $warn (@warnings) { $error.=$warn."\n" };
148
149                 # record creation was successful
150         } else {
151
152                         # check the record for warning flags again (warnings() will be cleared already if there was an error, see above block
153                         @warnings = $marc_record_obj->warnings();
154                         if (@warnings) {
155                                 warn "\nWarnings encountered while processing ISO-2709 record with title \"".$marc_record_obj->title()."\":\n";
156                                 foreach my $warn (@warnings) { warn "\t".$warn };
157                         }
158                 }
159
160                 # only proceed if no errors so far
161                 unless ($error) {
162
163                         # entity encode the XML unless instructed not to
164                 unless ($dont_entity_encode) {
165                         my ($marcxml_entity_encoded) = _entity_encode($marcxml);
166                         $marcxml = $marcxml_entity_encoded;
167                 }
168                 }
169         }
170         # return result to calling program
171         return ($error,$marcxml);
172 }
173
174 =head2 marcxml2marc - Convert from MARCXML to ISO-2709
175
176 =over 4
177
178 my ($error,$marc) = marcxml2marc($marcxml,$encoding,$flavour);
179
180 Returns an ISO-2709 scalar
181
182 =over 2
183
184 C<$marcxml> - a MARCXML record
185
186 C<$encoding> - UTF-8 or MARC-8 [UTF-8]
187
188 C<$flavour> - MARC21 or UNIMARC
189
190 =back
191
192 =back
193
194 =cut
195
196 sub marcxml2marc {
197     my ($marcxml,$encoding,$flavour) = @_;
198         my $error; # the error string
199         my $marc; # the final ISO-2709 scalar
200         unless($encoding) {$encoding = "UTF-8"}; # set the default encoding
201         unless($flavour) {$flavour = C4::Context->preference("marcflavour")}; # set the default MARC flavour
202
203         # attempt to do the conversion
204         eval { $marc = MARC::Record->new_from_xml($marcxml,$encoding,$flavour) }; # handle exceptions
205
206         # record creation failed, populate $error
207         if ($@) {$error .="\nCreation of MARCXML Record failed: ".$@;
208                 $error.=$MARC::File::ERROR if ($MARC::File::ERROR);
209                 };
210         # return result to calling program
211         return ($error,$marc);
212 }
213
214 =head2 marc2dcxml - Convert from ISO-2709 to Dublin Core
215
216 =over 4
217
218 my ($error,$dcxml) = marc2dcxml($marc,$qualified);
219
220 Returns a DublinCore::Record object, will eventually return a Dublin Core scalar
221
222 FIXME: should return actual XML, not just an object
223
224 =over 2
225
226 C<$marc> - an ISO-2709 scalar or MARC::Record object
227
228 C<$qualified> - specify whether qualified Dublin Core should be used in the input or output [0]
229
230 =back
231
232 =back
233
234 =cut
235
236 sub marc2dcxml {
237         my ($marc,$qualified) = @_;
238         my $error;
239     # test if it's already a MARC::Record object, if not, make it one
240     my $marc_record_obj;
241     if ($marc =~ /^MARC::Record/) { # it's already a MARC::Record object
242         $marc_record_obj = $marc;
243     } else { # it's not a MARC::Record object, make it one
244                 eval { $marc_record_obj = MARC::Record->new_from_usmarc($marc) }; # handle exceptions
245
246                 # conversion to MARC::Record object failed, populate $error
247                 if ($@) {
248                         $error .="\nCreation of MARC::Record object failed: ".$MARC::File::ERROR;
249                 }
250         }
251         my $crosswalk = MARC::Crosswalk::DublinCore->new;
252         if ($qualified) {
253                 $crosswalk = MARC::Crosswalk::DublinCore->new( qualified => 1 );
254         }
255         my $dcxml = $crosswalk->as_dublincore($marc_record_obj);
256         my $dcxmlfinal = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
257         $dcxmlfinal .= "<metadata
258   xmlns=\"http://example.org/myapp/\"
259   xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
260   xsi:schemaLocation=\"http://example.org/myapp/ http://example.org/myapp/schema.xsd\"
261   xmlns:dc=\"http://purl.org/dc/elements/1.1/\"
262   xmlns:dcterms=\"http://purl.org/dc/terms/\">";
263
264         foreach my $element ( $dcxml->elements() ) {
265                 $dcxmlfinal.="<"."dc:".$element->name().">".$element->content()."</"."dc:".$element->name().">\n";
266     }
267         $dcxmlfinal .= "\n</metadata>";
268         return ($error,$dcxmlfinal);
269 }
270 =head2 marc2modsxml - Convert from ISO-2709 to MODS
271
272 =over 4
273
274 my ($error,$modsxml) = marc2modsxml($marc);
275
276 Returns a MODS scalar
277
278 =back
279
280 =cut
281
282 sub marc2modsxml {
283         my ($marc) = @_;
284         # grab the XML, run it through our stylesheet, push it out to the browser
285         my $xmlrecord = marc2marcxml($marc);
286         my $xslfile = C4::Context->config('intrahtdocs')."/prog/en/xslt/MARC21slim2MODS3-1.xsl";
287         my $parser = XML::LibXML->new();
288         my $xslt = XML::LibXSLT->new();
289         my $source = $parser->parse_string($xmlrecord);
290         my $style_doc = $parser->parse_file($xslfile);
291         my $stylesheet = $xslt->parse_stylesheet($style_doc);
292         my $results = $stylesheet->transform($source);
293         my $newxmlrecord = $stylesheet->output_string($results);
294         return ($newxmlrecord);
295 }
296
297 sub marc2endnote {
298     my ($marc) = @_;
299         my $marc_rec_obj =  MARC::Record->new_from_usmarc($marc);
300         my $f260 = $marc_rec_obj->field('260');
301         my $f260a = $f260->subfield('a') if $f260;
302     my $f710 = $marc_rec_obj->field('710');
303     my $f710a = $f710->subfield('a') if $f710;
304         my $f500 = $marc_rec_obj->field('500');
305         my $abstract = $f500->subfield('a') if $f500;
306         my $fields = {
307                 DB => C4::Context->preference("LibraryName"),
308                 Title => $marc_rec_obj->title(),        
309                 Author => $marc_rec_obj->author(),      
310                 Publisher => $f710a,
311                 City => $f260a,
312                 Year => $marc_rec_obj->publication_date,
313                 Abstract => $abstract,
314         };
315         my $endnote;
316         my $style = new Biblio::EndnoteStyle();
317         my $template;
318         $template.= "DB - DB\n" if C4::Context->preference("LibraryName");
319         $template.="T1 - Title\n" if $marc_rec_obj->title();
320         $template.="A1 - Author\n" if $marc_rec_obj->author();
321         $template.="PB - Publisher\n" if  $f710a;
322         $template.="CY - City\n" if $f260a;
323         $template.="Y1 - Year\n" if $marc_rec_obj->publication_date;
324         $template.="AB - Abstract\n" if $abstract;
325         my ($text, $errmsg) = $style->format($template, $fields);
326         return ($text);
327         
328 }
329
330 =head2 marc2csv - Convert from UNIMARC to CSV
331
332 =over 4
333
334 my ($csv) = marc2csv($record, $csvprofileid);
335
336 Returns a CSV scalar
337
338 =over 2
339
340 C<$record> - a MARC::Record object
341
342 C<$csvprofileid> - the id of the CSV profile to use for the export (see export_format.export_format_id and the GetCsvProfiles function in C4::Csv)
343
344 =back
345
346 =back
347
348 =cut
349
350
351 sub marc2csv {
352     my ($record, $id, $header) = @_;
353     my $output;
354     my $csv = Text::CSV->new();
355
356     # Get the information about the csv profile
357     my $marcfieldslist = GetMarcFieldsForCsv($id);
358
359     # Getting the marcfields as an array
360     my @marcfields = split('\|', $marcfieldslist);
361
362     # If we have to insert the headers
363     if ($header) {
364         my @marcfieldsheaders;
365
366         my $dbh   = C4::Context->dbh;
367
368         # For each field or subfield
369         foreach (@marcfields) {
370             # We get the matching tag name
371             if (index($_, '$') > 0) {
372                 my ($fieldtag, $subfieldtag) = split('\$', $_);
373                 my $query = "SELECT liblibrarian FROM marc_subfield_structure WHERE tagfield=? AND tagsubfield=?";
374                 my $sth = $dbh->prepare($query);
375                 $sth->execute($fieldtag, $subfieldtag);
376                 my @results = $sth->fetchrow_array();
377                 push @marcfieldsheaders, @results[0];
378             } else {
379                 my $query = "SELECT liblibrarian FROM marc_tag_structure WHERE tagfield=?";
380                 my $sth = $dbh->prepare($query);
381                 $sth->execute($_);
382                 my @results = $sth->fetchrow_array();
383                 push @marcfieldsheaders, @results[0];
384             }
385         }
386         $csv->combine(@marcfieldsheaders);
387         $output = $csv->string() . "\n";        
388     }
389
390     # For each marcfield to export
391     my @fieldstab;
392     foreach my $marcfield (@marcfields) {
393         # If it is a subfield
394         if (index($marcfield, '$') > 0) {
395             my ($fieldtag, $subfieldtag) = split('\$', $marcfield);
396             my @fields = $record->field($fieldtag);
397             my @tmpfields;
398
399             # For each field
400             foreach my $field (@fields) {
401
402                 # We take every matching subfield
403                 my @subfields = $field->subfield($subfieldtag);
404                 foreach my $subfield (@subfields) {
405                     push @tmpfields, $subfield;
406                 }
407             }
408             push (@fieldstab, join(',', @tmpfields));           
409         # Or a field
410         } else {
411             my @fields = ($record->field($marcfield));
412             push (@fieldstab, join(',', map($_->as_string(), @fields)));                
413          }
414     };
415
416     $csv->combine(@fieldstab);
417     $output .= $csv->string() . "\n";
418    
419     return $output;
420
421 }
422
423
424 =head2 html2marcxml
425
426 =over 4
427
428 my ($error,$marcxml) = html2marcxml($tags,$subfields,$values,$indicator,$ind_tag);
429
430 Returns a MARCXML scalar
431
432 this is used in addbiblio.pl and additem.pl to build the MARCXML record from 
433 the form submission.
434
435 FIXME: this could use some better code documentation
436
437 =back
438
439 =cut
440
441 sub html2marcxml {
442     my ($tags,$subfields,$values,$indicator,$ind_tag) = @_;
443         my $error;
444         # add the header info
445     my $marcxml= MARC::File::XML::header(C4::Context->preference('TemplateEncoding'),C4::Context->preference('marcflavour'));
446
447         # some flags used to figure out where in the record we are
448     my $prevvalue;
449     my $prevtag=-1;
450     my $first=1;
451     my $j = -1;
452
453         # handle characters that would cause the parser to choke FIXME: is there a more elegant solution?
454     for (my $i=0;$i<=@$tags;$i++){
455                 @$values[$i] =~ s/&/&amp;/g;
456                 @$values[$i] =~ s/</&lt;/g;
457                 @$values[$i] =~ s/>/&gt;/g;
458                 @$values[$i] =~ s/"/&quot;/g;
459                 @$values[$i] =~ s/'/&apos;/g;
460         
461                 if ((@$tags[$i] ne $prevtag)){
462                         $j++ unless (@$tags[$i] eq "");
463                         #warn "IND:".substr(@$indicator[$j],0,1).substr(@$indicator[$j],1,1)." ".@$tags[$i];
464                         if (!$first){
465                                 $marcxml.="</datafield>\n";
466                                 if ((@$tags[$i] > 10) && (@$values[$i] ne "")){
467                         my $ind1 = substr(@$indicator[$j],0,1);
468                                         my $ind2 = substr(@$indicator[$j],1,1);
469                                         $marcxml.="<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
470                                         $marcxml.="<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
471                                         $first=0;
472                                 } else {
473                                         $first=1;
474                                 }
475                         } else {
476                                 if (@$values[$i] ne "") {
477                                         # handle the leader
478                                         if (@$tags[$i] eq "000") {
479                                                 $marcxml.="<leader>@$values[$i]</leader>\n";
480                                                 $first=1;
481                                         # rest of the fixed fields
482                                         } elsif (@$tags[$i] lt '010') { # don't compare numerically 010 == 8
483                                                 $marcxml.="<controlfield tag=\"@$tags[$i]\">@$values[$i]</controlfield>\n";
484                                                 $first=1;
485                                         } else {
486                                                 my $ind1 = substr(@$indicator[$j],0,1);
487                                                 my $ind2 = substr(@$indicator[$j],1,1);
488                                                 $marcxml.="<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
489                                                 $marcxml.="<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
490                                                 $first=0;
491                                         }
492                                 }
493                         }
494                 } else { # @$tags[$i] eq $prevtag
495                         if (@$values[$i] eq "") {
496                         } else {
497                                 if ($first){
498                                         my $ind1 = substr(@$indicator[$j],0,1);
499                                         my $ind2 = substr(@$indicator[$j],1,1);
500                                         $marcxml.="<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
501                                         $first=0;
502                                 }
503                                 $marcxml.="<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
504                         }
505                 }
506                 $prevtag = @$tags[$i];
507         }
508         $marcxml.= MARC::File::XML::footer();
509         #warn $marcxml;
510         return ($error,$marcxml);
511 }
512
513 =head2 html2marc
514
515 =over 4
516
517 Probably best to avoid using this ... it has some rather striking problems:
518
519 =over 2
520
521 * saves blank subfields
522
523 * subfield order is hardcoded to always start with 'a' for repeatable tags (because it is hardcoded in the addfield routine).
524
525 * only possible to specify one set of indicators for each set of tags (ie, one for all the 650s). (because they were stored in a hash with the tag as the key).
526
527 * the underlying routines didn't support subfield reordering or subfield repeatability.
528
529 =back 
530
531 I've left it in here because it could be useful if someone took the time to fix it. -- kados
532
533 =back
534
535 =cut
536
537 sub html2marc {
538     my ($dbh,$rtags,$rsubfields,$rvalues,%indicators) = @_;
539     my $prevtag = -1;
540     my $record = MARC::Record->new();
541 #   my %subfieldlist=();
542     my $prevvalue; # if tag <10
543     my $field; # if tag >=10
544     for (my $i=0; $i< @$rtags; $i++) {
545         # rebuild MARC::Record
546 #           warn "0=>".@$rtags[$i].@$rsubfields[$i]." = ".@$rvalues[$i].": ";
547         if (@$rtags[$i] ne $prevtag) {
548             if ($prevtag < 10) {
549                 if ($prevvalue) {
550                     if (($prevtag ne '000') && ($prevvalue ne "")) {
551                         $record->add_fields((sprintf "%03s",$prevtag),$prevvalue);
552                     } elsif ($prevvalue ne ""){
553                         $record->leader($prevvalue);
554                     }
555                 }
556             } else {
557                 if (($field) && ($field ne "")) {
558                     $record->add_fields($field);
559                 }
560             }
561             $indicators{@$rtags[$i]}.='  ';
562                 # skip blank tags, I hope this works
563                 if (@$rtags[$i] eq ''){
564                 $prevtag = @$rtags[$i];
565                 undef $field;
566                 next;
567             }
568             if (@$rtags[$i] <10) {
569                 $prevvalue= @$rvalues[$i];
570                 undef $field;
571             } else {
572                 undef $prevvalue;
573                 if (@$rvalues[$i] eq "") {
574                 undef $field;
575                 } else {
576                 $field = MARC::Field->new( (sprintf "%03s",@$rtags[$i]), substr($indicators{@$rtags[$i]},0,1),substr($indicators{@$rtags[$i]},1,1), @$rsubfields[$i] => @$rvalues[$i]);
577                 }
578 #           warn "1=>".@$rtags[$i].@$rsubfields[$i]." = ".@$rvalues[$i].": ".$field->as_formatted;
579             }
580             $prevtag = @$rtags[$i];
581         } else {
582             if (@$rtags[$i] <10) {
583                 $prevvalue=@$rvalues[$i];
584             } else {
585                 if (length(@$rvalues[$i])>0) {
586                     $field->add_subfields(@$rsubfields[$i] => @$rvalues[$i]);
587 #           warn "2=>".@$rtags[$i].@$rsubfields[$i]." = ".@$rvalues[$i].": ".$field->as_formatted;
588                 }
589             }
590             $prevtag= @$rtags[$i];
591         }
592     }
593     #}
594     # the last has not been included inside the loop... do it now !
595     #use Data::Dumper;
596     #warn Dumper($field->{_subfields});
597     $record->add_fields($field) if (($field) && $field ne "");
598     #warn "HTML2MARC=".$record->as_formatted;
599     return $record;
600 }
601
602 =head2 changeEncoding - Change the encoding of a record
603
604 =over 4
605
606 my ($error, $newrecord) = changeEncoding($record,$format,$flavour,$to_encoding,$from_encoding);
607
608 Changes the encoding of a record
609
610 =over 2
611
612 C<$record> - the record itself can be in ISO-2709, a MARC::Record object, or MARCXML for now (required)
613
614 C<$format> - MARC or MARCXML (required)
615
616 C<$flavour> - MARC21 or UNIMARC, if MARC21, it will change the leader (optional) [defaults to Koha system preference]
617
618 C<$to_encoding> - the encoding you want the record to end up in (optional) [UTF-8]
619
620 C<$from_encoding> - the encoding the record is currently in (optional, it will probably be able to tell unless there's a problem with the record)
621
622 =back 
623
624 FIXME: the from_encoding doesn't work yet
625
626 FIXME: better handling for UNIMARC, it should allow management of 100 field
627
628 FIXME: shouldn't have to convert to and from xml/marc just to change encoding someone needs to re-write MARC::Record's 'encoding' method to actually alter the encoding rather than just changing the leader
629
630 =back
631
632 =cut
633
634 sub changeEncoding {
635         my ($record,$format,$flavour,$to_encoding,$from_encoding) = @_;
636         my $newrecord;
637         my $error;
638         unless($flavour) {$flavour = C4::Context->preference("marcflavour")};
639         unless($to_encoding) {$to_encoding = "UTF-8"};
640         
641         # ISO-2709 Record (MARC21 or UNIMARC)
642         if (lc($format) =~ /^marc$/o) {
643                 # if we're converting encoding of an ISO2709 file, we need to roundtrip through XML
644                 #       because MARC::Record doesn't directly provide us with an encoding method
645                 #       It's definitely less than idea and should be fixed eventually - kados
646                 my $marcxml; # temporary storage of MARCXML scalar
647                 ($error,$marcxml) = marc2marcxml($record,$to_encoding,$flavour);
648                 unless ($error) {
649                         ($error,$newrecord) = marcxml2marc($marcxml,$to_encoding,$flavour);
650                 }
651         
652         # MARCXML Record
653         } elsif (lc($format) =~ /^marcxml$/o) { # MARCXML Record
654                 my $marc;
655                 ($error,$marc) = marcxml2marc($record,$to_encoding,$flavour);
656                 unless ($error) {
657                         ($error,$newrecord) = marc2marcxml($record,$to_encoding,$flavour);
658                 }
659         } else {
660                 $error.="Unsupported record format:".$format;
661         }
662         return ($error,$newrecord);
663 }
664
665 =head2 marc2bibtex - Convert from MARC21 and UNIMARC to BibTex
666
667 =over 4
668
669 my ($bibtex) = marc2bibtex($record, $id);
670
671 Returns a BibTex scalar
672
673 =over 2
674
675 C<$record> - a MARC::Record object
676
677 C<$id> - an id for the BibTex record (might be the biblionumber)
678
679 =back
680
681 =back
682
683 =cut
684
685
686 sub marc2bibtex {
687     my ($record, $id) = @_;
688     my $tex;
689
690     # Authors
691     my $marcauthors = GetMarcAuthors($record,C4::Context->preference("marcflavour"));
692     my $author;
693     for my $authors ( map { map { @$_ } values %$_  } @$marcauthors  ) {  
694         $author .= " and " if ($author && $$authors{value});
695         $author .= $$authors{value} if ($$authors{value}); 
696     }
697
698     # Defining the conversion hash according to the marcflavour
699     my %bh;
700     if (C4::Context->preference("marcflavour") eq "UNIMARC") {
701         
702         # FIXME, TODO : handle repeatable fields
703         # TODO : handle more types of documents
704
705         # Unimarc to bibtex hash
706         %bh = (
707
708             # Mandatory
709             author    => $author,
710             title     => $record->subfield("200", "a") || "",
711             editor    => $record->subfield("210", "g") || "",
712             publisher => $record->subfield("210", "c") || "",
713             year      => $record->subfield("210", "d") || $record->subfield("210", "h") || "",
714
715             # Optional
716             volume  =>  $record->subfield("200", "v") || "",
717             series  =>  $record->subfield("225", "a") || "",
718             address =>  $record->subfield("210", "a") || "",
719             edition =>  $record->subfield("205", "a") || "",
720             note    =>  $record->subfield("300", "a") || "",
721             url     =>  $record->subfield("856", "u") || ""
722         );
723     } else {
724
725         # Marc21 to bibtex hash
726         %bh = (
727
728             # Mandatory
729             author    => $author,
730             title     => $record->subfield("245", "a") || "",
731             editor    => $record->subfield("260", "f") || "",
732             publisher => $record->subfield("260", "b") || "",
733             year      => $record->subfield("260", "c") || $record->subfield("260", "g") || "",
734
735             # Optional
736             # unimarc to marc21 specification says not to convert 200$v to marc21
737             series  =>  $record->subfield("490", "a") || "",
738             address =>  $record->subfield("260", "a") || "",
739             edition =>  $record->subfield("250", "a") || "",
740             note    =>  $record->subfield("500", "a") || "",
741             url     =>  $record->subfield("856", "u") || ""
742         );
743     }
744
745     $tex .= "\@book{";
746     $tex .= join(",\n", $id, map { $bh{$_} ? qq(\t$_ = "$bh{$_}") : () } keys %bh);
747     $tex .= "\n}\n";
748
749     return $tex;
750 }
751
752
753 =head1 INTERNAL FUNCTIONS
754
755 =head2 _entity_encode - Entity-encode an array of strings
756
757 =over 4
758
759 my ($entity_encoded_string) = _entity_encode($string);
760
761 or
762
763 my (@entity_encoded_strings) = _entity_encode(@strings);
764
765 Entity-encode an array of strings
766
767 =back
768
769 =cut
770
771 sub _entity_encode {
772         my @strings = @_;
773         my @strings_entity_encoded;
774         foreach my $string (@strings) {
775                 my $nfc_string = NFC($string);
776                 $nfc_string =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
777                 push @strings_entity_encoded, $nfc_string;
778         }
779         return @strings_entity_encoded;
780 }
781
782 END { }       # module clean-up code here (global destructor)
783 1;
784 __END__
785
786 =head1 AUTHOR
787
788 Joshua Ferraro <jmf@liblime.com>
789
790 =head1 MODIFICATIONS
791
792
793 =cut