From 2f4fdb02acda214e607db86fb7abc2d16f397d97 Mon Sep 17 00:00:00 2001 From: Hector Castro Date: Mon, 20 Apr 2015 15:09:11 -0600 Subject: [PATCH] Bug 13642 [ENH] Adding new features for Dublin Core metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When Koha export a bibliographic record to DC, makes it in XML format. This XML not follows the DC-XML recommendations as should be: elements in uppercase eg. dc:Date, dc:Creator, section 4.2, recommendation 4 explain that "The property names for the 15 DC elements should be all lower-case." eg. dc:date, dc:creator" and section "4.3 Example - a simple DC record", xsi:schemaLocation="http://example.org/myapp/ http://example.org/myapp/schema.xsd" schema does not exist. The recommendation can be consulted at . This new feature, adds an ability to export Dublin Core metadata to XML and RDF (including rdfxml, rdfjson, ntriples, turtle, etc.) Test plan --------------- 1) Download Dublin Core file. Open up the file, and make sure that the document not follows the DC-XML recommendations as should be. 2) Apply patches. 3) Go to whichever bib record in OPAC or staff and click on Save > Dublin Core. A modal will display, prove all options. 4) Change the system preference 'Opac ExportOptions' by enabling and disabling Dublin Core and try to download a record in the OPAC. 5) Try several bibliographic records in any format (book, magazine, DVD, etc.) to confirm that properly exported. 6) RDF/XML can be validated per RDF Validator W3C. 7) DC-XML cannot valid if not exist a valid XSD schema. 8) OAI-DC can be validated using and the proper schema typed in text field for URL. 9) Unit test, prove t/db_dependent/Record.t Sponsored-by: Universidad de El Salvador NOTE: XML::Entities, HTML::Entities and, HTML::Entities::Numbered were eliminated from patch, use instead C4::Record::_entity_encode, RDF::Helper has been eliminated too. DublinCore.pm Changed to Crosswalk/ according with comment 83 by RM Signed-off-by: Marc Véron --- C4/Installer/PerlDependencies.pm | 5 + C4/Record.pm | 127 ++- Koha/Crosswalk/DublinCore.pm | 1011 ++++++++++++++++++++ catalogue/export.pl | 36 +- debian/control | 6 +- .../intranet-tmpl/prog/en/css/staff-global.css | 10 + .../intranet-tmpl/prog/en/includes/cat-toolbar.inc | 2 +- .../prog/en/modules/catalogue/detail.tt | 159 ++- koha-tmpl/opac-tmpl/bootstrap/css/opac.css | 2 +- .../bootstrap/en/includes/opac-detail-sidebar.inc | 5 +- .../opac-tmpl/bootstrap/en/modules/opac-detail.tt | 162 ++++ koha-tmpl/opac-tmpl/bootstrap/js/script.js | 2 +- opac/opac-export.pl | 36 +- t/db_dependent/Record.t | 39 +- 14 files changed, 1544 insertions(+), 58 deletions(-) create mode 100644 Koha/Crosswalk/DublinCore.pm diff --git a/C4/Installer/PerlDependencies.pm b/C4/Installer/PerlDependencies.pm index c2bc7e6..f9d5dd6 100644 --- a/C4/Installer/PerlDependencies.pm +++ b/C4/Installer/PerlDependencies.pm @@ -762,6 +762,11 @@ our $PERL_DEPS = { 'required' => '0', 'min_ver' => '0.614', }, + 'RDF::Trine' => { + 'usage' => 'Core', + 'required' => '1', + 'min_ver' => '1.000', + }, }; 1; diff --git a/C4/Record.pm b/C4/Record.pm index f46651b..af9609e 100644 --- a/C4/Record.pm +++ b/C4/Record.pm @@ -25,7 +25,7 @@ use strict; # please specify in which methods a given module is used use MARC::Record; # marc2marcxml, marcxml2marc, changeEncoding use MARC::File::XML; # marc2marcxml, marcxml2marc, changeEncoding -use MARC::Crosswalk::DublinCore; # marc2dcxml +use Koha::Crosswalk::DublinCore; # marc2dcxml use Biblio::EndnoteStyle; use Unicode::Normalize; # _entity_encode use C4::Biblio; #marc2bibtex @@ -220,20 +220,34 @@ sub marcxml2marc { =head2 marc2dcxml - Convert from ISO-2709 to Dublin Core - my ($error,$dcxml) = marc2dcxml($marc,$qualified); + ($error,$marc) = marc2dcxml($marc, $type, $qualified, + $entities, $root, $xsi_schemaLocation, $rdf_subject, $rdf_format); Returns a DublinCore::Record object, will eventually return a Dublin Core scalar -FIXME: should return actual XML, not just an object +should return actual XML, not just an object (Koha::Crosswalk::DublinCore return an XML) C<$marc> - an ISO-2709 scalar or MARC::Record object -C<$qualified> - specify whether qualified Dublin Core should be used in the input or output [0] +C<$type> - the recommendation ( simple-dc-rdf, dc-rdf, dc-xml, oai-dc, srw_dc ) + +C<$qualified> - specify whether simple or qualified Dublin Core should be used in output [0 or 1] + +C<$entities> placing entities to 0 will codify only the five basic entities e.g., <, >, ", &, and &apos. Conversely if it is encoded to 1, all entities will coded into decimal + +C<$root> root element for XML files + +C<$xsi_schemaLocation> the xsischemaLocation for XML files. + +C<$rdf_subject> Represent the main subject of RDF triples (for Koha the URI of the resource) + +C<$rdf_format> Imply multiple formats for RDF (ntriples, nquads, rdfxml, rdfjson, ntriples-canonical, turtle) =cut sub marc2dcxml { - my ($marc,$qualified) = @_; + my ( $marc, $type, $qualified, $entities, $root, $xsi_schemaLocation, $rdf_subject, + $rdf_format ) = @_; my $error; # test if it's already a MARC::Record object, if not, make it one my $marc_record_obj; @@ -247,24 +261,93 @@ sub marc2dcxml { $error .="\nCreation of MARC::Record object failed: ".$MARC::File::ERROR; } } - my $crosswalk = MARC::Crosswalk::DublinCore->new; - if ($qualified) { - $crosswalk = MARC::Crosswalk::DublinCore->new( qualified => 1 ); - } - my $dcxml = $crosswalk->as_dublincore($marc_record_obj); - my $dcxmlfinal = "\n"; - $dcxmlfinal .= ""; - - foreach my $element ( $dcxml->elements() ) { - $dcxmlfinal.="<"."dc:".$element->name().">".$element->content()."name().">\n"; + my $dcxml; + + SWITCH: + for ($type) { + if (/^simple-dc-rdf/) { + my $objectDC = Koha::Crosswalk::DublinCore->new({ + type => 'rdf', + qualified => 0, + get_marc_record => $marc, + }); + + $objectDC->get_rdf_data({ + rdf_subject => $rdf_subject, + rdf_format => $rdf_format, + entities => 1, + }); + $dcxml = $objectDC->write_rdf(); + last SWITCH; + } + if (/^dc-rdf/) { + my $objectDC = Koha::Crosswalk::DublinCore->new({ + type => 'rdf', + qualified => 1, + get_marc_record => $marc, + }); + + $objectDC->get_rdf_data({ + rdf_subject => $rdf_subject, + rdf_format => $rdf_format, + entities => 1, + }); + $dcxml = $objectDC->write_rdf(); + last SWITCH; + } + if (/^dc-xml/) { + my $objectDC = Koha::Crosswalk::DublinCore->new({ + type => 'xml', + qualified => $qualified, + get_marc_record => $marc, + }); + + $objectDC->get_xml_data({ + root => $root, + xsi_schemaLocation => $xsi_schemaLocation, + entities => 1, + }); + $dcxml = $objectDC->write_xml(); + last SWITCH; + } + if (/^oai-dc/) { + my $objectDC = Koha::Crosswalk::DublinCore->new({ + type => 'xml', + qualified => 0, + get_marc_record => $marc, + }); + + $objectDC->get_xml_data({ + root => 'oai_dc:dc', + xsi_schemaLocation => 'http://www.openarchives.org/OAI/2.0/oai_dc/' . + ' http://www.openarchives.org/OAI/2.0/oai_dc.xsd', + entities => 0, + opt_namespace => 'oai_dc', + opt_namespace_url => 'http://www.openarchives.org/OAI/2.0/oai_dc/', + }); + $dcxml = $objectDC->write_xml(); + last SWITCH; + } + if (/^srw_dc/) { + my $objectDC = Koha::Crosswalk::DublinCore->new({ + type => 'xml', + qualified => 0, + get_marc_record => $marc, + }); + + $objectDC->get_xml_data({ + root => 'srw_dc:dc', + xsi_schemaLocation => 'info:srw/schema/1/dc-schema' . + ' http://www.loc.gov/standards/sru/resources/dc-schema.xsd', + entities => 0, + opt_namespace => 'srw_dc', + opt_namespace_url => 'info:srw/schema/1/dc-schema', + }); + $dcxml = $objectDC->write_xml(); + last SWITCH; + } } - $dcxmlfinal .= "\n"; - return ($error,$dcxmlfinal); + return ( $error, $dcxml ); } =head2 marc2modsxml - Convert from ISO-2709 to MODS diff --git a/Koha/Crosswalk/DublinCore.pm b/Koha/Crosswalk/DublinCore.pm new file mode 100644 index 0000000..f9cd584 --- /dev/null +++ b/Koha/Crosswalk/DublinCore.pm @@ -0,0 +1,1011 @@ +#!/usr/bin/perl +package Koha::Crosswalk::DublinCore; + +# +# This file is part of Koha. +# +# Copyright (C) 2014 Universidad de El Salvador, +# Facultad Multidisciplinaria Oriental +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . +# + +=head1 NAME + +Koha::Crosswalk::DublinCore - transform the values of MARC::Crosswalk::DublinCore +into metadata in XML or RDF format. + +=head1 SYNOPSIS + +=head2 XML-DC + + use Koha::Crosswalk::DublinCore; + use MARC::File::USMARC; + my $batch = MARC::File::USMARC->in('in.mrc'); + + #the $record will be a MARC::Record object. + my $record = $batch->next(); + + #Simple DC-XML + my $objectDC = Koha::Crosswalk::DublinCore->new({ + type => 'xml', + qualified => 0, + get_marc_record => $record}); + + #Qualified DC-XML + my $objectDC = Koha::Crosswalk::DublinCore->new({ + type => 'xml', + qualified => 1, + get_marc_record => $record}); + + #After call constructor you need to pass the root element and + #the schema in .xsd format. If you prefer or you don't know nothing + #about it pass the method in blank, i.e., without arguments. + #Also you can determine if you want to encode with xml entities or + #just with the five basics entities. + #Set entities to 0 for the five basics or 1 to encode with the + #complete xml entities. + $objectDC->get_xml_data({ + root => 'metadata', + xsi_schemaLocation => 'http://example.org/myapp/' . + ' http://example.org/myapp/schema.xsd', + }); + #This print your xml file in STDOUT + my $XML-DC = $objectDC->write_xml(); + print $XML-DC; + + #Close the file according to MARC::File::USMARC + $batch->close(); + undef $batch; + + #Or if you want to generate a file + open (OUTPUT, '> dc2xml.xml') or die $!; + print OUTPUT $XML-DC; + close(OUTPUT); + +=head2 DC-RDF + + use Koha::Crosswalk::DublinCore; + use MARC::File::USMARC; + my $marc = MARC::File::USMARC->in('in.mrc'); + my $record = $marc->next(); + + #Simple DC-RDF is not used but not deprecated, DublinCore + #can generate it as follow: + my $objectDC = Koha::Crosswalk::DublinCore->new({ + type => 'rdf', + qualified => 0, + get_marc_record => $record}); + + #DC-RDF + my $objectDC = Koha::Crosswalk::DublinCore->new({ + type => 'rdf', + qualified => 1, + get_marc_record => $record}); + + #The get_rdf_data method for RDF does not need to use the root element + #and the schema, but you need to declare the resource being described, + #i.e., the record. + #Note: For Koha is the IRI identifier of the record + $objectDC->get_rdf_data({ + rdf_subject => 'http://opac.mydomain.edu/cgi-bin/koha/opac-detail.pl?biblionumber=17589', + }); + + my $XML-RDF = $objectDC->write_rdf(); + print $XML-RDF; + #Close the file according to MARC::File::USMARC + $batch->close(); + undef $marc; + +=head1 DESCRIPTION + +The Koha::Crosswalk::DublinCore defines the interface to transform +and manipulate MARC records into DC-XML or DC-RDF recommendation. +This package transform MARC records to XML or RDF/XML Dublin Core +and follows the recommendations listed below: + +L + +L +This recommendation is superseded by DC-RDFrecommendation but not obsoleted at all. + +L + +=cut + +use Modern::Perl; +use Carp; +use utf8; +use version; + +BEGIN { + require Exporter; + + # set the version for version checking + our $VERSION = version->declare("v0.01"); + + # Inherit from Exporter to export functions and variables + our @ISA = qw(Exporter); + + # Functions and variables which are exported by default + our @EXPORT = qw(new get_xml_data get_rdf_data write_xml write_rdf); + + # Functions and variables which can be optionally exported + our @EXPORT_OK = + qw(type qualified get_marc_record root xsi_schemaLocation rdf_subject rdf_format opt_namespace opt_namespace_url); + +} + +use MARC::File::USMARC; +use MARC::Crosswalk::DublinCore; +use XML::LibXML; +use C4::Record; +use RDF::Trine; +use base qw(Class::Accessor); +__PACKAGE__->mk_accessors( + qw(type qualified get_marc_record root xsi_schemaLocation rdf_subject rdf_format entities opt_namespace opt_namespace_url) +); + +# Defining some constants +my $xsi_schema = qq(http://www.w3.org/2001/XMLSchema-instance); +my $unqualified = qq(http://purl.org/dc/elements/1.1/); +my $qualified = qq(http://purl.org/dc/terms/); +my $dcam = qq(http://purl.org/dc/dcam/); +my $rdfNS = qq(http://www.w3.org/1999/02/22-rdf-syntax-ns#); +my @elementupcase = + qw/tableOfContents dateAccepted dateCopyrighted dateSubmitted isVersionOf hasVersion isReplacedBy isRequiredBy isPartOf hasPart isReferencedBy isFormatOf hasFormat conformsTo accrualMethod accrualPeriodicity accrualPolicy instructionalMethod rightsHolder educationLevel accessRights bibliographicCitation/; +my $map; + +=head2 METHODS + +=over 12 + +=item C + +Return a new Koha::Crosswalk::DublinCore object. +This constructor sets the output type (RDF or XML), the level of the standard (Simple DC or Qualified DC) and the MARC record to be transformed to DC. + + my $objectDC = Koha::Crosswalk::DublinCore->new({ + type => 'rdf', + qualified => 0, + get_marc_record => $record}); + +=cut + +sub new { + my ( $class, $args ) = @_; + $args = {} unless defined $args; + return bless( $args, $class ); +} + +=item C + +This method is just for DC-XML: Fetch the xml root element and the schema in .xsd format. + + $objectDC->get_xml_data({ + root => 'metadata', + xsi_schemaLocation => 'http://example.org/myapp/ http://example.org/myapp/schema.xsd', + entities => 1, + opt_namespace => 'oai_dc', + opt_namespace_url => 'http://www.openarchives.org/OAI/2.0/oai_dc/', + }); + +The entities argument is for extended xml entities, i.e., placing entities to zero will codify only the five basic entities e.g., <, >, ", &, and &apos. Conversely if it is encoded to one, all entities will coded into decimal. + +Additionally, has been included the opt_namespace and opt_namespace_url arguments to include another XML name space, like OAI-DC or SRW-DC. + +NOTE: This method is only for use of the DC-XML recommendation, i.e., type => 'xml'. +See C method or the section SYNOPSIS. + +=cut + +sub get_xml_data { + + my $self = shift; + my ($args) = @_; + + if (@_) { + $self->root( $args->{root} ); + $self->xsi_schemaLocation( $args->{xsi_schemaLocation} ); + $self->entities( $args->{entities} ); + $self->opt_namespace( $args->{opt_namespace} ); + $self->opt_namespace_url( $args->{opt_namespace_url} ); + } + + #if data not filled + $self->{root} = "metadata" unless ( defined $self->{root} ); + $self->{entities} = 0 unless ( defined $self->{entities} ); + my %dataxml = ( + xmlroot => $self->{root}, + entities => $self->{entities}, + opt_namespace => $self->{opt_namespace}, + opt_namespace_url => $self->{opt_namespace_url}, + ); + + if ( exists $self->{xsi_schemaLocation} ) { + $dataxml{'xsi_schemaLocation'} = $self->{xsi_schemaLocation}; + } + if ( exists $self->{xsi_schemaLocation} ) { + $dataxml{'opt_namespace'} = $self->{opt_namespace}; + } + if ( exists $self->{xsi_schemaLocation} ) { + $dataxml{'opt_namespace_url'} = $self->{opt_namespace_url}; + } + return \%dataxml; +} + +=item C + +This method is only for DC-RDF: Fetch the IRI identifier of the resource being described. As well, you can pass the format you want to generate with the rdf, by default the DC-RDF specify RDF/XML but RDF can represent in other formats listed below: + +. ntriples + +. nquads + +. rdfxml + +. rdfjson + +. ntriples-canonical + +. turtle + + $objectDC->get_rdf_data({ + rdf_subject => 'http://opac.mydomain.edu/cgi-bin/koha/opac-detail.pl?biblionumber=17589', + rdf_format => 'rdfxml', + entities => 0|1, + }); + +NOTE: The entities is needed only for rdfxml value and is used like C method. + +=cut + +sub get_rdf_data { + + my $self = shift; + my ($args) = @_; + + if (@_) { + $self->rdf_subject( $args->{rdf_subject} ); + $self->rdf_format( $args->{rdf_format} ); + $self->entities( $args->{entities} ); + } + + #if data not filled + $self->{rdf_subject} = "http://koha-community.org/" + unless ( defined $self->{rdf_subject} ); + $self->{rdf_format} = "rdfxml" unless ( defined $self->{rdf_format} ); + $self->{entities} = 0 unless ( defined $self->{entities} ); + my %rdfdescription = ( + rdf_subject => $self->{rdf_subject}, + rdf_format => $self->{rdf_format}, + entities => $self->{entities}, + ); + + return \%rdfdescription; +} + +=item C + +Get the MARC record and return a Dublin Core elements, passed as argument in dc_order_execution method. + +USAGE: + my dc = $self->conversion_to_dc(); + $self->dc_order_execution ($dc, $dcxml, $root); + +=cut + +sub conversion_to_dc { + + my $self = shift; + my $marc_record_obj = $self->get_marc_record(); + my $crosswalk; + my $dc; + + #if data not filled + $self->qualified(0) unless ( exists $self->{qualified} ); + if ( $self->qualified() != 0 and $self->qualified() != 1 ) + { + croak "Only the 0 or 1 values are accepted\n" . + "Set qualified => " . $self->qualified() . " is no good, stopped"; + } + if ( $self->qualified() == 1 ) { + $crosswalk = MARC::Crosswalk::DublinCore->new( qualified => 1 ); + } + elsif ( $self->qualified() == 0 ) { + $crosswalk = MARC::Crosswalk::DublinCore->new( qualified => 0 ); + } + if ( $marc_record_obj =~ /^MARC::Record/ ) { + $dc = $crosswalk->as_dublincore($marc_record_obj); + } + else { + croak "MARC::Record object failed or not a MARC::Record\n" . + "Stopped due get_marc_record => is not a MARC::Record"; + } + + return $dc; + +} +=item C + +Return the xml object as string. + +NOTE: This method is only for use of the DC-XML recommendation, i.e., type => 'xml'. +See C and C methods or the section SYNOPSIS. + +=cut + +sub write_xml { + my $self = shift; + my $dataxml = $self->get_xml_data(); + my $dc = $self->conversion_to_dc(); + + #Looking for bad data + if ( $self->type() ne 'xml' and $self->type() ne 'rdf' ) + { + croak "Only the xml or rdf values are accepted\n" . + "Set type => " . $self->type() . " is no good, stopped"; + } + elsif ( $self->type() eq 'rdf' ) + { + croak "The constructor specified rdf\n" . + "Cannot use this method with rdf. This is no good, stopped"; + } + + if ( $self->entities() != 0 and $self->entities() != 1 ) + { + croak "Only the 0 or 1 values are accepted\n" . + "Set entities => " . $self->entities() . " is no good, stopped"; + } + + #Creating XML object + my $doc = XML::LibXML::Document->new( '1.0', 'utf-8' ); + my $root = $doc->createElement( $dataxml->{'xmlroot'} ); + $root->setNamespace( $xsi_schema, 'xsi', 0 ); + + if ( defined $dataxml->{'opt_namespace'} ) { + $root->setNamespace( $dataxml->{'opt_namespace_url'}, + $dataxml->{'opt_namespace'}, 0 ); + } + + if ( $self->type() eq 'xml' ) { + if ( $self->qualified() == 0 ) { + if ( defined $dataxml->{'xsi_schemaLocation'} ) { + $root->setAttribute( + 'xsi:schemaLocation' => $dataxml->{'xsi_schemaLocation'} ); + $root->setNamespace( $unqualified, 'dc', 0 ); + $self->dc_order_execution( $dc, $doc, $root ); + } + else { + $root->setNamespace( $unqualified, 'dc', 0 ); + $self->dc_order_execution( $dc, $doc, $root ); + } + } + elsif ( $self->qualified() == 1 ) { + if ( defined $dataxml->{'xsi_schemaLocation'} ) { + $root->setAttribute( + 'xsi:schemaLocation' => $dataxml->{'xsi_schemaLocation'} ); + $root->setNamespace( $unqualified, 'dc', 0 ); + $root->setNamespace( $qualified, 'dcterms', 0 ); + $self->dc_order_execution( $dc, $doc, $root ); + } + else { + $root->setNamespace( $unqualified, 'dc', 0 ); + $root->setNamespace( $qualified, 'dcterms', 0 ); + $self->dc_order_execution( $dc, $doc, $root ); + } + } + } + + $doc->setDocumentElement($root); + + #Enabling or desabling entities and print the xml and, decoding string to utf8 + if ( $dataxml->{'entities'} == 1 ) { + my $serialized_data = $doc->toString(1); + utf8::decode($serialized_data); + my ($dcxml_entity_encoded) = C4::Record::_entity_encode($serialized_data); + $serialized_data = $dcxml_entity_encoded; + return $serialized_data; + } + else { + return $doc->toString(1); + } +} + +=item C + +Return the rdf object as string. + +NOTE: This method is only for use of the DC-RDF recommendation, i.e., type => 'rdf'. +See C and C methods or the section SYNOPSIS. + +=cut + +sub write_rdf { + my $self = shift; + my $rdfdescription = $self->get_rdf_data(); + my $dc = $self->conversion_to_dc(); + my $serializer; + my %namespaces; + + #Looking for bad data + if ( $self->type() ne 'xml' and $self->type() ne 'rdf' ) + { + croak "Only the xml or rdf values are accepted\n" . + "Set type => " . $self->type() . " is no good, stopped"; + } + elsif ( $self->type() eq 'xml' ) { + croak "The constructor specified xml\n" . + "Cannot use this method with xml. This is no good, stopped"; + } + + if ( $self->entities() != 0 and $self->entities() != 1 ) + { + croak "Only the 0 or 1 values are accepted\n" . + "Set entities => " . $self->entities() . " is no good, stopped"; + } + + # Creating the triple store in temporary data + my $store = RDF::Trine::Store::Memory->new(); + + # Creating the model + my $rdf = RDF::Trine::Model->new($store); + + # Creating the subject + my $subject = + RDF::Trine::Node::Resource->new( $rdfdescription->{'rdf_subject'} ); + + $namespaces{'rdf'} = $rdfNS; + + if ( $self->qualified() == 0 ) { + + # Declaring all namespaces in a hash + $namespaces{'dc'} = $unqualified; + } + else { + + # Declaring all namespaces in a hash + $namespaces{'dcterms'} = $qualified; + $namespaces{'dcam'} = $dcam; + } + + # Creating namespaces + $map = RDF::Trine::NamespaceMap->new( \%namespaces ); + + if ( $self->type() eq 'rdf' ) { + $self->dc_order_execution( $dc, $rdf, $subject ); + } + + #Enabling or desabling entities and print the xml and, decoding string to utf8 + if ( $rdfdescription->{'rdf_format'} eq 'rdfxml' ) { + if ( $rdfdescription->{'entities'} == 1 ) { + $serializer = + RDF::Trine::Serializer::RDFXML->new( + namespaces => {%namespaces} ); + my $serialized_data = $serializer->serialize_model_to_string($rdf); + utf8::decode($serialized_data); + my ($dcxml_entity_encoded) = C4::Record::_entity_encode($serialized_data); + return $serialized_data = $dcxml_entity_encoded; + } + else { + $serializer = + RDF::Trine::Serializer::RDFXML->new( + namespaces => {%namespaces} ); + return $serializer->serialize_model_to_string($rdf); + } + } + elsif ( $rdfdescription->{'rdf_format'} eq 'turtle' ) { + $serializer = + RDF::Trine::Serializer::Turtle->new( namespaces => {%namespaces} ); + return $serializer->serialize_model_to_string($rdf); + } + else { + $serializer = + RDF::Trine::Serializer->new( $rdfdescription->{'rdf_format'} ); + return $serializer->serialize_model_to_string($rdf); + } +} + +=item C + +This subroutine define if is necessary to convert lowercase some qualifiers or let them as crosswalk method basis + +USAGE: upper_or_lowercase (element->qualifier()) + +=cut + +sub upper_or_lowercase { + + + my $dcelement = $_[0]; + my $count = 0; + my $result; + while ( $count <= $#elementupcase ) { + if ( $elementupcase[$count] eq $dcelement ) { + $result = $dcelement; + last; + } + elsif ( $dcelement ne $elementupcase[$count] ) { + $result = lc($dcelement); + $count++; + next; + } + } + + return $result; + +} + +=item C + +xml_constructor ( doc, root, namespace, attr, element_name, element_content, element_qualifier, element_scheme ) + +USAGE: define all posible elements (name, content, qualifier, scheme) as neccesary +doc: the XML object +root: append root to doc +namespace: defined as "dc" or "dcterms" +attr: a boolean value to determinate xsi:type xml namespace for dcterms +Examples: +xml_constructor ( $dcxml, $root, "dc", 0, element->name(), element->content() ); +xml_constructor ( $dcxml, $root, "dcterms", 1, element->name(), element->content(), $element->qualifier() ); + +NOTE: This is a routine not a method. + +=cut + +sub xml_constructor { + + my ( $doc, $root, $namespace, $attr, $element_name, $element_content, + $element_qualifier, $element_scheme ) + = @_; + + # This scalar is used for deleting spaces in scheme elements + my $deletespaces; + + # This scalar is used for making upper or lowercase the qualifier elements + my $lowercase; + my $tagxml; + + if ( $namespace eq "dc" ) { + $lowercase = lc($element_name); + } + elsif ( $namespace eq "dcterms" ) { + $lowercase = upper_or_lowercase($element_qualifier); + + #replacing spacial to spatial, bug from dublin or crosswalk. + $lowercase =~ s/spacial/spatial/; + } + + if ( ( $namespace eq "dc" or "dcterms" ) and $attr == 0 ) { + + # This will do something like: Title + $tagxml = XML::LibXML::Element->new($lowercase); + $tagxml->setNamespace( undef, $namespace, 1 ); + $tagxml->appendTextNode($element_content); + $root->appendChild($tagxml); + } + elsif ( ( $namespace eq "dcterms" ) and $attr == 1 ) { + +# This will do something like: us + $deletespaces = $element_scheme; + $deletespaces =~ s/\s//; + $tagxml = XML::LibXML::Element->new($lowercase); + $tagxml->setNamespace( undef, $namespace, 1 ); + my $attribute = $namespace . ':' . $deletespaces; + $tagxml->setAttribute( 'xsi:type', $attribute ); + $tagxml->appendTextNode($element_content); + $root->appendChild($tagxml); + } + elsif ( ( $namespace eq "dc" ) and $attr == 1 ) { + +# This will do something like: Water supply. + if ( $element_scheme ne "DCMI Type Vocabulary" ) + { #Dublin core returns DCMI Type Vocabulary + $deletespaces = $element_scheme; + $deletespaces =~ s/\s//; + $tagxml = XML::LibXML::Element->new($lowercase); + $tagxml->setNamespace( undef, $namespace, 1 ); + my $attribute = 'dcterms:' . $deletespaces; + $tagxml->setAttribute( 'xsi:type', $attribute ); + $tagxml->appendTextNode($element_content); + $root->appendChild($tagxml); + } + elsif ( $element_scheme eq "DCMI Type Vocabulary" ) + { #Changing for DCMIType + # This will do something like: Text + $tagxml = XML::LibXML::Element->new($lowercase); + $tagxml->setNamespace( undef, $namespace, 1 ); + $tagxml->setAttribute( 'xsi:type', 'dcterms:DCMIType' ); + $tagxml->appendTextNode($element_content); + $root->appendChild($tagxml); + } + } #if...elsif + + return $tagxml; + +} #subroutine + +=item C + +xml_constructor ( rdf, subject, namespace, attr, element_name, element_content, element_qualifier, element_scheme ) + +USAGE: define all posible elements (name, content, qualifier, scheme) as neccesary +rdf: the RDF object +subject: the subject for the triples +namespace: defined as "dc" or "dcterms". This is for Simple DC or DC-RDF recommendations. +attr: a boolean value to determine the nodeID attribute for dcterms + +NOTE: This is a routine not a method. + +=cut + +sub rdf_constructor { + + my ( $rdf, $subject, $namespace, $attr, $element_name, $element_content, + $element_qualifier, $element_scheme ) + = @_; + + #This scalar is used for deleting spaces in scheme elements + my $deletespaces; + + #This scalar is used for making upper or lowercase the qualifier elements + my $lowercase; + + if ( $namespace eq "dc" ) { + $lowercase = lc($element_name); + } + elsif ( $namespace eq "dcterms" ) { + $lowercase = upper_or_lowercase($element_qualifier); + + #replacing spacial to spatial, bug from dublin or crosswalk. + $lowercase =~ s/spacial/spatial/; + } + + if ( ( $namespace eq "dc" or "dcterms" ) and $attr == 0 ) { + + # This will do something like: Title + my $predicate = undef; + if ( $namespace eq "dc" ) { + $predicate = $map->dc($lowercase); + } + else { + $predicate = $map->dcterms($lowercase); + } + my $object = RDF::Trine::Node::Literal->new($element_content); + my $statement = + RDF::Trine::Statement->new( $subject, $predicate, $object ); + $rdf->add_statement($statement); + } + elsif ( ( $namespace eq "dc" ) and $attr == 2 ) { + + # This will do something like: Title + my $predicate = $map->dcterms($lowercase); + my $object = RDF::Trine::Node::Literal->new($element_content); + my $statement = + RDF::Trine::Statement->new( $subject, $predicate, $object ); + $rdf->add_statement($statement); + } + elsif ( ( $namespace eq "dc" ) and $attr == 1 ) { + $deletespaces = $element_scheme; + $deletespaces =~ s/\s//; + SWITCH: + for ($element_scheme) { + if (/^ISO 3166/) { + + # Dublin core returns DCMI Type Vocabulary + # This will do something like: + # + # + # us + # + # + my $objectbnode = RDF::Trine::Node::Blank->new('country'); + my $predicate = $map->dcterms($lowercase); + my $object = RDF::Trine::Node::Literal->new($element_content); + my $dcam_member_of = $map->dcam('memberOf'); + my $resource_schema = $map->dcterms($deletespaces); + my $rdf_value = $map->rdf('value'); + my $statement1 = + RDF::Trine::Statement->new( $subject, $predicate, + $objectbnode ); + my $statement2 = + RDF::Trine::Statement->new( $objectbnode, $dcam_member_of, + $resource_schema ); + my $statement3 = + RDF::Trine::Statement->new( $objectbnode, $rdf_value, + $object ); + $rdf->add_statement($statement1); + $rdf->add_statement($statement2); + $rdf->add_statement($statement3); + last SWITCH; + } + if (/^DCMI Type Vocabulary/) { #Changing for DCMIType + my $objectbnode = RDF::Trine::Node::Blank->new('DCMIType'); + my $predicate = $map->dcterms($lowercase); + my $object = RDF::Trine::Node::Literal->new($element_content); + my $dcam_member_of = $map->dcam('memberOf'); + my $resource_schema = $map->dcterms('DCMIType'); + my $rdf_value = $map->rdf('value'); + my $statement1 = + RDF::Trine::Statement->new( $subject, $predicate, + $objectbnode ); + my $statement2 = + RDF::Trine::Statement->new( $objectbnode, $dcam_member_of, + $resource_schema ); + my $statement3 = + RDF::Trine::Statement->new( $objectbnode, $rdf_value, + $object ); + $rdf->add_statement($statement1); + $rdf->add_statement($statement2); + $rdf->add_statement($statement3); + last SWITCH; + } + if (/^ISO 639-2/) { #Changing bnodeId ISO639-2 for language + my $objectbnode = RDF::Trine::Node::Blank->new('language'); + my $predicate = $map->dcterms($lowercase); + my $object = RDF::Trine::Node::Literal->new($element_content); + my $dcam_member_of = $map->dcam('memberOf'); + my $resource_schema = $map->dcterms($deletespaces); + my $rdf_value = $map->rdf('value'); + my $statement1 = + RDF::Trine::Statement->new( $subject, $predicate, + $objectbnode ); + my $statement2 = + RDF::Trine::Statement->new( $objectbnode, $dcam_member_of, + $resource_schema ); + my $statement3 = + RDF::Trine::Statement->new( $objectbnode, $rdf_value, + $object ); + $rdf->add_statement($statement1); + $rdf->add_statement($statement2); + $rdf->add_statement($statement3); + last SWITCH; + } + if (/^RFC 1766/) { #Changing bnodeId RFC1766 for languagetag + my $objectbnode = RDF::Trine::Node::Blank->new('languagetag'); + my $predicate = $map->dcterms($lowercase); + my $object = RDF::Trine::Node::Literal->new($element_content); + my $dcam_member_of = $map->dcam('memberOf'); + my $resource_schema = $map->dcterms($deletespaces); + my $rdf_value = $map->rdf('value'); + my $statement1 = + RDF::Trine::Statement->new( $subject, $predicate, + $objectbnode ); + my $statement2 = + RDF::Trine::Statement->new( $objectbnode, $dcam_member_of, + $resource_schema ); + my $statement3 = + RDF::Trine::Statement->new( $objectbnode, $rdf_value, + $object ); + $rdf->add_statement($statement1); + $rdf->add_statement($statement2); + $rdf->add_statement($statement3); + last SWITCH; + } + if ( /^LCSH/ || /^MeSH/ || /^LCC/ || /^DDC/ || + /^UDC/ || /^IMT/ || /^URI/ || /^TGN/ ) + { #Scheme sharing common elements + my $objectbnode = RDF::Trine::Node::Blank->new($deletespaces); + my $predicate = $map->dcterms($lowercase); + my $object = RDF::Trine::Node::Literal->new($element_content); + my $dcam_member_of = $map->dcam('memberOf'); + my $resource_schema = $map->dcterms($deletespaces); + my $rdf_value = $map->rdf('value'); + my $statement1 = + RDF::Trine::Statement->new( $subject, $predicate, + $objectbnode ); + my $statement2 = + RDF::Trine::Statement->new( $objectbnode, $dcam_member_of, + $resource_schema ); + my $statement3 = + RDF::Trine::Statement->new( $objectbnode, $rdf_value, + $object ); + $rdf->add_statement($statement1); + $rdf->add_statement($statement2); + $rdf->add_statement($statement3); + last SWITCH; + } + } + } #if-elsif + return $rdf; +} #subroutine + +=item C + +USAGE: Get the parameters for xml or rdf and execute in sequence. + + my dc = $self->conversion_to_dc(); + $self->dc_order_execution ($dc, $dcxml, $root); + +=back + +=cut + +sub dc_order_execution { + + my $self = shift; + + if ( $self->type() eq 'xml' ) { #Order for xml + my ( $dc, $dcxml, $root ) = @_; + foreach my $element ( $dc->elements() ) { + SWITCH: { + if ( $element->name() + and not defined $element->scheme() + and not defined $element->qualifier() ) + { + $dcxml = + xml_constructor( $dcxml, $root, "dc", 0, $element->name(), + $element->content() ); + last SWITCH; + } + + if ( $element->name() + and not defined $element->scheme() + and not defined $element->qualifier() ) + { + $dcxml = + xml_constructor( $dcxml, $root, "dcterms", 0, + $element->name(), $element->content() ); + last SWITCH; + } + + if ( $element->name() + and $element->scheme() + and $element->qualifier() ) + { + $dcxml = xml_constructor( + $dcxml, $root, + "dcterms", 1, + $element->name(), $element->content(), + $element->qualifier(), $element->scheme() + ); + last SWITCH; + } + + if ( $element->name() + and $element->scheme() + and ( $element->scheme ne "DCMI Type Vocabulary" ) ) + { + $dcxml = + xml_constructor( $dcxml, $root, "dc", 1, $element->name(), + $element->content(), $element->qualifier(), + $element->scheme() ); + last SWITCH; + } + + if ( $element->name() + and $element->scheme() + and ( $element->scheme() eq "DCMI Type Vocabulary" ) ) + { + $dcxml = + xml_constructor( $dcxml, $root, "dc", 1, $element->name(), + $element->content(), $element->qualifier(), + $element->scheme() ); + last SWITCH; + } + + if ( $element->name() and $element->qualifier() ) { + $dcxml = xml_constructor( + $dcxml, $root, + "dcterms", 0, + $element->name(), $element->content(), + $element->qualifier(), $element->scheme() + ); + last SWITCH; + } #if + } #SWITCH + } #foreach 1 + } + elsif ( $self->type() eq 'rdf' ) { #Order for rdf/xml + my ( $dc, $rdf, $subject ) = @_; + foreach my $element ( $dc->elements() ) { + SWITCH: { + if ( $element->name() + and not defined $element->scheme() + and not defined $element->qualifier() ) + { + rdf_constructor( $rdf, $subject, "dc", 0, $element->name(), + $element->content() ) + unless $self->qualified() == 1; + rdf_constructor( $rdf, $subject, "dc", 2, $element->name(), + $element->content() ) + unless $self->qualified() == 0; + last SWITCH; + } + + if ( $element->name() + and $element->scheme() + and ( $element->scheme ne "DCMI Type Vocabulary" ) ) + { + rdf_constructor( $rdf, $subject, "dc", 1, $element->name(), + $element->content(), $element->qualifier(), + $element->scheme() ); + last SWITCH; + } + + if ( $element->name() + and $element->scheme() + and ( $element->scheme() eq "DCMI Type Vocabulary" ) ) + { + rdf_constructor( $rdf, $subject, "dc", 1, $element->name(), + $element->content(), $element->qualifier(), + $element->scheme() ); + last SWITCH; + } + + if ( $element->name() and $element->qualifier() ) { + rdf_constructor( + $rdf, $subject, + "dcterms", 0, + $element->name(), $element->content(), + $element->qualifier(), $element->scheme() + ); + last SWITCH; + } #if + } #SWITCH + } #foreach 2 + } + return; +} + +END { + + #When opened with MARC::File::USMARC, close marc record if not closed + sub DESTROY { + my $self = shift; + if ( $self->{get_marc_record} =~ /^MARC:File::USMARC/ ) { + $self->{get_marc_record}->close(); + undef $self->{get_marc_record}; + } + } +} +1; +__END__ + +=head1 VERSION + +version 0.01 + +=head1 RELATES MODULES + +This package requires the following modules: + +L + +L + +L + +L + +L + +L + +L + +=head1 SEE ALSO + +Dublin Core Web Page L. + +=head1 LICENSE + +Copyright (c) 2014 Koha Community. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. + +=head1 AUTHOR + +Hector Eduardo Catro Avalos E hector.hecaxmmx at gmail dot com E +and the Koha Development Team. + +=cut diff --git a/catalogue/export.pl b/catalogue/export.pl index e66d3f2..c4e0617 100755 --- a/catalogue/export.pl +++ b/catalogue/export.pl @@ -26,6 +26,14 @@ my $format=$query->param("format"); my $error = ''; if ($op eq "export") { my $biblionumber = $query->param("bib"); + my $recommendation = $query->param("recommendation"); + my $formats = $query->param("formats"); + my $qualifier = $query->param("qualifier"); + my $root_element = $query->param("root_element"); + my $xsischemalocation = $query->param("xsischemalocation"); + my $resource_url = $query->url(-base => 1) . + '/cgi-bin/koha/catalogue/detail.pl?biblionumber=' . + $biblionumber; if ($biblionumber){ my $marc = GetMarcBiblio($biblionumber, 1); @@ -51,8 +59,32 @@ if ($op eq "export") { $format = "bibtex"; } elsif ($format =~ /dc/) { - ($error,$marc) = marc2dcxml($marc,1); - $format = "dublin-core.xml"; + SWITCH: + for ($recommendation) { + if (/^simple-dc-rdf/) { + ($error,$marc) = marc2dcxml($marc, $recommendation, 0, + 1, undef, undef, $resource_url , $formats); + $format = "dublin-core." . $formats; + last SWITCH; } + if (/^dc-rdf/) { + ($error,$marc) = marc2dcxml($marc, $recommendation, 1, + 1, undef, undef, $resource_url, $formats); + $format = "dublin-core." . $formats; + last SWITCH; } + if (/^dc-xml/) { + ($error,$marc) = marc2dcxml($marc, $recommendation, $qualifier, + 1, $root_element, $xsischemalocation, undef, undef); + $format = "dublin-core.xml"; + last SWITCH; } + if (/^oai-dc/) { + ($error,$marc) = marc2dcxml($marc, $recommendation); + $format = "dublin-core.xml"; + last SWITCH; } + if (/^srw_dc/) { + ($error,$marc) = marc2dcxml($marc, $recommendation); + $format = "dublin-core.xml"; + last SWITCH; } + } } elsif ($format =~ /marc8/) { $marc = changeEncoding($marc,"MARC","MARC21","MARC-8"); diff --git a/debian/control b/debian/control index f5f635f..2bd301a 100644 --- a/debian/control +++ b/debian/control @@ -86,11 +86,11 @@ Build-Depends: libalgorithm-checkdigits-perl, libopenoffice-oodoc-perl, libpdf-api2-perl, libpdf-api2-simple-perl, - libpdf-fromhtml-perl, libpdf-reuse-barcode-perl, libpdf-reuse-perl, libpdf-table-perl, libplack-middleware-reverseproxy-perl, + librdf-trine-perl, libscalar-list-utils-perl, libschedule-at-perl, libsms-send-perl, @@ -99,7 +99,6 @@ Build-Depends: libalgorithm-checkdigits-perl, libtemplate-perl, libtemplate-plugin-htmltotext-perl, libtemplate-plugin-json-escape-perl, - libtemplate-plugin-stash-perl, libtest-deep-perl, libtest-harness-perl | perl-modules, libtest-mockmodule-perl, @@ -290,11 +289,11 @@ Depends: libalgorithm-checkdigits-perl, libopenoffice-oodoc-perl, libpdf-api2-perl, libpdf-api2-simple-perl, - libpdf-fromhtml-perl, libpdf-reuse-barcode-perl, libpdf-reuse-perl, libpdf-table-perl, libplack-middleware-reverseproxy-perl, + librdf-trine-perl, libscalar-list-utils-perl, libschedule-at-perl, libsms-send-perl, @@ -303,7 +302,6 @@ Depends: libalgorithm-checkdigits-perl, libtemplate-perl, libtemplate-plugin-htmltotext-perl, libtemplate-plugin-json-escape-perl, - libtemplate-plugin-stash-perl, libtest-deep-perl, libtest-harness-perl | perl-modules, libtest-mockmodule-perl, diff --git a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css index 23066ea..9516c16 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css +++ b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css @@ -2798,3 +2798,13 @@ div#cn_browser_table_wrapper > table#cn_browser_table { margin: auto; width:90%; } + +/* CSS for Dublin Core Modal */ + +#dc-rdf, #dc-xml { + display: none; +} + +.label_dc { + cursor: pointer; +} diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc index 8f44496..3a17d3d 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc @@ -217,7 +217,7 @@ CAN_user_serials_create_subscription ) %]