11require 'open-uri'
22require 'tess_rdf_extractors'
3- require 'oai'
4- require 'nokogiri'
53
64module Ingestors
75 class OaiPmhIngestor < Ingestor
@@ -18,15 +16,25 @@ def self.config
1816 }
1917 end
2018
19+ def read ( source_url )
20+ client = OAI ::Client . new source_url , headers : { 'From' => config [ :mail ] }
21+ found_bioschemas = begin
22+ read_oai_rdf ( client )
23+ rescue OAI ::ArgumentException
24+ false
25+ end
26+
27+ read_oai_default ( client ) unless found_bioschemas
28+ end
29+
2130 def ns
2231 {
2332 'dc' => 'http://purl.org/dc/elements/1.1/' ,
2433 'oai_dc' => 'http://www.openarchives.org/OAI/2.0/oai_dc/'
2534 }
2635 end
2736
28- def read ( source_url )
29- client = OAI ::Client . new source_url , headers : { 'From' => config [ :mail ] }
37+ def read_oai_default ( client )
3038 count = 0
3139 client . list_records . full . each do |record |
3240 read_dublin_core ( record . metadata . to_s )
@@ -52,7 +60,9 @@ def read_dublin_core_material(xml_doc)
5260 material . description = convert_description ( xml_doc . at_xpath ( '//dc:description' , ns ) &.text )
5361 material . authors = xml_doc . xpath ( '//dc:creator' , ns ) . map ( &:text )
5462 material . contributors = xml_doc . xpath ( '//dc:contributor' , ns ) . map ( &:text )
55- material . licence = xml_doc . at_xpath ( '//dc:rights' , ns ) &.text
63+
64+ rights = xml_doc . xpath ( '//dc:rights' , ns ) . map { |n | n . text &.strip } . reject ( &:empty? )
65+ material . licence = rights . find { |r | r . start_with? ( 'http://' , 'https://' ) } || rights . first || 'notspecified'
5666
5767 dates = xml_doc . xpath ( '//dc:date' , ns ) . map ( &:text )
5868 parsed_dates = dates . map do |d |
@@ -100,5 +110,144 @@ def read_dublin_core_event(_xml_doc)
100110
101111 add_event event
102112 end
113+
114+ def read_oai_rdf ( client )
115+ provider_events = [ ]
116+ provider_materials = [ ]
117+ totals = Hash . new ( 0 )
118+
119+ client . list_records ( metadata_prefix : 'rdf' ) . full . each do |record |
120+ metadata_tag = Nokogiri ::XML ( record . metadata . to_s )
121+ bioschemas_xml = metadata_tag . at_xpath ( 'metadata/rdf:RDF' , 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' ) &.to_s
122+ output = parse_bioschemas ( bioschemas_xml )
123+ next unless output
124+
125+ provider_events += output [ :resources ] [ :events ]
126+ provider_materials += output [ :resources ] [ :materials ]
127+ output [ :totals ] . each do |key , value |
128+ totals [ key ] += value
129+ end
130+ end
131+
132+ if totals . keys . any?
133+ bioschemas_summary = "Bioschemas summary:\n "
134+ totals . each do |type , count |
135+ bioschemas_summary << "\n - #{ type } : #{ count } "
136+ end
137+ @messages << bioschemas_summary
138+ end
139+
140+ deduplicate ( provider_events ) . each do |event_params |
141+ add_event ( event_params )
142+ end
143+
144+ deduplicate ( provider_materials ) . each do |material_params |
145+ add_material ( material_params )
146+ end
147+
148+ provider_events . any? || provider_materials . any?
149+ end
150+
151+ def parse_bioschemas ( content )
152+ output = {
153+ resources : {
154+ events : [ ] ,
155+ materials : [ ]
156+ } ,
157+ totals : Hash . new ( 0 )
158+ }
159+
160+ return output unless content
161+
162+ begin
163+ events = Tess ::Rdf ::EventExtractor . new ( content , :rdfxml ) . extract do |p |
164+ convert_params ( p )
165+ end
166+ courses = Tess ::Rdf ::CourseExtractor . new ( content , :rdfxml ) . extract do |p |
167+ convert_params ( p )
168+ end
169+ course_instances = Tess ::Rdf ::CourseInstanceExtractor . new ( content , :rdfxml ) . extract do |p |
170+ convert_params ( p )
171+ end
172+ learning_resources = Tess ::Rdf ::LearningResourceExtractor . new ( content , :rdfxml ) . extract do |p |
173+ convert_params ( p )
174+ end
175+ output [ :totals ] [ 'Events' ] += events . count
176+ output [ :totals ] [ 'Courses' ] += courses . count
177+ output [ :totals ] [ 'CourseInstances' ] += course_instances . count
178+ output [ :totals ] [ 'LearningResources' ] += learning_resources . count
179+
180+ deduplicate ( events + courses + course_instances ) . each do |event |
181+ output [ :resources ] [ :events ] << event
182+ end
183+
184+ deduplicate ( learning_resources ) . each do |material |
185+ output [ :resources ] [ :materials ] << material
186+ end
187+ rescue StandardError => e
188+ Rails . logger . error ( "#{ e . class } : #{ e . message } " )
189+ Rails . logger . error ( e . backtrace . join ( "\n " ) ) if e . backtrace &.any?
190+ error = 'An error'
191+ comment = nil
192+ if e . is_a? ( RDF ::ReaderError )
193+ error = 'A parsing error'
194+ comment = 'Please check your page contains valid RDF/XML.'
195+ end
196+ message = "#{ error } occurred while reading the source."
197+ message << " #{ comment } " if comment
198+ @messages << message
199+ end
200+
201+ output
202+ end
203+
204+ # ---- This is copied unchanged from bioschemas_ingestor.rb and needs to be refactored. ----
205+
206+ # If duplicate resources have been extracted, prefer ones with the most metadata.
207+ def deduplicate ( resources )
208+ return [ ] unless resources . any?
209+
210+ puts "De-duplicating #{ resources . count } resources" if verbose
211+ hash = { }
212+ scores = { }
213+ resources . each do |resource |
214+ resource_url = resource [ :url ]
215+ puts " Considering: #{ resource_url } " if verbose
216+ if hash [ resource_url ]
217+ score = metadata_score ( resource )
218+ # Replace the resource if this resource has a higher metadata score
219+ puts " Duplicate! Comparing #{ score } vs. #{ scores [ resource_url ] } " if verbose
220+ if score > scores [ resource_url ]
221+ puts ' Replacing resource' if verbose
222+ hash [ resource_url ] = resource
223+ scores [ resource_url ] = score
224+ end
225+ else
226+ puts ' Not present, adding' if verbose
227+ hash [ resource_url ] = resource
228+ scores [ resource_url ] = metadata_score ( resource )
229+ end
230+ end
231+
232+ puts "#{ hash . values . count } resources after de-duplication" if verbose
233+
234+ hash . values
235+ end
236+
237+ # Score based on number of metadata fields available
238+ def metadata_score ( resource )
239+ score = 0
240+ resource . each_value do |value |
241+ score += 1 unless value . nil? || value == { } || value == [ ] || ( value . is_a? ( String ) && value . strip == '' )
242+ end
243+
244+ score
245+ end
246+
247+ def convert_params ( params )
248+ params [ :description ] = convert_description ( params [ :description ] ) if params . key? ( :description )
249+
250+ params
251+ end
103252 end
104253end
0 commit comments