oai 0.0.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +128 -0
  3. data/Rakefile +64 -0
  4. data/bin/oai +66 -0
  5. data/examples/models/file_model.rb +63 -0
  6. data/examples/providers/dublin_core.rb +474 -0
  7. data/lib/oai/{get_record.rb → client/get_record.rb} +5 -1
  8. data/lib/oai/client/header.rb +18 -0
  9. data/lib/oai/{identify.rb → client/identify.rb} +2 -0
  10. data/lib/oai/{list_identifiers.rb → client/list_identifiers.rb} +1 -0
  11. data/lib/oai/{list_records.rb → client/list_records.rb} +6 -5
  12. data/lib/oai/{list_sets.rb → client/list_sets.rb} +7 -6
  13. data/lib/oai/client/record.rb +27 -0
  14. data/lib/oai/client/response.rb +53 -0
  15. data/lib/oai/client/resumable.rb +42 -0
  16. data/lib/oai/client.rb +313 -108
  17. data/lib/oai/constants.rb +34 -0
  18. data/lib/oai/exception.rb +80 -1
  19. data/lib/oai/harvester/config.rb +41 -0
  20. data/lib/oai/harvester/harvest.rb +162 -0
  21. data/lib/oai/harvester/logging.rb +68 -0
  22. data/lib/oai/harvester/mailer.rb +17 -0
  23. data/lib/oai/harvester/shell.rb +337 -0
  24. data/lib/oai/harvester.rb +43 -0
  25. data/lib/oai/provider/metadata_format/oai_dc.rb +28 -0
  26. data/lib/oai/provider/metadata_format.rb +119 -0
  27. data/lib/oai/provider/model/activerecord_caching_wrapper.rb +129 -0
  28. data/lib/oai/provider/model/activerecord_wrapper.rb +217 -0
  29. data/lib/oai/provider/model.rb +80 -0
  30. data/lib/oai/provider/partial_result.rb +18 -0
  31. data/lib/oai/provider/response/error.rb +16 -0
  32. data/lib/oai/provider/response/get_record.rb +27 -0
  33. data/lib/oai/provider/response/identify.rb +37 -0
  34. data/lib/oai/provider/response/list_identifiers.rb +30 -0
  35. data/lib/oai/provider/response/list_metadata_formats.rb +39 -0
  36. data/lib/oai/provider/response/list_records.rb +47 -0
  37. data/lib/oai/provider/response/list_sets.rb +23 -0
  38. data/lib/oai/provider/response/record_response.rb +103 -0
  39. data/lib/oai/provider/response.rb +133 -0
  40. data/lib/oai/provider/resumption_token.rb +155 -0
  41. data/lib/oai/provider.rb +487 -0
  42. data/lib/oai/set.rb +14 -5
  43. data/lib/oai/xpath.rb +59 -5
  44. data/lib/oai.rb +7 -13
  45. data/lib/test.rb +25 -0
  46. data/test/activerecord_provider/config/connection.rb +30 -0
  47. data/test/activerecord_provider/database/0001_oaipmh_tables.rb +70 -0
  48. data/test/activerecord_provider/fixtures/dc.yml +1501 -0
  49. data/test/activerecord_provider/helpers/providers.rb +68 -0
  50. data/test/activerecord_provider/helpers/set_provider.rb +24 -0
  51. data/test/activerecord_provider/helpers/transactional_test_case.rb +35 -0
  52. data/test/activerecord_provider/models/dc_field.rb +15 -0
  53. data/test/activerecord_provider/models/dc_lang.rb +3 -0
  54. data/test/activerecord_provider/models/dc_set.rb +7 -0
  55. data/test/activerecord_provider/models/exclusive_set_dc_field.rb +17 -0
  56. data/test/activerecord_provider/models/oai_token.rb +3 -0
  57. data/test/activerecord_provider/tc_activerecord_wrapper.rb +63 -0
  58. data/test/activerecord_provider/tc_ar_provider.rb +199 -0
  59. data/test/activerecord_provider/tc_ar_sets_provider.rb +159 -0
  60. data/test/activerecord_provider/tc_caching_paging_provider.rb +50 -0
  61. data/test/activerecord_provider/tc_simple_paging_provider.rb +71 -0
  62. data/test/activerecord_provider/test_helper_ar_provider.rb +7 -0
  63. data/test/client/helpers/provider.rb +65 -0
  64. data/test/client/helpers/test_wrapper.rb +6 -0
  65. data/test/client/tc_exception.rb +36 -0
  66. data/test/client/tc_get_record.rb +39 -0
  67. data/test/client/tc_http_client.rb +129 -0
  68. data/test/client/tc_identify.rb +13 -0
  69. data/test/client/tc_libxml.rb +61 -0
  70. data/test/{tc_list_identifiers.rb → client/tc_list_identifiers.rb} +28 -9
  71. data/test/{tc_list_metadata_formats.rb → client/tc_list_metadata_formats.rb} +4 -1
  72. data/test/client/tc_list_records.rb +26 -0
  73. data/test/client/tc_list_sets.rb +34 -0
  74. data/test/client/tc_low_resolution_dates.rb +14 -0
  75. data/test/client/tc_utf8_escaping.rb +19 -0
  76. data/test/client/tc_xpath.rb +26 -0
  77. data/test/client/test_helper_client.rb +5 -0
  78. data/test/harvester/tc_harvest.rb +42 -0
  79. data/test/harvester/test_helper_harvester.rb +6 -0
  80. data/test/provider/models.rb +246 -0
  81. data/test/provider/tc_exceptions.rb +81 -0
  82. data/test/provider/tc_functional_tokens.rb +53 -0
  83. data/test/provider/tc_instance_provider.rb +41 -0
  84. data/test/provider/tc_provider.rb +136 -0
  85. data/test/provider/tc_resumption_tokens.rb +52 -0
  86. data/test/provider/tc_simple_provider.rb +138 -0
  87. data/test/provider/test_helper_provider.rb +62 -0
  88. data/test/test.xml +22 -0
  89. metadata +238 -56
  90. data/lib/oai/header.rb +0 -12
  91. data/lib/oai/record.rb +0 -11
  92. data/lib/oai/response.rb +0 -20
  93. data/test/tc_get_record.rb +0 -27
  94. data/test/tc_identify.rb +0 -8
  95. data/test/tc_list_records.rb +0 -9
  96. data/test/tc_list_sets.rb +0 -17
  97. data/test.rb +0 -12
  98. /data/lib/oai/{list_metadata_formats.rb → client/list_metadata_formats.rb} +0 -0
  99. /data/lib/oai/{metadata_format.rb → client/metadata_format.rb} +0 -0
