# frozen_string_literal: true module Briard module DataciteUtils def datacite_xml @datacite_xml ||= Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml| xml.resource(root_attributes) do insert_work(xml) end end.to_xml end def datacite_errors(xml: nil, schema_version: nil) if xml.present? namespaces = Nokogiri::XML(xml, nil, 'UTF-8').root.namespaces schema_version = namespaces.fetch('xmlns', nil).presence || namespaces.fetch('xmlns:ns0', nil).presence end # handle crossref namespace unless schema_version.to_s.start_with?('http://datacite.org/schema/kernel') schema_version = 'http://datacite.org/schema/kernel-4' end kernel = schema_version.to_s.split('/').last filepath = File.expand_path("../../../resources/#{kernel}/metadata.xsd", __FILE__) schema = Nokogiri::XML::Schema(open(filepath)) schema.validate(Nokogiri::XML(xml, nil, 'UTF-8')).map(&:to_s).unwrap rescue Nokogiri::XML::SyntaxError => e e.message end def insert_work(xml) insert_identifier(xml) insert_creators(xml) insert_titles(xml) insert_publisher(xml) insert_publication_year(xml) insert_resource_type(xml) insert_subjects(xml) insert_contributors(xml) insert_dates(xml) insert_language(xml) insert_alternate_identifiers(xml) insert_related_identifiers(xml) insert_related_items(xml) insert_sizes(xml) insert_formats(xml) insert_version(xml) insert_rights_list(xml) insert_descriptions(xml) insert_geo_locations(xml) insert_funding_references(xml) end def insert_identifier(xml) xml.identifier(doi, 'identifierType' => 'DOI') end def insert_creators(xml) xml.creators do Array.wrap(creators).each do |au| xml.creator do insert_person(xml, au, 'creator') end end end end def insert_contributors(xml) return xml unless contributors.present? xml.contributors do Array.wrap(contributors).each do |con| xml.contributor('contributorType' => con['contributorType'] || 'Other') do insert_person(xml, con, 'contributor') end end end end def insert_person(xml, person, type) person_name = if person['familyName'].present? [person['familyName'], person['givenName']].compact.join(', ') else person['name'] end attributes = { 'nameType' => person['nameType'] }.compact xml.send("#{type}Name", person_name, attributes) xml.givenName(person['givenName']) if person['givenName'].present? xml.familyName(person['familyName']) if person['familyName'].present? Array.wrap(person['nameIdentifiers']).each do |ni| xml.nameIdentifier(ni['nameIdentifier'], 'nameIdentifierScheme' => ni['nameIdentifierScheme'], 'schemeURI' => ni['schemeUri']) end Array.wrap(person['affiliation']).each do |affiliation| attributes = { 'affiliationIdentifier' => affiliation['affiliationIdentifier'], 'affiliationIdentifierScheme' => affiliation['affiliationIdentifierScheme'], 'schemeURI' => affiliation['schemeUri'] }.compact xml.affiliation(affiliation['name'], attributes) end end def insert_titles(xml) xml.titles do Array.wrap(titles).each do |title| if title.is_a?(Hash) t = title else t = {} t['title'] = title end attributes = { 'titleType' => t['titleType'], 'xml:lang' => t['lang'] }.compact xml.title(t['title'], attributes) end end end def insert_publisher(xml) xml.publisher(publisher || (container && container['title'])) end def insert_publication_year(xml) xml.publicationYear(publication_year) end def insert_resource_type(xml) unless types.is_a?(Hash) && (types['schemaOrg'].present? || types['resourceTypeGeneral']) return xml end xml.resourceType(types['resourceType'] || types['schemaOrg'], 'resourceTypeGeneral' => types['resourceTypeGeneral'] || Metadata::SO_TO_DC_TRANSLATIONS[types['schemaOrg']] || 'Other') end def insert_alternate_identifiers(xml) alternate_identifiers = Array.wrap(identifiers).reject { |r| r['identifierType'] == 'DOI' } return xml unless alternate_identifiers.present? xml.alternateIdentifiers do Array.wrap(alternate_identifiers).each do |alternate_identifier| xml.alternateIdentifier(alternate_identifier['identifier'], 'alternateIdentifierType' => alternate_identifier['identifierType']) end end end def insert_dates(xml) return xml unless Array.wrap(dates).present? xml.dates do Array.wrap(dates).each do |date| attributes = { 'dateType' => date['dateType'] || 'Issued', 'dateInformation' => date['dateInformation'] }.compact xml.date(date['date'], attributes) end end end def insert_funding_references(xml) return xml unless Array.wrap(funding_references).present? xml.fundingReferences do Array.wrap(funding_references).each do |funding_reference| xml.fundingReference do xml.funderName(funding_reference['funderName']) if funding_reference['funderIdentifier'].present? xml.funderIdentifier(funding_reference['funderIdentifier'], { 'funderIdentifierType' => funding_reference['funderIdentifierType'] }.compact) end if funding_reference['awardNumber'].present? || funding_reference['awardUri'].present? xml.awardNumber(funding_reference['awardNumber'], { 'awardURI' => funding_reference['awardUri'] }.compact) end if funding_reference['awardTitle'].present? xml.awardTitle(funding_reference['awardTitle']) end end end end end def insert_subjects(xml) return xml unless subjects.present? xml.subjects do subjects.each do |subject| if subject.is_a?(Hash) s = subject else s = {} s['subject'] = subject end attributes = { 'subjectScheme' => s['subjectScheme'], 'schemeURI' => s['schemeUri'], 'valueURI' => s['valueUri'], 'xml:lang' => s['lang'] }.compact xml.subject(s['subject'], attributes) end end end def insert_version(xml) return xml unless version_info.present? xml.version(version_info) end def insert_language(xml) return xml unless language.present? xml.language(language) end def insert_related_identifiers(xml) return xml unless related_identifiers.present? xml.relatedIdentifiers do related_identifiers.each do |related_identifier| attributes = { 'relatedIdentifierType' => related_identifier['relatedIdentifierType'], 'relationType' => related_identifier['relationType'], 'resourceTypeGeneral' => related_identifier['resourceTypeGeneral'] }.compact if %w[HasMetadata IsMetadataFor].include?(related_identifier['relationType']) attributes.merge({ 'relatedMetadataScheme' => related_identifier['relatedMetadataSchema'], 'schemeURI' => related_identifier['schemeUri'], 'schemeType' => related_identifier['schemeType'] }.compact) end xml.relatedIdentifier(related_identifier['relatedIdentifier'], attributes) end end end def insert_related_items(xml) return xml unless related_items.present? xml.relatedItems do related_items.each do |related_item| attributes = { 'relatedItemType' => related_item['relatedItemType'], 'relationType' => related_item['relationType'] }.compact xml.relatedItem(related_item['relatedItem'], attributes) do xml.relatedItemIdentifier(related_item['relatedItemIdentifier']['relatedItemIdentifier'], { 'relatedItemIdentifierType' => related_item['relatedItemIdentifier']['relatedItemIdentifierType'], 'relatedMetadataScheme' => related_item['relatedItemIdentifier']['relatedMetadataScheme'], 'schemeURI' => related_item['relatedItemIdentifier']['schemeURI'], 'schemeType' => related_item['relatedItemIdentifier']['schemeType'] }.compact) xml.creators do Array.wrap(related_item['creators']).each do |au| xml.creator do insert_person(xml, au, 'creator') end end end xml.titles do Array.wrap(related_item['titles']).each do |title| if title.is_a?(Hash) t = title else t = {} t['title'] = title end attributes = { 'titleType' => t['titleType'], 'xml:lang' => t['lang'] }.compact xml.title(t['title'], attributes) end end xml.publicationYear(related_item['publicationYear']) xml.volume(related_item['volume']) xml.issue(related_item['issue']) xml.number(related_item['number'], { 'numberType' => related_item['numberType'] }.compact) xml.firstPage(related_item['firstPage']) xml.lastPage(related_item['lastPage']) xml.publisher(related_item['publisher']) xml.edition(related_item['edition']) xml.contributors do Array.wrap(related_item['contributors']).each do |con| xml.contributor('contributorType' => con['contributorType'] || 'Other') do insert_person(xml, con, 'contributor') end end end end end end end def insert_sizes(xml) xml.sizes do Array.wrap(sizes).each do |s| xml.size(s) end end end def insert_formats(xml) xml.formats do Array.wrap(formats).each do |f| xml.format(f) end end end def insert_rights_list(xml) return xml unless rights_list.present? xml.rightsList do Array.wrap(rights_list).each do |rights| if rights.is_a?(Hash) r = rights else r = {} r['rights'] = rights r['rightsUri'] = normalize_id(rights) end attributes = { 'rightsURI' => r['rightsUri'], 'rightsIdentifier' => r['rightsIdentifier'], 'rightsIdentifierScheme' => r['rightsIdentifierScheme'], 'schemeURI' => r['schemeUri'], 'xml:lang' => r['lang'] }.compact xml.rights(r['rights'], attributes) end end end def insert_descriptions(xml) return xml unless descriptions.present? || (container && container['title'].present?) xml.descriptions do if container && container['title'].present? issue = container['issue'].present? ? "(#{container['issue']})" : nil volume_issue = container['volume'].present? ? [container['volume'], issue].join : nil if container['firstPage'].present? pages = [container['firstPage'], container['lastPage']].compact.join('-') end series_information = [container['title'], volume_issue, pages].compact.join(', ') xml.description(series_information, 'descriptionType' => 'SeriesInformation') end Array.wrap(descriptions).each do |description| if description.is_a?(Hash) d = description else d = {} d['description'] = description d['descriptionType'] = 'Abstract' end attributes = { 'xml:lang' => d['lang'], 'descriptionType' => d['descriptionType'] || 'Abstract' }.compact xml.description(d['description'], attributes) end end end def insert_geo_locations(xml) return xml unless geo_locations.present? xml.geoLocations do geo_locations.each do |geo_location| xml.geoLocation do if geo_location['geoLocationPlace'] xml.geoLocationPlace(geo_location['geoLocationPlace']) end if geo_location['geoLocationPoint'] xml.geoLocationPoint do xml.pointLatitude(geo_location.dig('geoLocationPoint', 'pointLatitude')) xml.pointLongitude(geo_location.dig('geoLocationPoint', 'pointLongitude')) end end if geo_location['geoLocationBox'] xml.geoLocationBox do xml.westBoundLongitude(geo_location.dig('geoLocationBox', 'westBoundLongitude')) xml.eastBoundLongitude(geo_location.dig('geoLocationBox', 'eastBoundLongitude')) xml.southBoundLatitude(geo_location.dig('geoLocationBox', 'southBoundLatitude')) xml.northBoundLatitude(geo_location.dig('geoLocationBox', 'northBoundLatitude')) end end geo_location['geoLocationPolygon']&.each do |geo_location_polygon| xml.geoLocationPolygon do geo_location_polygon.each do |polygon_point| xml.polygonPoint do xml.pointLatitude(polygon_point.dig('polygonPoint', 'pointLatitude')) xml.pointLongitude(polygon_point.dig('polygonPoint', 'pointLongitude')) end end end end end end end end def root_attributes { 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation': 'http://datacite.org/schema/kernel-4 http://schema.datacite.org/meta/kernel-4/metadata.xsd', xmlns: 'http://datacite.org/schema/kernel-4' } end end end