|
Line 0
Link Here
|
|
|
1 |
package Koha::Import::Oaipmh; |
| 2 |
|
| 3 |
# This file is part of Koha. |
| 4 |
# |
| 5 |
# Koha is free software; you can redistribute it and/or modify it |
| 6 |
# under the terms of the GNU General Public License as published by |
| 7 |
# the Free Software Foundation; either version 3 of the License, or |
| 8 |
# (at your option) any later version. |
| 9 |
# |
| 10 |
# Koha is distributed in the hope that it will be useful, but |
| 11 |
# WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 |
# GNU General Public License for more details. |
| 14 |
# |
| 15 |
# You should have received a copy of the GNU General Public License |
| 16 |
|
| 17 |
use Modern::Perl; |
| 18 |
use XML::LibXML::Reader; |
| 19 |
use MARC::Record; |
| 20 |
use DateTime::Format::Strptime; |
| 21 |
|
| 22 |
use Koha::Import::Oaipmh::Biblios; |
| 23 |
use C4::Context; |
| 24 |
use C4::Biblio; |
| 25 |
|
| 26 |
my $xpc = XML::LibXML::XPathContext->new(); |
| 27 |
$xpc->registerNs('oai','http://www.openarchives.org/OAI/2.0/'); |
| 28 |
my $xpath_identifier = XML::LibXML::XPathExpression->new("oai:header/oai:identifier"); |
| 29 |
my $xpath_datestamp = XML::LibXML::XPathExpression->new("oai:header/oai:datestamp"); |
| 30 |
my $xpath_status = XML::LibXML::XPathExpression->new(q{oai:header/@status}); |
| 31 |
my $xpath_metadata = XML::LibXML::XPathExpression->new(q{oai:metadata/*}); |
| 32 |
|
| 33 |
my $strp = DateTime::Format::Strptime->new( |
| 34 |
pattern => '%F %T', |
| 35 |
time_zone => 'floating', |
| 36 |
); |
| 37 |
|
| 38 |
=head1 API Model |
| 39 |
|
| 40 |
=head2 Methods |
| 41 |
|
| 42 |
=cut |
| 43 |
|
| 44 |
sub new { |
| 45 |
my ($class, $args) = @_; |
| 46 |
$args = {} unless defined $args; |
| 47 |
return bless ($args, $class); |
| 48 |
} |
| 49 |
|
| 50 |
#NOTE: To improve performance, you could turn off autocommit at the start of this function, then commit at the end of the function |
| 51 |
sub process_metadata { |
| 52 |
my ($self, $args) = @_; |
| 53 |
my $added = 0; |
| 54 |
my $updated = 0; |
| 55 |
my $deleted = 0; |
| 56 |
my $skipped = 0; |
| 57 |
|
| 58 |
my $repository = $args->{repository}; |
| 59 |
my $records = $args->{records}; |
| 60 |
my $frameworkcode = $args->{frameworkcode} // ''; |
| 61 |
if ($repository && $records) { |
| 62 |
my $reader = XML::LibXML::Reader->new( string => $records ); |
| 63 |
my $each_pattern = XML::LibXML::Pattern->new('//oai:record', { 'oai' => "http://www.openarchives.org/OAI/2.0/" }); |
| 64 |
while (my $rv = $reader->nextPatternMatch($each_pattern)){ |
| 65 |
#NOTE: We do this so we only get the opening tag of the element. |
| 66 |
next unless $reader->nodeType == XML_READER_TYPE_ELEMENT; |
| 67 |
if ($reader->localName eq "record"){ |
| 68 |
my $record_node = $reader->copyCurrentNode(1); |
| 69 |
if ($record_node){ |
| 70 |
my $document = XML::LibXML::Document->new('1.0', 'UTF-8'); |
| 71 |
$document->setDocumentElement($record_node); |
| 72 |
my $root = $document->documentElement; |
| 73 |
|
| 74 |
my $identifier = _get_node_text({ |
| 75 |
root => $root, |
| 76 |
xpath_expression => $xpath_identifier, |
| 77 |
}); |
| 78 |
my $datestamp = _get_node_text({ |
| 79 |
root => $root, |
| 80 |
xpath_expression => $xpath_datestamp, |
| 81 |
}); |
| 82 |
#Remove UTC markers from timestamp |
| 83 |
$datestamp =~ s/T/ /; |
| 84 |
$datestamp =~ s/Z//; |
| 85 |
|
| 86 |
my $metadata; |
| 87 |
my $metadata_node = _get_node({ |
| 88 |
root => $root, |
| 89 |
xpath_expression => $xpath_metadata, |
| 90 |
}); |
| 91 |
if ($metadata_node){ |
| 92 |
$metadata = $metadata_node->toString(2); |
| 93 |
} |
| 94 |
|
| 95 |
my $deleted = _get_node_text({ |
| 96 |
root => $root, |
| 97 |
xpath_expression => $xpath_status, |
| 98 |
}); |
| 99 |
|
| 100 |
if ($self->{type} && $self->{type} eq 'biblio'){ |
| 101 |
if ($identifier && $datestamp){ |
| 102 |
my $record = Koha::Import::Oaipmh::Biblios->find({ |
| 103 |
repository => $repository, |
| 104 |
identifier => $identifier, |
| 105 |
}); |
| 106 |
if ($record){ |
| 107 |
if ($metadata && ! $deleted){ |
| 108 |
eval { |
| 109 |
my $incoming_dt = $strp->parse_datetime($datestamp); |
| 110 |
my $existing_dt = $strp->parse_datetime( $record->datestamp ); |
| 111 |
warn $incoming_dt; |
| 112 |
warn $existing_dt; |
| 113 |
if ( $incoming_dt > $existing_dt ){ |
| 114 |
MARC::Record::default_record_format("UNIMARC") |
| 115 |
if ( C4::Context->preference("marcflavour") eq "UNIMARC" ); |
| 116 |
my $marc_record = MARC::Record::new_from_xml( $metadata, 'UTF-8' ); |
| 117 |
C4::Biblio::ModBiblio($marc_record, $record->biblionumber, $frameworkcode); |
| 118 |
$record->update({ |
| 119 |
datestamp => $datestamp, |
| 120 |
metadata => $metadata, |
| 121 |
}); |
| 122 |
$updated++; |
| 123 |
} else { |
| 124 |
$skipped++; |
| 125 |
} |
| 126 |
}; |
| 127 |
} elsif ($deleted){ |
| 128 |
eval { |
| 129 |
C4::Biblio::DelBiblio($record->biblionumber); |
| 130 |
#$record->update({ |
| 131 |
# datestamp => $datestamp, |
| 132 |
# deleted => 1, |
| 133 |
# metadata => '' |
| 134 |
#}); |
| 135 |
$deleted++; |
| 136 |
}; |
| 137 |
} |
| 138 |
} else { |
| 139 |
#NOTE: Having metadata and being deleted should be mutually exclusive, |
| 140 |
#but it doesn't hurt to be thorough |
| 141 |
if ($metadata && ! $deleted){ |
| 142 |
eval { |
| 143 |
MARC::Record::default_record_format("UNIMARC") |
| 144 |
if ( C4::Context->preference("marcflavour") eq "UNIMARC" ); |
| 145 |
my $marc_record = MARC::Record::new_from_xml( $metadata, 'UTF-8' ); |
| 146 |
my ($biblionumber) = C4::Biblio::AddBiblio($marc_record,$frameworkcode); |
| 147 |
Koha::Import::Oaipmh::Biblio->new({ |
| 148 |
repository => $repository, |
| 149 |
identifier => $identifier, |
| 150 |
datestamp => $datestamp, |
| 151 |
metadata => $metadata, |
| 152 |
frameworkcode => $frameworkcode, |
| 153 |
biblionumber => $biblionumber, |
| 154 |
})->store; |
| 155 |
$added++; |
| 156 |
}; |
| 157 |
} |
| 158 |
} |
| 159 |
} |
| 160 |
} |
| 161 |
} |
| 162 |
} |
| 163 |
} |
| 164 |
} |
| 165 |
my $results = { |
| 166 |
added => $added, |
| 167 |
updated => $updated, |
| 168 |
deleted => $deleted, |
| 169 |
skipped => $skipped, |
| 170 |
}; |
| 171 |
return $results; |
| 172 |
} |
| 173 |
|
| 174 |
sub _get_node { |
| 175 |
my ($args) = @_; |
| 176 |
my $node; |
| 177 |
my $root = $args->{root}; |
| 178 |
my $xpath_expression = $args->{xpath_expression}; |
| 179 |
if ($root && $xpath_expression){ |
| 180 |
my $node_list = $xpc->findnodes($xpath_expression,$root); |
| 181 |
if ($node_list){ |
| 182 |
my $first_node = $node_list->shift; |
| 183 |
if ($first_node){ |
| 184 |
$node = $first_node; |
| 185 |
} |
| 186 |
} |
| 187 |
} |
| 188 |
return $node; |
| 189 |
} |
| 190 |
|
| 191 |
sub _get_node_text { |
| 192 |
my ($args) = @_; |
| 193 |
my $text; |
| 194 |
my $root = $args->{root}; |
| 195 |
my $xpath_expression = $args->{xpath_expression}; |
| 196 |
if ($root && $xpath_expression){ |
| 197 |
my $node = _get_node({ |
| 198 |
root => $root, |
| 199 |
xpath_expression => $xpath_expression, |
| 200 |
}); |
| 201 |
if ($node){ |
| 202 |
$text = $node->textContent; |
| 203 |
} |
| 204 |
} |
| 205 |
return $text; |
| 206 |
} |
| 207 |
|
| 208 |
1; |