@@ -0,0 +1,487 @@
1
+ require 'rexml/document'
2
+ require 'singleton'
3
+ require 'builder'
4
+
5
+ if not defined?(OAI::Const::VERBS)
6
+ require 'oai/exception'
7
+ require 'oai/constants'
8
+ require 'oai/xpath'
9
+ require 'oai/set'
10
+ end
11
+
12
+ %w{ response metadata_format resumption_token model partial_result
13
+ response/record_response response/identify response/get_record
14
+ response/list_identifiers response/list_records
15
+ response/list_metadata_formats response/list_sets response/error
16
+ }.each { |lib| require File.dirname(__FILE__) + "/provider/#{lib}" }
17
+
18
+ if defined?(ActiveRecord)
19
+ require File.dirname(__FILE__) + "/provider/model/activerecord_wrapper"
20
+ require File.dirname(__FILE__) + "/provider/model/activerecord_caching_wrapper"
21
+ end
22
+
23
+ # # OAI::Provider
24
+ #
25
+ # Open Archives Initiative - Protocol for Metadata Harvesting see
26
+ # <http://www.openarchives.org/>
27
+ #
28
+ # ## Features
29
+ # * Easily setup a simple repository
30
+ # * Simple integration with ActiveRecord
31
+ # * Dublin Core metadata format included
32
+ # * Easily add addition metadata formats
33
+ # * Adaptable to any data source
34
+ # * Simple resumption token support
35
+ #
36
+ # ## Usage
37
+ #
38
+ # To create a functional provider either subclass {OAI::Provider::Base},
39
+ # or reconfigure the defaults.
40
+ #
41
+ # ### Sub classing a provider
42
+ #
43
+ # ```ruby
44
+ # class MyProvider < Oai::Provider
45
+ # repository_name 'My little OAI provider'
46
+ # repository_url 'http://localhost/provider'
47
+ # record_prefix 'oai:localhost'
48
+ # admin_email 'root@localhost' # String or Array
49
+ # source_model MyModel.new # Subclass of OAI::Provider::Model
50
+ # end
51
+ # ```
52
+ #
53
+ # ### Configuring the default provider
54
+ #
55
+ # ```ruby
56
+ # class Oai::Provider::Base
57
+ # repository_name 'My little OAI Provider'
58
+ # repository_url 'http://localhost/provider'
59
+ # record_prefix 'oai:localhost'
60
+ # admin_email 'root@localhost'
61
+ # # record_prefix will be automatically prepended to sample_id, so in this
62
+ # # case it becomes: oai:localhost:13900
63
+ # sample_id '13900'
64
+ # source_model MyModel.new
65
+ # end
66
+ # ```
67
+ #
68
+ # The provider does allow a URL to be passed in at request processing time
69
+ # in case the repository URL cannot be determined ahead of time.
70
+ #
71
+ # ## Integrating with frameworks
72
+ #
73
+ # ### Camping
74
+ #
75
+ # In the Models module of your camping application post model definition:
76
+ #
77
+ # ```ruby
78
+ # class CampingProvider < OAI::Provider::Base
79
+ # repository_name 'Camping Test OAI Repository'
80
+ # source_model ActiveRecordWrapper.new(YOUR_ACTIVE_RECORD_MODEL)
81
+ # end
82
+ # ```
83
+ #
84
+ # In the Controllers module:
85
+ #
86
+ # ```ruby
87
+ # class Oai
88
+ # def get
89
+ # @headers['Content-Type'] = 'text/xml'
90
+ # provider = Models::CampingProvider.new
91
+ # provider.process_request(@input.merge(:url => "http:"+URL(Oai).to_s))
92
+ # end
93
+ # end
94
+ # ```
95
+ #
96
+ # The provider will be available at "/oai"
97
+ #
98
+ # ### Rails
99
+ #
100
+ # At the bottom of environment.rb create a OAI Provider:
101
+ #
102
+ # ```ruby
103
+ # # forgive the standard blog example.
104
+ #
105
+ # require 'oai'
106
+ # class BlogProvider < OAI::Provider::Base
107
+ # repository_name 'My little OAI Provider'
108
+ # repository_url 'http://localhost:3000/provider'
109
+ # record_prefix 'oai:blog'
110
+ # admin_email 'root@localhost'
111
+ # source_model OAI::Provider::ActiveRecordWrapper.new(Post)
112
+ # sample_id '13900' # record prefix used, so becomes oai:blog:13900
113
+ # end
114
+ # ```
115
+ #
116
+ # Create a custom controller:
117
+ #
118
+ # ```ruby
119
+ # class OaiController < ApplicationController
120
+ # def index
121
+ # provider = BlogProvider.new
122
+ # response = provider.process_request(oai_params.to_h)
123
+ # render :body => response, :content_type => 'text/xml'
124
+ # end
125
+ #
126
+ # private
127
+ #
128
+ # def oai_params
129
+ # params.permit(:verb, :identifier, :metadataPrefix, :set, :from, :until, :resumptionToken)
130
+ # end
131
+ # end
132
+ # ```
133
+ #
134
+ # And route to it in your `config/routes.rb` file:
135
+ #
136
+ # ```ruby
137
+ # match 'oai', to: "oai#index", via: [:get, :post]
138
+ # ```
139
+ #
140
+ # Special thanks to Jose Hales-Garcia for this solution.
141
+ #
142
+ # ### Leverage the Provider instance
143
+ #
144
+ # The traditional implementation of the OAI::Provider would pass the OAI::Provider
145
+ # class to the different resposnes. This made it hard to inject context into a
146
+ # common provider. Consider that we might have different request headers that
147
+ # change the scope of the OAI::Provider queries.
148
+ #
149
+ # ```ruby
150
+ # class InstanceProvider
151
+ # def initialize(options = {})
152
+ # super({ :provider_context => :instance_based })
153
+ # @controller = options.fetch(:controller)
154
+ # end
155
+ # attr_reader :controller
156
+ # end
157
+ #
158
+ # class OaiController < ApplicationController
159
+ # def index
160
+ # provider = InstanceProvider.new({ :controller => self })
161
+ # request_body = provider.process_request(oai_params.to_h)
162
+ # render :body => request_body, :content_type => 'text/xml'
163
+ # end
164
+ # ```
165
+ #
166
+ # In the above example, the underlying response object will now receive an
167
+ # instance of the InstanceProvider. Without the `super({ :provider_context => :instance_based })`
168
+ # the response objects would have received the class InstanceProvider as the
169
+ # given provider.
170
+ #
171
+ # ## Supporting custom metadata formats
172
+ #
173
+ # See {OAI::MetadataFormat} for details.
174
+ #
175
+ # ## ActiveRecord Integration
176
+ #
177
+ # ActiveRecord integration is provided by the `ActiveRecordWrapper` class.
178
+ # It takes one required paramater, the class name of the AR class to wrap,
179
+ # and optional hash of options.
180
+ #
181
+ # As of `oai` gem 1.0.0, Rails 5.2.x and Rails 6.0.x are supported.
182
+ # Please check the .travis.yml file at root of repo to see what versions of ruby/rails
183
+ # are being tested, in case this is out of date.
184
+ #
185
+ # Valid options include:
186
+ #
187
+ # * `timestamp_field` - Specifies the model field/method to use as the update
188
+ # filter. Defaults to `updated_at`.
189
+ # * `identifier_field` -- specifies the model field/method to use to get value to use
190
+ # as oai identifier (method return value should not include prefix)
191
+ # * `limit` - Maximum number of records to return in each page/set.
192
+ # Defaults to 100, set to `nil` for all records in one page. Otherwise
193
+ # the wrapper will paginate the result via resumption tokens.
194
+ # _Caution: specifying too large a limit will adversely affect performance._
195
+ #
196
+ # Mapping from a ActiveRecord object to a specific metadata format follows
197
+ # this set of rules:
198
+ #
199
+ # 1. Does `Model#to_{metadata_prefix}` exist? If so just return the result.
200
+ # 2. Does the model provide a map via `Model.map_{metadata_prefix}`? If so
201
+ # use the map to generate the xml document.
202
+ # 3. Loop thru the fields of the metadata format and check to see if the
203
+ # model responds to either the plural, or singular of the field.
204
+ #
205
+ # For maximum control of the xml metadata generated, it's usually best to
206
+ # provide a `to_{metadata_prefix}` in the model. If using Builder be sure
207
+ # not to include any `instruct!` in the xml object.
208
+ #
209
+ # ### Explicit creation example
210
+ #
211
+ # ```ruby
212
+ # class Post < ActiveRecord::Base
213
+ # def to_oai_dc
214
+ # xml = Builder::XmlMarkup.new
215
+ # xml.tag!("oai_dc:dc",
216
+ # 'xmlns:oai_dc' => "http://www.openarchives.org/OAI/2.0/oai_dc/",
217
+ # 'xmlns:dc' => "http://purl.org/dc/elements/1.1/",
218
+ # 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
219
+ # 'xsi:schemaLocation' =>
220
+ # %{http://www.openarchives.org/OAI/2.0/oai_dc/
221
+ # http://www.openarchives.org/OAI/2.0/oai_dc.xsd}) do
222
+ # xml.tag!('oai_dc:title', title)
223
+ # xml.tag!('oai_dc:description', text)
224
+ # xml.tag!('oai_dc:creator', user)
225
+ # tags.each do |tag|
226
+ # xml.tag!('oai_dc:subject', tag)
227
+ # end
228
+ # end
229
+ # xml.target!
230
+ # end
231
+ # end
232
+ # ```
233
+ #
234
+ # ### Mapping Example
235
+ #
236
+ # ```ruby
237
+ # # Extremely contrived mapping
238
+ # class Post < ActiveRecord::Base
239
+ # def self.map_oai_dc
240
+ # {:subject => :tags,
241
+ # :description => :text,
242
+ # :creator => :user,
243
+ # :contibutor => :comments}
244
+ # end
245
+ # end
246
+ # ```
247
+ # ### Scopes for restrictions or eager-loading
248
+ #
249
+ # Instead of passing in a Model class to OAI::Provider::ActiveRecordWrapper, you can actually
250
+ # pass in any scope (or ActiveRecord::Relation). This means you can use it for restrictions:
251
+ #
252
+ # OAI::Provider::ActiveRecordWrapper.new(Post.where(published: true))
253
+ #
254
+ # Or eager-loading an association you will need to create serialization, to avoid n+1 query
255
+ # performance problems:
256
+ #
257
+ # OAI::Provider::ActiveRecordWrapper.new(Post.includes(:categories))
258
+ #
259
+ # Or both of those in combination, or anything else that returns an ActiveRecord::Relation,
260
+ # including using custom scopes, etc.
261
+ #
262
+ # ### Sets?
263
+ #
264
+ # There is some code written to support oai-pmh "sets" in the ActiveRecord::Wrapper, but
265
+ # it's somewhat inflexible, and not well-documented, and as I write this I don't understand
266
+ # it enough to say more. See https://github.com/code4lib/ruby-oai/issues/67
267
+ #
268
+ module OAI::Provider
269
+ class Base
270
+ include OAI::Provider
271
+
272
+ class << self
273
+ attr_reader :formats
274
+ attr_accessor :name, :url, :prefix, :email, :delete_support, :granularity, :model, :identifier, :description
275
+
276
+ def register_format(format)
277
+ @formats ||= {}
278
+ @formats[format.prefix] = format
279
+ end
280
+
281
+ def format_supported?(prefix)
282
+ @formats.keys.include?(prefix)
283
+ end
284
+
285
+ def format(prefix)
286
+ if @formats[prefix].nil?
287
+ raise OAI::FormatException.new
288
+ else
289
+ @formats[prefix]
290
+ end
291
+ end
292
+
293
+ protected
294
+
295
+ def inherited(klass)
296
+ self.instance_variables.each do |iv|
297
+ klass.instance_variable_set(iv, self.instance_variable_get(iv))
298
+ end
299
+ end
300
+
301
+ alias_method :repository_name, :name=
302
+ alias_method :repository_url, :url=
303
+ alias_method :record_prefix, :prefix=
304
+ alias_method :admin_email, :email=
305
+ alias_method :deletion_support, :delete_support=
306
+ alias_method :update_granularity, :granularity=
307
+ alias_method :source_model, :model=
308
+ alias_method :sample_id, :identifier=
309
+ alias_method :extra_description, :description=
310
+
311
+ end
312
+
313
+ # Default configuration of a repository
314
+ Base.repository_name 'Open Archives Initiative Data Provider'
315
+ Base.repository_url 'unknown'
316
+ Base.record_prefix 'oai:localhost'
317
+ Base.admin_email 'nobody@localhost'
318
+ Base.deletion_support OAI::Const::Delete::TRANSIENT
319
+ Base.update_granularity OAI::Const::Granularity::HIGH
320
+ Base.sample_id '13900'
321
+
322
+ Base.register_format(OAI::Provider::Metadata::DublinCore.instance)
323
+
324
+ PROVIDER_CONTEXTS = {
325
+ :class_based => :class_based,
326
+ :instance_based => :instance_based
327
+ }
328
+
329
+ def initialize(options = {})
330
+ provider_context = options.fetch(:provider_context) { :class_based }
331
+ @provider_context = PROVIDER_CONTEXTS.fetch(provider_context)
332
+ end
333
+
334
+ # @note These are the accessor methods on the class. If you need to overwrite
335
+ # them on the instance level you can do that. However, an instance of this
336
+ # class won't be used unless you initialize with:
337
+ # { :provider_context => :instance_based }
338
+ attr_writer :name, :url, :prefix, :email, :delete_support, :granularity, :model, :identifier, :description
339
+
340
+ # The traditional interaction of a Provider has been to:
341
+ #
342
+ # 1) Assign attributes to the Provider class
343
+ # 2) Instantiate the Provider class
344
+ # 3) Call response instance methods for theProvider which pass
345
+ # the Provider class and not the instance.
346
+ #
347
+ # The above behavior continues unless you initialize the Provider with
348
+ # { :provider_context => :instance_based }. If you do that, then the
349
+ # Provider behavior will be:
350
+ #
351
+ # 1) Assign attributes to Provider class
352
+ # 2) Instantiate the Provider class
353
+ # 3) Call response instance methods for theProvider which pass an
354
+ # instance of the Provider to those response objects.
355
+ # a) The instance will mirror all of the assigned Provider class
356
+ # attributes, but allows for overriding and extending on a
357
+ # case by case basis.
358
+ # (Dear reader, please note the second behavior is something most
359
+ # of us would've assumed to be the case, but for historic now lost
360
+ # reasons is not the case.)
361
+ def provider_context
362
+ if @provider_context == :instance_based
363
+ self
364
+ else
365
+ self.class
366
+ end
367
+ end
368
+
369
+ def format_supported?(*args)
370
+ self.class.format_supported?(*args)
371
+ end
372
+
373
+ def format(*args)
374
+ self.class.format(*args)
375
+ end
376
+
377
+ def formats
378
+ self.class.formats
379
+ end
380
+
381
+ def name
382
+ @name || self.class.name
383
+ end
384
+
385
+ def url
386
+ @url || self.class.url
387
+ end
388
+
389
+ def prefix
390
+ @prefix || self.class.prefix
391
+ end
392
+
393
+ def email
394
+ @email || self.class.email
395
+ end
396
+
397
+ def delete_support
398
+ @delete_support || self.class.delete_support
399
+ end
400
+
401
+ def granularity
402
+ @granularity || self.class.granularity
403
+ end
404
+
405
+ def model
406
+ @model || self.class.model
407
+ end
408
+
409
+ def identifier
410
+ @identifier || self.class.identifier
411
+ end
412
+
413
+ def description
414
+ @description || self.class.description
415
+ end
416
+
417
+ # Equivalent to '&verb=Identify', returns information about the repository
418
+ def identify(options = {})
419
+ Response::Identify.new(provider_context, options).to_xml
420
+ end
421
+
422
+ # Equivalent to '&verb=ListSets', returns a list of sets that are supported
423
+ # by the repository or an error if sets are not supported.
424
+ def list_sets(options = {})
425
+ Response::ListSets.new(provider_context, options).to_xml
426
+ end
427
+
428
+ # Equivalent to '&verb=ListMetadataFormats', returns a list of metadata formats
429
+ # supported by the repository.
430
+ def list_metadata_formats(options = {})
431
+ Response::ListMetadataFormats.new(provider_context, options).to_xml
432
+ end
433
+
434
+ # Equivalent to '&verb=ListIdentifiers', returns a list of record headers that
435
+ # meet the supplied criteria.
436
+ def list_identifiers(options = {})
437
+ Response::ListIdentifiers.new(provider_context, options).to_xml
438
+ end
439
+
440
+ # Equivalent to '&verb=ListRecords', returns a list of records that meet the
441
+ # supplied criteria.
442
+ def list_records(options = {})
443
+ Response::ListRecords.new(provider_context, options).to_xml
444
+ end
445
+
446
+ # Equivalent to '&verb=GetRecord', returns a record matching the required
447
+ # :identifier option
448
+ def get_record(options = {})
449
+ Response::GetRecord.new(provider_context, options).to_xml
450
+ end
451
+
452
+ # xml_response = process_verb('ListRecords', :from => 'October 1, 2005',
453
+ # :until => 'November 1, 2005')
454
+ #
455
+ # If you are implementing a web interface using process_request is the
456
+ # preferred way.
457
+ def process_request(params = {})
458
+ begin
459
+
460
+ # Allow the request to pass in a url
461
+ provider_context.url = params['url'] ? params.delete('url') : self.url
462
+
463
+ verb = params.delete('verb') || params.delete(:verb)
464
+
465
+ unless verb and OAI::Const::VERBS.keys.include?(verb)
466
+ raise OAI::VerbException.new
467
+ end
468
+
469
+ send(methodize(verb), params)
470
+
471
+ rescue => err
472
+ if err.respond_to?(:code)
473
+ Response::Error.new(self.class, err).to_xml
474
+ else
475
+ raise err
476
+ end
477
+ end
478
+ end
479
+
480
+ # Convert valid OAI-PMH verbs into ruby method calls
481
+ def methodize(verb)
482
+ verb.gsub(/[A-Z]/) {|m| "_#{m.downcase}"}.sub(/^\_/,'')
483
+ end
484
+
485
+ end
486
+
487
+ end
data/lib/oai/set.rb CHANGED
@@ -7,12 +7,21 @@ module OAI
7
7
  include OAI::XPath
