MT2116: Addons to the CSV export
[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 C<$header> - true if the headers are to be printed (typically at first pass)
345
346 =back
347
348 =back
349
350 =cut
351
352
353 sub marc2csv {
354     my ($record, $id, $header) = @_;
355     my $output;
356
357     # Get the information about the csv profile
358     my $profile = GetCsvProfile($id);
359
360     # Getting separators
361     my $csvseparator      = $profile->{csv_separator}      || ',';
362     my $fieldseparator    = $profile->{field_separator}    || '#';
363     my $subfieldseparator = $profile->{subfield_separator} || '|';
364
365     # TODO: Be more generic (in case we have to handle other protected chars or more separators)
366     if ($csvseparator eq '\t') { $csvseparator = "\t" }
367     if ($fieldseparator eq '\t') { $fieldseparator = "\t" }
368     if ($subfieldseparator eq '\t') { $subfieldseparator = "\t" }
369
370     # Init CSV
371     my $csv = Text::CSV->new({ sep_char => $csvseparator });
372
373     # Getting the marcfields
374     my $marcfieldslist = $profile->{marcfields};
375
376     # Getting the marcfields as an array
377     my @marcfieldsarray = split('\|', $marcfieldslist);
378
379    # Separating the marcfields from the the user-supplied headers
380     my @marcfields;
381     foreach (@marcfieldsarray) {
382         my @result = split('=', $_);
383         if (scalar(@result) == 2) {
384            push @marcfields, { header => $result[0], field => $result[1] }; 
385         } else {
386            push @marcfields, { field => $result[0] }
387         }
388     }
389
390     # If we have to insert the headers
391     if ($header) {
392         my @marcfieldsheaders;
393         my $dbh   = C4::Context->dbh;
394
395         # For each field or subfield
396         foreach (@marcfields) {
397
398             my $field = $_->{field};
399
400             # If we have a user-supplied header, we use it
401             if (exists $_->{header}) {
402                     push @marcfieldsheaders, $_->{header};
403             } else {
404 warn "else";
405                 # If not, we get the matching tag name from koha
406                 if (index($field, '$') > 0) {
407                     my ($fieldtag, $subfieldtag) = split('\$', $field);
408                     my $query = "SELECT liblibrarian FROM marc_subfield_structure WHERE tagfield=? AND tagsubfield=?";
409                     my $sth = $dbh->prepare($query);
410                     $sth->execute($fieldtag, $subfieldtag);
411                     my @results = $sth->fetchrow_array();
412 warn "subfield $fieldtag, $subfieldtag";
413                     push @marcfieldsheaders, $results[0];
414                 } else {
415                     my $query = "SELECT liblibrarian FROM marc_tag_structure WHERE tagfield=?";
416                     my $sth = $dbh->prepare($query);
417                     $sth->execute($field);
418                     my @results = $sth->fetchrow_array();
419 warn "field $results[0]";
420                     push @marcfieldsheaders, $results[0];
421                 }
422             }
423         }
424         $csv->combine(@marcfieldsheaders);
425         $output = $csv->string() . "\n";        
426     }
427
428     # For each marcfield to export
429     my @fieldstab;
430     foreach (@marcfields) {
431         my $marcfield = $_->{field};
432         # If it is a subfield
433         if (index($marcfield, '$') > 0) {
434             my ($fieldtag, $subfieldtag) = split('\$', $marcfield);
435             my @fields = $record->field($fieldtag);
436             my @tmpfields;
437
438             # For each field
439             foreach my $field (@fields) {
440
441                 # We take every matching subfield
442                 my @subfields = $field->subfield($subfieldtag);
443                 foreach my $subfield (@subfields) {
444                     push @tmpfields, $subfield;
445                 }
446             }
447             push (@fieldstab, join($subfieldseparator, @tmpfields));            
448         # Or a field
449         } else {
450             my @fields = ($record->field($marcfield));
451             push (@fieldstab, join($fieldseparator, map($_->as_string(), @fields)));            
452          }
453     };
454
455     $csv->combine(@fieldstab);
456     $output .= $csv->string() . "\n";
457    
458     return $output;
459
460 }
461
462
463 =head2 html2marcxml
464
465 =over 4
466
467 my ($error,$marcxml) = html2marcxml($tags,$subfields,$values,$indicator,$ind_tag);
468
469 Returns a MARCXML scalar
470
471 this is used in addbiblio.pl and additem.pl to build the MARCXML record from 
472 the form submission.
473
474 FIXME: this could use some better code documentation
475
476 =back
477
478 =cut
479
480 sub html2marcxml {
481     my ($tags,$subfields,$values,$indicator,$ind_tag) = @_;
482         my $error;
483         # add the header info
484     my $marcxml= MARC::File::XML::header(C4::Context->preference('TemplateEncoding'),C4::Context->preference('marcflavour'));
485
486         # some flags used to figure out where in the record we are
487     my $prevvalue;
488     my $prevtag=-1;
489     my $first=1;
490     my $j = -1;
491
492         # handle characters that would cause the parser to choke FIXME: is there a more elegant solution?
493     for (my $i=0;$i<=@$tags;$i++){
494                 @$values[$i] =~ s/&/&amp;/g;
495                 @$values[$i] =~ s/</&lt;/g;
496                 @$values[$i] =~ s/>/&gt;/g;
497                 @$values[$i] =~ s/"/&quot;/g;
498                 @$values[$i] =~ s/'/&apos;/g;
499         
500                 if ((@$tags[$i] ne $prevtag)){
501                         $j++ unless (@$tags[$i] eq "");
502                         #warn "IND:".substr(@$indicator[$j],0,1).substr(@$indicator[$j],1,1)." ".@$tags[$i];
503                         if (!$first){
504                                 $marcxml.="</datafield>\n";
505                                 if ((@$tags[$i] > 10) && (@$values[$i] ne "")){
506                         my $ind1 = substr(@$indicator[$j],0,1);
507                                         my $ind2 = substr(@$indicator[$j],1,1);
508                                         $marcxml.="<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
509                                         $marcxml.="<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
510                                         $first=0;
511                                 } else {
512                                         $first=1;
513                                 }
514                         } else {
515                                 if (@$values[$i] ne "") {
516                                         # handle the leader
517                                         if (@$tags[$i] eq "000") {
518                                                 $marcxml.="<leader>@$values[$i]</leader>\n";
519                                                 $first=1;
520                                         # rest of the fixed fields
521                                         } elsif (@$tags[$i] lt '010') { # don't compare numerically 010 == 8
522                                                 $marcxml.="<controlfield tag=\"@$tags[$i]\">@$values[$i]</controlfield>\n";
523                                                 $first=1;
524                                         } else {
525                                                 my $ind1 = substr(@$indicator[$j],0,1);
526                                                 my $ind2 = substr(@$indicator[$j],1,1);
527                                                 $marcxml.="<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
528                                                 $marcxml.="<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
529                                                 $first=0;
530                                         }
531                                 }
532                         }
533                 } else { # @$tags[$i] eq $prevtag
534                         if (@$values[$i] eq "") {
535                         } else {
536                                 if ($first){
537                                         my $ind1 = substr(@$indicator[$j],0,1);
538                                         my $ind2 = substr(@$indicator[$j],1,1);
539                                         $marcxml.="<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
540                                         $first=0;
541                                 }
542                                 $marcxml.="<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
543                         }
544                 }
545                 $prevtag = @$tags[$i];
546         }
547         $marcxml.= MARC::File::XML::footer();
548         #warn $marcxml;
549         return ($error,$marcxml);
550 }
551
552 =head2 html2marc
553
554 =over 4
555
556 Probably best to avoid using this ... it has some rather striking problems:
557
558 =over 2
559
560 * saves blank subfields
561
562 * subfield order is hardcoded to always start with 'a' for repeatable tags (because it is hardcoded in the addfield routine).
563
564 * 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).
565
566 * the underlying routines didn't support subfield reordering or subfield repeatability.
567
568 =back 
569
570 I've left it in here because it could be useful if someone took the time to fix it. -- kados
571
572 =back
573
574 =cut
575
576 sub html2marc {
577     my ($dbh,$rtags,$rsubfields,$rvalues,%indicators) = @_;
578     my $prevtag = -1;
579     my $record = MARC::Record->new();
580 #   my %subfieldlist=();
581     my $prevvalue; # if tag <10
582     my $field; # if tag >=10
583     for (my $i=0; $i< @$rtags; $i++) {
584         # rebuild MARC::Record
585 #           warn "0=>".@$rtags[$i].@$rsubfields[$i]." = ".@$rvalues[$i].": ";
586         if (@$rtags[$i] ne $prevtag) {
587             if ($prevtag < 10) {
588                 if ($prevvalue) {
589                     if (($prevtag ne '000') && ($prevvalue ne "")) {
590                         $record->add_fields((sprintf "%03s",$prevtag),$prevvalue);
591                     } elsif ($prevvalue ne ""){
592                         $record->leader($prevvalue);
593                     }
594                 }
595             } else {
596                 if (($field) && ($field ne "")) {
597                     $record->add_fields($field);
598                 }
599             }
600             $indicators{@$rtags[$i]}.='  ';
601                 # skip blank tags, I hope this works
602                 if (@$rtags[$i] eq ''){
603                 $prevtag = @$rtags[$i];
604                 undef $field;
605                 next;
606             }
607             if (@$rtags[$i] <10) {
608                 $prevvalue= @$rvalues[$i];
609                 undef $field;
610             } else {
611                 undef $prevvalue;
612                 if (@$rvalues[$i] eq "") {
613                 undef $field;
614                 } else {
615                 $field = MARC::Field->new( (sprintf "%03s",@$rtags[$i]), substr($indicators{@$rtags[$i]},0,1),substr($indicators{@$rtags[$i]},1,1), @$rsubfields[$i] => @$rvalues[$i]);
616                 }
617 #           warn "1=>".@$rtags[$i].@$rsubfields[$i]." = ".@$rvalues[$i].": ".$field->as_formatted;
618             }
619             $prevtag = @$rtags[$i];
620         } else {
621             if (@$rtags[$i] <10) {
622                 $prevvalue=@$rvalues[$i];
623             } else {
624                 if (length(@$rvalues[$i])>0) {
625                     $field->add_subfields(@$rsubfields[$i] => @$rvalues[$i]);
626 #           warn "2=>".@$rtags[$i].@$rsubfields[$i]." = ".@$rvalues[$i].": ".$field->as_formatted;
627                 }
628             }
629             $prevtag= @$rtags[$i];
630         }
631     }
632     #}
633     # the last has not been included inside the loop... do it now !
634     #use Data::Dumper;
635     #warn Dumper($field->{_subfields});
636     $record->add_fields($field) if (($field) && $field ne "");
637     #warn "HTML2MARC=".$record->as_formatted;
638     return $record;
639 }
640
641 =head2 changeEncoding - Change the encoding of a record
642
643 =over 4
644
645 my ($error, $newrecord) = changeEncoding($record,$format,$flavour,$to_encoding,$from_encoding);
646
647 Changes the encoding of a record
648
649 =over 2
650
651 C<$record> - the record itself can be in ISO-2709, a MARC::Record object, or MARCXML for now (required)
652
653 C<$format> - MARC or MARCXML (required)
654
655 C<$flavour> - MARC21 or UNIMARC, if MARC21, it will change the leader (optional) [defaults to Koha system preference]
656
657 C<$to_encoding> - the encoding you want the record to end up in (optional) [UTF-8]
658
659 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)
660
661 =back 
662
663 FIXME: the from_encoding doesn't work yet
664
665 FIXME: better handling for UNIMARC, it should allow management of 100 field
666
667 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
668
669 =back
670
671 =cut
672
673 sub changeEncoding {
674         my ($record,$format,$flavour,$to_encoding,$from_encoding) = @_;
675         my $newrecord;
676         my $error;
677         unless($flavour) {$flavour = C4::Context->preference("marcflavour")};
678         unless($to_encoding) {$to_encoding = "UTF-8"};
679         
680         # ISO-2709 Record (MARC21 or UNIMARC)
681         if (lc($format) =~ /^marc$/o) {
682                 # if we're converting encoding of an ISO2709 file, we need to roundtrip through XML
683                 #       because MARC::Record doesn't directly provide us with an encoding method
684                 #       It's definitely less than idea and should be fixed eventually - kados
685                 my $marcxml; # temporary storage of MARCXML scalar
686                 ($error,$marcxml) = marc2marcxml($record,$to_encoding,$flavour);
687                 unless ($error) {
688                         ($error,$newrecord) = marcxml2marc($marcxml,$to_encoding,$flavour);
689                 }
690         
691         # MARCXML Record
692         } elsif (lc($format) =~ /^marcxml$/o) { # MARCXML Record
693                 my $marc;
694                 ($error,$marc) = marcxml2marc($record,$to_encoding,$flavour);
695                 unless ($error) {
696                         ($error,$newrecord) = marc2marcxml($record,$to_encoding,$flavour);
697                 }
698         } else {
699                 $error.="Unsupported record format:".$format;
700         }
701         return ($error,$newrecord);
702 }
703
704 =head1 INTERNAL FUNCTIONS
705
706 =head2 _entity_encode - Entity-encode an array of strings
707
708 =over 4
709
710 my ($entity_encoded_string) = _entity_encode($string);
711
712 or
713
714 my (@entity_encoded_strings) = _entity_encode(@strings);
715
716 Entity-encode an array of strings
717
718 =back
719
720 =cut
721
722 sub _entity_encode {
723         my @strings = @_;
724         my @strings_entity_encoded;
725         foreach my $string (@strings) {
726                 my $nfc_string = NFC($string);
727                 $nfc_string =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
728                 push @strings_entity_encoded, $nfc_string;
729         }
730         return @strings_entity_encoded;
731 }
732
733 END { }       # module clean-up code here (global destructor)
734 1;
735 __END__
736
737 =head1 AUTHOR
738
739 Joshua Ferraro <jmf@liblime.com>
740
741 =head1 MODIFICATIONS
742
743
744 =cut