From 7158c1da8f4b4108c8ba9f0e5fbdaef6512ad7db Mon Sep 17 00:00:00 2001 From: Hector Castro <hector.hecaxmmx@gmail.com> Date: Mon, 20 Apr 2015 15:09:11 -0600 Subject: [PATCH] Bug 13642 [ENH] Adding new features for Dublin Core metadata 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 <http://dublincore.org/documents/dc-xml-guidelines/>. 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 <http://www.freeformatter.com/xml-validator-xsd.html> and the proper schema <http://www.openarchives.org/OAI/2.0/oai_dc.xsd> typed in text field for URL. 9) Since unit test fail last time, test with prove t/db_dependent/Record.t Sponsored-by: Universidad de El Salvador Signed-off-by: Hector Castro <hector.hecaxmmx@gmail.com> XML::Entities, HTML::Entities and, HTML::Entities::Numbered were eliminated from patch, use instead C4::Record::_entity_encode, RDF::Helper has been eliminated too. --- C4/Installer/PerlDependencies.pm | 5 + C4/Record.pm | 127 ++- Koha/DublinCore.pm | 992 ++++++++++++++++++++ catalogue/export.pl | 36 +- debian/control | 6 +- .../intranet-tmpl/prog/en/includes/cat-toolbar.inc | 2 +- .../prog/en/modules/catalogue/detail.tt | 143 ++- .../opac-tmpl/bootstrap/en/includes/masthead.inc | 66 ++ .../bootstrap/en/includes/opac-detail-sidebar.inc | 5 +- koha-tmpl/opac-tmpl/bootstrap/js/script.js | 78 +- opac/opac-export.pl | 36 +- t/db_dependent/Record.t | 39 +- 12 files changed, 1481 insertions(+), 54 deletions(-) create mode 100644 Koha/DublinCore.pm diff --git a/C4/Installer/PerlDependencies.pm b/C4/Installer/PerlDependencies.pm index 7e382bd..b4ae230 100644 --- a/C4/Installer/PerlDependencies.pm +++ b/C4/Installer/PerlDependencies.pm @@ -737,6 +737,11 @@ our $PERL_DEPS = { 'required' => '0', 'min_ver' => '1.17', }, + 'RDF::Trine' => { + 'usage' => 'Core', + 'required' => '1', + 'min_ver' => '1.000', + }, }; 1; diff --git a/C4/Record.pm b/C4/Record.pm index 5d894fa..2c6aac2 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::DublinCore; # marc2dcxml use Biblio::EndnoteStyle; use Unicode::Normalize; # _entity_encode use C4::Biblio; #marc2bibtex @@ -219,20 +219,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 +FIXME: should return actual XML, not just an object (Koha::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; @@ -246,24 +260,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 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; - $dcxmlfinal .= "<metadata - xmlns=\"http://example.org/myapp/\" - xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" - xsi:schemaLocation=\"http://example.org/myapp/ http://example.org/myapp/schema.xsd\" - xmlns:dc=\"http://purl.org/dc/elements/1.1/\" - xmlns:dcterms=\"http://purl.org/dc/terms/\">"; - - foreach my $element ( $dcxml->elements() ) { - $dcxmlfinal.="<"."dc:".$element->name().">".$element->content()."</"."dc:".$element->name().">\n"; + my $dcxml; + + SWITCH: + for ($type) { + if (/^simple-dc-rdf/) { + my $objectDC = Koha::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::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::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::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::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</metadata>"; - return ($error,$dcxmlfinal); + return ( $error, $dcxml ); } =head2 marc2modsxml - Convert from ISO-2709 to MODS diff --git a/Koha/DublinCore.pm b/Koha/DublinCore.pm new file mode 100644 index 0000000..2313349 --- /dev/null +++ b/Koha/DublinCore.pm @@ -0,0 +1,992 @@ +#!/usr/bin/perl +package Koha::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 <http://www.gnu.org/licenses>. +# + +use Modern::Perl; +use strict; +use warnings::register; +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; + +#USAGE: Koha::DublinCore->new({ +# type => 'xml|rdf', +# qualified => 0|1, +# get_marc_record => $record +#}); + +sub new { + my ( $class, $args ) = @_; + $args = {} unless defined $args; + return bless( $args, $class ); +} + +sub get_xml_data { + +#USAGE: oop->get_xml_data({ +# root => 'root', +# xsi_schemaLocation => 'http://example.org/myapp/ http://example.org/myapp/schema.xsd', +# entities => 0|1, +# opt_namespace => 'oai_dc' +# opt_namespace_url => 'http://www.openarchives.org/OAI/2.0/oai_dc/' +#}); + 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; +} + +sub get_rdf_data { + +#USAGE: oop->get_rdf_data({ +# rdf_subject => 'http://opac.mydomain.edu/cgi-bin/koha/opac-detail.pl?biblionumber=17589', +# rdf_format => 'ntriples | nquads| rdfxml | rdfjson | ntriples-canonical | turtle', +# entities => 0|1, +#}) + + 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; +} + +sub conversion_to_dc { + +#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); + 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 ) + && warnings::enabled($self) ) + { + warnings::warn( $self, "Only the 0 or 1 values are accepted" ); + die "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 { + warnings::warn( $self, + "MARC::Record object failed or not a MARC::Record" ); + die "Stopped due get_marc_record => is not a MARC::Record"; + } + + return $dc; + +} + +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' ) + && warnings::enabled($self) ) + { + warnings::warn( $self, "Only the xml or rdf values are accepted" ); + die "Set type => " . $self->type() . " is no good, stopped"; + } + elsif ( $self->type() eq 'rdf' && warnings::enabled($self) ) { + warnings::warn( $self, "The constructor specified rdf" ); + die "Cannot use this method with rdf. This is no good, stopped"; + } + + if ( ( $self->entities() != 0 and $self->entities() != 1 ) + && warnings::enabled($self) ) + { + warnings::warn( $self, "Only the 0 or 1 values are accepted" ); + die "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); + } +} + +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' ) + && warnings::enabled($self) ) + { + warnings::warn( $self, "Only the xml or rdf values are accepted" ); + die "Set type => " . $self->type() . " is no good, stopped"; + } + elsif ( $self->type() eq 'xml' && warnings::enabled($self) ) { + warnings::warn( $self, "The constructor specified xml" ); + die "Cannot use this method with xml. This is no good, stopped"; + } + + if ( ( $self->entities() != 0 and $self->entities() != 1 ) + && warnings::enabled($self) ) + { + warnings::warn( $self, "Only the 0 or 1 values are accepted" ); + die "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); + } +} + +sub upper_or_lowercase { + +#This subroutine define if is necessary to convert lowercase some qualifiers or let them as crosswalk method basis +#USAGE: upper_or_lowercase (element->qualifier()) + 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; + +} + +sub xml_constructor { + +#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() ); + + 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: <dc:title>Title</dc: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: <dcterms:spatial xsi:type="dcterms:ISO3166">us</dcterms:spatial> + $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: <dc:subject xsi:type="dcterms:LCSH">Water supply.</dc:subject> + 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: <dc:type xsi:type="dcterms:DCMIType">Text</dc:type> + $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 + +sub rdf_constructor { + +#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 + + 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: <dc:title>Title</dc: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: <dcterms:title>Title</dcterms: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: + # <rdf:Description rdf:nodeID="country"> + # <dcam:memberOf rdf:resource="http://purl.org/dc/terms/ISO3166"/> + # <rdf:value>us</rdf:value> + # </rdf:Description> + # <dcterms:spatial rdf:nodeID="country"/> + 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 + +sub dc_order_execution { + + #USAGE: USAGE: Get the parameters for xml or rdf and execute in order. + 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__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Koha::DublinCore - transform the values of MARC::Crosswalk::DublinCore +into metadata in XML or RDF format. + +=head1 SYNOPSIS + +=head2 XML-DC + + use Koha::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(); + + #For Simple DC-XML + my $objectDC = Koha::DublinCore->new({ + type => 'xml', + qualified => 0, + get_marc_record => $record}); + + #For Qualified DC-XML + my $objectDC = Koha::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::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::DublinCore->new({ + type => 'rdf', + qualified => 0, + get_marc_record => $record}); + + #DC-RDF + my $objectDC = Koha::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::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<DC-XML-GUIDELINES [DCMI Recommendation]|http://dublincore.org/documents/dc-xml-guidelines/> + +L<Expressing Simple Dublin Core in RDF-XML|http://dublincore.org/documents/dcmes-xml/> +This recommendation is superseded by DC-RDFrecommendation but not obsoleted at all. + +L<DC-RDF [DCMI Recommendation]|http://dublincore.org/documents/dc-rdf/> + +=head2 Methods + +=over 12 + +=item C<new> + +Return a new Koha::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::DublinCore->new({ + type => 'rdf', + qualified => 0, + get_marc_record => $record}); + +=item C<get_xml_data> + +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, + }); + +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<new> method or the section SYNOPSIS. + +=item C<get_rdf_data> + +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<get_xml_data> method. + +=item C<write_xml> + +Return the xml object as string. + +NOTE: This method is only for use of the DC-XML recommendation, i.e., type => 'xml'. +See C<new> and C<get_xml_data> methods or the section SYNOPSIS. + +=item C<write_rdf> + +Return the rdf object as string. + +NOTE: This method is only for use of the DC-RDF recommendation, i.e., type => 'rdf'. +See C<new> and C<get_rdf_data> methods or the section SYNOPSIS. + +=back + +=head1 VERSION + +version 0.01 + +=head1 RELATES MODULES + +This package requires the following modules: + +L<DublinCore::Record> + +L<MARC::Record> + +L<MARC::File::USMARC> + +L<MARC::Crosswalk::DublinCore> + +L<XML::LibXML> + +L<RDF::Trine> + +L<C4::Record> + +=head2 SEE ALSO + +Dublin Core Web Page L<http://dublincore.org>. + +=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<lt> hector.hecaxmmx at gmail dot com E<gt> +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 429b4d4..f825377 100644 --- a/debian/control +++ b/debian/control @@ -43,6 +43,7 @@ Build-Depends: libalgorithm-checkdigits-perl, libdbi-perl, libdbix-class-schema-loader-perl, libdbix-connector-perl, + libdevel-cover-perl, libdigest-sha-perl | perl, libemail-date-perl, libemail-valid-perl, @@ -87,6 +88,7 @@ Build-Depends: libalgorithm-checkdigits-perl, libpdf-reuse-barcode-perl, libpdf-reuse-perl, libpdf-table-perl, + librdf-trine-perl, libscalar-list-utils-perl, libschedule-at-perl, libsms-send-perl, @@ -124,7 +126,7 @@ Build-Depends: libalgorithm-checkdigits-perl, libyaml-perl, libyaml-syck-perl, perl, - perl-modules, + perl-modules, debhelper (>= 7.0.50), gettext, xsltproc, docbook-xsl, libxml2-utils, bash-completion, perl-modules (>= 5.14.2) | libtest-simple-perl (>= 0.98) @@ -239,6 +241,7 @@ Depends: libalgorithm-checkdigits-perl, libdbi-perl, libdbix-class-schema-loader-perl, libdbix-connector-perl, + libdevel-cover-perl, libdigest-sha-perl | perl, libemail-date-perl, libemail-valid-perl, @@ -283,6 +286,7 @@ Depends: libalgorithm-checkdigits-perl, libpdf-reuse-barcode-perl, libpdf-reuse-perl, libpdf-table-perl, + librdf-trine-perl, libscalar-list-utils-perl, libschedule-at-perl, libsms-send-perl, 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 ee39f31..d5b454d 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/cat-toolbar.inc @@ -221,7 +221,7 @@ CAN_user_serials_create_subscription ) %] <button class="btn btn-small dropdown-toggle" data-toggle="dropdown"><i class="icon-download-alt"></i> Save <span class="caret"></span></button> <ul class="dropdown-menu"> <li><a href="/cgi-bin/koha/catalogue/export.pl?format=bibtex&op=export&bib=[% biblionumber %]">BIBTEX</a></li> - <li><a href="/cgi-bin/koha/catalogue/export.pl?format=dc&op=export&bib=[% biblionumber %]">Dublin Core (XML)</a></li> + <li><a href="#" data-toggle="modal" data-target="#exportModal_">Dublin Core</a></li> <li><a href="/cgi-bin/koha/catalogue/export.pl?format=marcxml&op=export&bib=[% biblionumber %]">MARCXML</a></li> <li><a href="/cgi-bin/koha/catalogue/export.pl?format=marc8&op=export&bib=[% biblionumber %]">MARC (non-Unicode/MARC-8)</a></li> <li><a href="/cgi-bin/koha/catalogue/export.pl?format=utf8&op=export&bib=[% biblionumber %]">MARC (Unicode/UTF-8)</a></li> diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt index b782616..9349713 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/catalogue/detail.tt @@ -303,6 +303,83 @@ function verify_images() { [% END %] }); + + //JQuery for Dublin Core Modal + $(document).ready(function(){ + $("#input-simple").click(function(){ + $(".rdfoptions").removeAttr("disabled"); + $(".xmloptions").attr("disabled","disabled"); + $("#dc-rdf").toggle(); + $("#dc-xml").hide(); + }); + }); + + $(document).ready(function(){ + $("#input-rdf").click(function(){ + $(".rdfoptions").removeAttr("disabled"); + $(".xmloptions").attr("disabled","disabled"); + $("#dc-rdf").toggle(); + $("#dc-xml").hide(); + }); + }); + + $(document).ready(function(){ + $("#input-xml").click(function(){ + $(".xmloptions").removeAttr("disabled"); + $(".rdfoptions").attr("disabled","disabled"); + if($("#root_checked").is(':checked')){ + $("#root_value").removeAttr("disabled"); + } else { + $("#root_value").attr("disabled","disabled"); + } + if($("#schema_checked").is(':checked')){ + $("#schema_value").removeAttr("disabled"); + } else { + $("#schema_value").attr("disabled","disabled"); + } + $("#dc-xml").toggle(); + $("#dc-rdf").hide(); + }); + }); + + $(document).ready(function(){ + $("#root_checked").click(function(){ + if($("#root_checked").is(':checked')){ + $("#root_value").removeAttr("disabled"); + } else { + $("#root_value").attr("disabled","disabled"); + } + }); + }); + + $(document).ready(function(){ + $("#schema_checked").click(function(){ + if($("#schema_checked").is(':checked')){ + $("#schema_value").removeAttr("disabled"); + } else { + $("#schema_value").attr("disabled","disabled"); + } + }); + }); + + $(document).ready(function(){ + $("#input-oai").click(function(){ + $(".rdfoptions").attr("disabled","disabled"); + $(".xmloptions").attr("disabled","disabled"); + $("#dc-rdf").hide(); + $("#dc-xml").hide(); + }); + }); + + $(document).ready(function(){ + $("#input-srw").click(function(){ + $(".rdfoptions").attr("disabled","disabled"); + $(".xmloptions").attr("disabled","disabled"); + $("#dc-rdf").hide(); + $("#dc-xml").hide(); + }); + }); + //]]> </script> </head> @@ -1043,7 +1120,7 @@ function verify_images() { <th>Save Record</th> </tr> <tr><td> Select download format: <select name="format"> <option value="mods">MODS (XML)</option> - <option value="dc">Dublin Core (XML)</option> + <option data-toggle="modal" data-target="#exportModal_">Dublin Core</option> <option value="marcxml">MARCXML</option> <option value="marc8">MARC (non-Unicode/MARC-8)</option> <option value="utf8">MARC (Unicode/UTF-8)</option> </select> @@ -1076,4 +1153,68 @@ function verify_images() { </div> [% END %] </div> + + <!--Modal for Dublin Core--> + <div class="modal hide" id="exportModal_" tabindex="-1" role="dialog" aria-labelledby="exportLabelexportModal_" aria-hidden="true"> + <div class="modal-header"> + <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button> + <h3 id="exportLabelexportModal_">Exporting to Dublin Core...</h3> + </div> + <!-- Form to recolect data for Dublin Core--> + <form method="get" action="/cgi-bin/koha/catalogue/export.pl"> + <div class="modal-body"> + <fieldset> + <input id="input-simple" type="radio" name="recommendation" value="simple-dc-rdf">Simple DC-RDF + <br> + <input id="input-rdf" type="radio" name="recommendation" value="dc-rdf" checked>DC-RDF (Recommended) + <div id="dc-rdf" style="display:none;"> + <br>DC-RDF is downloadable in "rdfxml" by default (this is according with the + <br>recommendation), but you can download the file in other formats: + <br> + <input class="rdfoptions" type="radio" name="formats" value="rdfxml" checked>rdfxml + <input class="rdfoptions" type="radio" name="formats" value="rdfjson">rdfjson + <input class="rdfoptions" type="radio" name="formats" value="ntriples">ntriples + <input class="rdfoptions" type="radio" name="formats" value="nquads">nquads + <input class="rdfoptions" type="radio" name="formats" value="ntriples-canonical">ntriples-canonical + <input class="rdfoptions" type="radio" name="formats" value="turtle">turtle + </div> + <br> + <input id="input-xml" type="radio" name="recommendation" value="dc-xml">DC-XML + <div id="dc-xml" style="display:none;"> + <p><b>Output options for DC-XML</b> + <br> + <b>Dublin Core level</b></p> + <p><input class="xmloptions" type="radio" name="qualifier" value="0" checked disabled>Simple Dublin Core + <input class="xmloptions" type="radio" name="qualifier" value="1" disabled>Qualified Dublin Core</p> + <b>Other data (Optional)</b> + <br> + <input id="root_checked" class="xmloptions" type="checkbox" value="root"> + Root element: <input id="root_value" class="xmloptions" type="text" name="root_element" value="metadata" disabled> + <br>The xsischemaLocation should be filled like: + <br>http://example.org/myapp/ http://example.org/myapp/schema.xsd + <br> + <input id="schema_checked" class="xmloptions" type="checkbox" value="schema" disabled> + xsischemaLocation: <input id="schema_value" class="xmloptions" type="url" name="xsischemalocation" disabled><br> + </div> + <br> + <input id="input-oai" type="radio" name="recommendation" value="oai-dc">OAI-DC + <br> + <input id="input-srw" type="radio" name="recommendation" value="srw_dc">SRW-DC + <br> + <div> + <br>Learn more about DCMI Specifications at: <a href="http://dublincore.org/specifications/">http://dublincore.org/specifications/</a> + <br> + </div> + </fieldset> + </div> + <div class="modal-footer"> + <button type="submit" class="btn">Export</button> + <button class="btn btn-link" data-dismiss="modal" aria-hidden="true">Cancel</button> + </div> + <input type="hidden" name="op" value="export" /> + <input type="hidden" name="format" value="dc" /> + <input type="hidden" name="bib" value="[% biblionumber %]" /> + </form> + </div> + [% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc index aeca007..6fe1a3c 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc +++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc @@ -316,3 +316,69 @@ </div> </form> <!-- /#auth --> </div> <!-- /#modalAuth --> + + <!-- Dublin Core Modal Form --> + <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button> + <h4 class="modal-title" id="myModalLabel">Exporting to Dublin Core...</h4> + </div> + <form method="get" action="/cgi-bin/koha/opac-export.pl"> + <div class="modal-body"> + <fieldset> + <input id="input-simple" type="radio" name="recommendation" value="simple-dc-rdf">Simple DC-RDF + <br> + <input id="input-rdf" type="radio" name="recommendation" value="dc-rdf" checked>DC-RDF (Recommended) + <div id="dc-rdf" style="display:none;"> + <br>DC-RDF is downloadable in "rdfxml" by default (this is according with the + <br>recommendation), but you can download the file in other formats: + <br> + <input class="rdfoptions" type="radio" name="formats" value="rdfxml" checked>rdfxml + <input class="rdfoptions" type="radio" name="formats" value="rdfjson">rdfjson + <input class="rdfoptions" type="radio" name="formats" value="ntriples">ntriples + <input class="rdfoptions" type="radio" name="formats" value="nquads">nquads + <input class="rdfoptions" type="radio" name="formats" value="ntriples-canonical">ntriples-canonical + <input class="rdfoptions" type="radio" name="formats" value="turtle">turtle + </div> + <br> + <input id="input-xml" type="radio" name="recommendation" value="dc-xml">DC-XML + <div id="dc-xml" style="display:none;"> + <p><b>Output options for DC-XML</b> + <br> + <b>Dublin Core level</b></p> + <p><input class="xmloptions" type="radio" name="qualifier" value="0" checked disabled>Simple Dublin Core + <input class="xmloptions" type="radio" name="qualifier" value="1" disabled>Qualified Dublin Core</p> + <b>Other data (Optional)</b> + <br> + <input id="root_checked" class="xmloptions" type="checkbox" value="root"> + Root element: <input id="root_value" class="xmloptions" type="text" name="root_element" value="metadata" disabled> + <br>The xsischemaLocation should be filled like: + <br>http://example.org/myapp/ http://example.org/myapp/schema.xsd + <br> + <input id="schema_checked" class="xmloptions" type="checkbox" value="schema" disabled> + xsischemaLocation: <input id="schema_value" class="xmloptions" type="url" name="xsischemalocation" disabled><br> + </div> + <br> + <input id="input-oai" type="radio" name="recommendation" value="oai-dc">OAI-DC + <br> + <input id="input-srw" type="radio" name="recommendation" value="srw_dc">SRW-DC + <br> + <div> + <br>Learn more about DCMI Specifications at: <a href="http://dublincore.org/specifications/">http://dublincore.org/specifications/</a> + <br> + </div> + </fieldset> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> + <button type="submit" class="btn btn-primary">Export</button> + </div> + <input type="hidden" name="op" value="export" /> + <input type="hidden" name="format" value="dc" /> + <input type="hidden" name="bib" value="[% biblionumber %]" /> + </form> + </div> + </div> + </div> diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/opac-detail-sidebar.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/opac-detail-sidebar.inc index a4ef40e..069c0cf 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/opac-detail-sidebar.inc +++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/opac-detail-sidebar.inc @@ -38,11 +38,13 @@ <a id="format" class="dropdown-toggle" data-toggle="dropdown" href="#">Save record <b class="caret"></b></a> <ul class="dropdown-menu pull-right" role="menu" aria-labelledby="format"> [% FOREACH option IN export_options %] + [% IF option == 'dc' %] + <li><a role="menuitem" href="#" data-toggle="modal" data-target="#myModal">Dublin Core</a></li> + [% ELSE %] <li> <a role="menuitem" href="/cgi-bin/koha/opac-export.pl?op=export&bib=[% biblionumber %]&format=[% option %]"> [% SWITCH option %] [% CASE 'bibtex' %]BIBTEX - [% CASE 'dc' %]Dublin Core (XML) [% CASE 'endnote' %]EndNote [% CASE 'marcxml' %]MARCXML [% CASE 'marc8' %]MARC (non-Unicode/MARC-8) @@ -54,6 +56,7 @@ [% END %] </a> </li> + [% END %] [% END %] </ul> </div> diff --git a/koha-tmpl/opac-tmpl/bootstrap/js/script.js b/koha-tmpl/opac-tmpl/bootstrap/js/script.js index de8401b..29b6108 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/js/script.js +++ b/koha-tmpl/opac-tmpl/bootstrap/js/script.js @@ -63,4 +63,80 @@ $(document).ready(function(){ $("#members").removeAttr("style"); } }); -}); \ No newline at end of file +}); + + //JQuery for Dublin Core Modal + $(document).ready(function(){ + $("#input-simple").click(function(){ + $(".rdfoptions").removeAttr("disabled"); + $(".xmloptions").attr("disabled","disabled"); + $("#dc-rdf").toggle(); + $("#dc-xml").hide(); + }); + }); + + $(document).ready(function(){ + $("#input-rdf").click(function(){ + $(".rdfoptions").removeAttr("disabled"); + $(".xmloptions").attr("disabled","disabled"); + $("#dc-rdf").toggle(); + $("#dc-xml").hide(); + }); + }); + + $(document).ready(function(){ + $("#input-xml").click(function(){ + $(".xmloptions").removeAttr("disabled"); + $(".rdfoptions").attr("disabled","disabled"); + if($("#root_checked").is(':checked')){ + $("#root_value").removeAttr("disabled"); + } else { + $("#root_value").attr("disabled","disabled"); + } + if($("#schema_checked").is(':checked')){ + $("#schema_value").removeAttr("disabled"); + } else { + $("#schema_value").attr("disabled","disabled"); + } + $("#dc-xml").toggle(); + $("#dc-rdf").hide(); + }); + }); + + $(document).ready(function(){ + $("#root_checked").click(function(){ + if($("#root_checked").is(':checked')){ + $("#root_value").removeAttr("disabled"); + } else { + $("#root_value").attr("disabled","disabled"); + } + }); + }); + + $(document).ready(function(){ + $("#schema_checked").click(function(){ + if($("#schema_checked").is(':checked')){ + $("#schema_value").removeAttr("disabled"); + } else { + $("#schema_value").attr("disabled","disabled"); + } + }); + }); + + $(document).ready(function(){ + $("#input-oai").click(function(){ + $(".rdfoptions").attr("disabled","disabled"); + $(".xmloptions").attr("disabled","disabled"); + $("#dc-rdf").hide(); + $("#dc-xml").hide(); + }); + }); + + $(document).ready(function(){ + $("#input-srw").click(function(){ + $(".rdfoptions").attr("disabled","disabled"); + $(".xmloptions").attr("disabled","disabled"); + $("#dc-rdf").hide(); + $("#dc-xml").hide(); + }); + }); diff --git a/opac/opac-export.pl b/opac/opac-export.pl index bf040db..c7bfe61 100755 --- a/opac/opac-export.pl +++ b/opac/opac-export.pl @@ -34,6 +34,14 @@ my $format=$query->param("format")||'utf8'; my $biblionumber = $query->param("bib")||0; $biblionumber = int($biblionumber); my ($marc, $error)= ('',''); +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/opac-detail.pl?biblionumber=' . + $biblionumber; $marc = GetMarcBiblio($biblionumber, 1) if $biblionumber; if(!$marc) { @@ -61,8 +69,32 @@ elsif ($format =~ /bibtex/) { $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/) { ($error,$marc) = changeEncoding($marc,"MARC","MARC21","MARC-8"); diff --git a/t/db_dependent/Record.t b/t/db_dependent/Record.t index e8d6094..d372c4c 100755 --- a/t/db_dependent/Record.t +++ b/t/db_dependent/Record.t @@ -46,29 +46,22 @@ is ($marcxml, $testxml, "testing marc2xml"); my $marcconvert=marcxml2marc($marcxml); is ($marcconvert->as_xml,$marc->as_xml, "testing xml2marc"); -my $marcdc=marc2dcxml($marc); -my $test2xml=qq(<?xml version="1.0" encoding="UTF-8"?> -<metadata - xmlns="http://example.org/myapp/" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://example.org/myapp/ http://example.org/myapp/schema.xsd" - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:dcterms="http://purl.org/dc/terms/"> -</metadata>); - -is ($marcdc, $test2xml, "testing marc2dcxml"); - -my $marcqualified=marc2dcxml($marc,1); -my $test3xml=qq(<?xml version="1.0" encoding="UTF-8"?> -<metadata - xmlns="http://example.org/myapp/" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://example.org/myapp/ http://example.org/myapp/schema.xsd" - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:dcterms="http://purl.org/dc/terms/"> -</metadata>); - -is ($marcqualified, $test3xml, "testing marcQualified"); +my $marcrdf=marc2dcxml($marc, 'dc-rdf', 1, + 0, undef, undef, undef, 'rdfxml'); +my $test2xml=qq(<?xml version="1.0" encoding="utf-8"?> +<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dcam="http://purl.org/dc/dcam/" xmlns:dcterms="http://purl.org/dc/terms/"> +</rdf:RDF> +); + +is ($marcrdf, $test2xml, "testing marc2dcxml with dc-rdf"); + +my $marcdc=marc2dcxml($marc, 'dc-xml', 0, + 0, 'metadata', undef, undef, undef); +my $test3xml=qq(<?xml version="1.0" encoding="utf-8"?> +<metadata xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dc="http://purl.org/dc/elements/1.1/"/> +); + +is ($marcdc, $test3xml, "testing marc2dcxml with unqualified dc-xml"); my $mods=marc2modsxml($marc); my $test4xml=qq(<?xml version="1.0" encoding="UTF-8"?> -- 1.7.10.4