8
8
  attr_accessor :name, :spec, :description
9
9
 
10
- def initialize(element)
11
- @name = xpath(element, './/setName')
12
- @spec = xpath(element, './/setSpec')
13
- @description = xpath_first(element, './/setDescription')
10
+ def initialize(values = {})
11
+ @name = values.delete(:name)
12
+ @spec = values.delete(:spec)
13
+ @description = values.delete(:description)
14
+ raise ArgumentException, "Invalid options" unless values.empty?
14
15
  end
15
-
16
+
17
+ def self.parse(element)
18
+ set = self.new
19
+ set.name = set.xpath(element, './/setName')
20
+ set.spec = set.xpath(element, './/setSpec')
21
+ set.description = set.xpath_first(element, './/setDescription')
22
+ set
23
+ end
24
+
16
25
  def to_s
17
26
  "#{@name} [#{@spec}]"
18
27
  end
data/lib/oai/xpath.rb CHANGED
@@ -1,21 +1,75 @@
1
- require 'rexml/xpath'
2
-
3
1
  module OAI
4
2
  module XPath
3
+
4
+ # get all matching nodes
5
5
  def xpath_all(doc, path)
6
- return REXML::XPath.match(doc, path)
6
+ case parser_type(doc)
7
+ when 'libxml'
8
+ return doc.find(path).to_a if doc.find(path)
9
+ when 'rexml'
10
+ return REXML::XPath.match(doc, path)
11
+ end
12
+ return []
7
13
  end
8
14
 
15
+ # get first matching node
9
16
  def xpath_first(doc, path)
10
17
  elements = xpath_all(doc, path)
11
18
  return elements[0] if elements != nil
12
19
  return nil
13
20
  end
14
21
 
22
+ # get text for first matching node
15
23
  def xpath(doc, path)
16
- e = xpath_first(doc, path)
17
- return e.text if e != nil
24
+ el = xpath_first(doc, path)
25
+ return unless el
26
+ case parser_type(doc)
27
+ when 'libxml'
28
+ return el.content
29
+ when 'rexml'
30
+ return el.text
31
+ end
18
32
  return nil
19
33
  end
34
+
35
+ # figure out an attribute
36
+ def get_attribute(node, attr_name)
37
+ case node.class.to_s
38
+ when 'REXML::Element'
39
+ return node.attribute(attr_name)
40
+ when 'LibXML::XML::Node'
41
+ #There has been a method shift between 0.5 and 0.7
42
+ if defined?(node.property) == nil
43
+ return node.attributes[attr_name]
44
+ else
45
+ #node.property is being deprecated. We'll eventually remove
46
+ #this trap
47
+ begin
48
+ return node[attr_name]
49
+ rescue
50
+ return node.property(attr_name)
51
+ end
52
+ end
53
+ end
54
+ return nil
55
+ end
56
+
57
+ private
58
+
59
+ # figure out what sort of object we should do xpath on
60
+ def parser_type(x)
61
+ case x.class.to_s
62
+ when 'LibXML::XML::Document'
63
+ return 'libxml'
64
+ when 'LibXML::XML::Node'
65
+ return 'libxml'
66
+ when 'LibXML::XML::Node::Set'
67
+ return 'libxml'
68
+ when 'REXML::Element'
69
+ return 'rexml'
70
+ when 'REXML::Document'
71
+ return 'rexml'
72
+ end
73
+ end
20
74
  end
21
75
  end
data/lib/oai.rb CHANGED
@@ -1,14 +1,8 @@
1
- require 'oai/xpath'
2
- require 'oai/response'
3
- require 'oai/exception'
4
- require 'oai/header'
5
- require 'oai/record'
6
- require 'oai/set'
7
- require 'oai/metadata_format'
1
+ require 'rubygems'
2
+ require 'date'
3
+
4
+ # Sub projects (client, provider) require their own libraries so the user
5
+ # can selectively load them.
8
6
  require 'oai/client'
9
- require 'oai/identify'
10
- require 'oai/list_identifiers'
11
- require 'oai/list_metadata_formats'
12
- require 'oai/get_record'
13
- require 'oai/list_records'
14
- require 'oai/list_sets'
7
+ require 'oai/provider'
8
+
data/lib/test.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'oai'
2
+
3
+ buffer = ""
4
+ start_time = Time.now()
5
+
6
+ client = OAI::Client.new 'http://digitalcollections.library.oregonstate.edu/cgi-bin/oai.exe', :parser =>'libxml'
7
+
8
+ last_check = Date.new(2006,9,5)
9
+ records = client.list_records
10
+ # :set => 'archives', :metadata_prefix => 'oai_dc', :from => last_check
11
+
12
+ x = 0
13
+ records.each do |record|
14
+ #fields = record.serialize_metadata(record.metadata, "oai_dc", "Oai_Dc")
15
+ #puts "Primary Title: " + fields.title[0] + "\n"
16
+ puts "Identifier: " + record.header.identifier + "\n"
17
+ x += 1
18
+ end
19
+
20
+ end_time = Time.now()
21
+
22
+ puts buffer
23
+ puts "Time to run: " + (end_time - start_time).to_s + "\n"
24
+ puts "Records returned: " + x.to_s
25
+
@@ -0,0 +1,30 @@
1
+ require 'active_record'
2
+ require 'logger'
3
+
4
+ # Configure AR connection
5
+ #ActiveRecord::Base.logger = Logger.new(STDOUT)
6
+
7
+ if RUBY_PLATFORM == "java"
8
+ require 'jdbc/sqlite3'
9
+ Jdbc::SQLite3.load_driver
10
+ end
11
+
12
+ ActiveRecord::Migration.verbose = false
13
+ ActiveRecord::Base.establish_connection :adapter => "sqlite3",
14
+ :database => ":memory:"
15
+
16
+ if ActiveRecord.version < Gem::Version.new("6.0.0")
17
+ ActiveRecord::MigrationContext.new(
18
+ File.join(File.dirname(__FILE__), '..', 'database')
19
+ ).migrate
20
+ elsif ActiveRecord.version < Gem::Version.new("7.2.0")
21
+ ActiveRecord::MigrationContext.new(
22
+ File.join(File.dirname(__FILE__), '..', 'database'),
23
+ ActiveRecord::Base.connection.schema_migration
24
+ ).migrate
25
+ else
26
+ ActiveRecord::MigrationContext.new(
27
+ File.join(File.dirname(__FILE__), '..', 'database')
28
+ ).migrate
29
+ end
30
+