oai_talia 0.0.13

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 (84) hide show
  1. data/README +81 -0
  2. data/Rakefile +127 -0
  3. data/bin/oai +68 -0
  4. data/examples/models/file_model.rb +63 -0
  5. data/examples/providers/dublin_core.rb +474 -0
  6. data/lib/oai/client/get_record.rb +15 -0
  7. data/lib/oai/client/header.rb +18 -0
  8. data/lib/oai/client/identify.rb +30 -0
  9. data/lib/oai/client/list_identifiers.rb +12 -0
  10. data/lib/oai/client/list_metadata_formats.rb +12 -0
  11. data/lib/oai/client/list_records.rb +21 -0
  12. data/lib/oai/client/list_sets.rb +19 -0
  13. data/lib/oai/client/metadata_format.rb +12 -0
  14. data/lib/oai/client/record.rb +26 -0
  15. data/lib/oai/client/response.rb +35 -0
  16. data/lib/oai/client.rb +301 -0
  17. data/lib/oai/constants.rb +34 -0
  18. data/lib/oai/exception.rb +75 -0
  19. data/lib/oai/harvester/config.rb +41 -0
  20. data/lib/oai/harvester/harvest.rb +150 -0
  21. data/lib/oai/harvester/logging.rb +70 -0
  22. data/lib/oai/harvester/mailer.rb +17 -0
  23. data/lib/oai/harvester/shell.rb +338 -0
  24. data/lib/oai/harvester.rb +39 -0
  25. data/lib/oai/provider/metadata_format/oai_dc.rb +29 -0
  26. data/lib/oai/provider/metadata_format/oai_europeana.rb +38 -0
  27. data/lib/oai/provider/metadata_format.rb +143 -0
  28. data/lib/oai/provider/model/activerecord_caching_wrapper.rb +134 -0
  29. data/lib/oai/provider/model/activerecord_wrapper.rb +139 -0
  30. data/lib/oai/provider/model.rb +74 -0
  31. data/lib/oai/provider/partial_result.rb +18 -0
  32. data/lib/oai/provider/response/error.rb +16 -0
  33. data/lib/oai/provider/response/get_record.rb +26 -0
  34. data/lib/oai/provider/response/identify.rb +25 -0
  35. data/lib/oai/provider/response/list_identifiers.rb +35 -0
  36. data/lib/oai/provider/response/list_metadata_formats.rb +34 -0
  37. data/lib/oai/provider/response/list_records.rb +34 -0
  38. data/lib/oai/provider/response/list_sets.rb +23 -0
  39. data/lib/oai/provider/response/record_response.rb +70 -0
  40. data/lib/oai/provider/response.rb +161 -0
  41. data/lib/oai/provider/resumption_token.rb +106 -0
  42. data/lib/oai/provider.rb +304 -0
  43. data/lib/oai/set.rb +29 -0
  44. data/lib/oai/xpath.rb +75 -0
  45. data/lib/oai.rb +8 -0
  46. data/lib/test.rb +25 -0
  47. data/test/activerecord_provider/config/connection.rb +5 -0
  48. data/test/activerecord_provider/config/database.yml +6 -0
  49. data/test/activerecord_provider/database/ar_migration.rb +59 -0
  50. data/test/activerecord_provider/database/oaipmhtest +0 -0
  51. data/test/activerecord_provider/fixtures/dc.yml +1501 -0
  52. data/test/activerecord_provider/helpers/providers.rb +44 -0
  53. data/test/activerecord_provider/helpers/set_provider.rb +36 -0
  54. data/test/activerecord_provider/models/dc_field.rb +7 -0
  55. data/test/activerecord_provider/models/dc_set.rb +6 -0
  56. data/test/activerecord_provider/models/oai_token.rb +3 -0
  57. data/test/activerecord_provider/tc_ar_provider.rb +113 -0
  58. data/test/activerecord_provider/tc_ar_sets_provider.rb +72 -0
  59. data/test/activerecord_provider/tc_caching_paging_provider.rb +55 -0
  60. data/test/activerecord_provider/tc_simple_paging_provider.rb +57 -0
  61. data/test/activerecord_provider/test_helper.rb +4 -0
  62. data/test/client/helpers/provider.rb +68 -0
  63. data/test/client/helpers/test_wrapper.rb +11 -0
  64. data/test/client/tc_exception.rb +36 -0
  65. data/test/client/tc_get_record.rb +37 -0
  66. data/test/client/tc_identify.rb +13 -0
  67. data/test/client/tc_libxml.rb +61 -0
  68. data/test/client/tc_list_identifiers.rb +52 -0
  69. data/test/client/tc_list_metadata_formats.rb +18 -0
  70. data/test/client/tc_list_records.rb +13 -0
  71. data/test/client/tc_list_sets.rb +19 -0
  72. data/test/client/tc_low_resolution_dates.rb +14 -0
  73. data/test/client/tc_utf8_escaping.rb +11 -0
  74. data/test/client/tc_xpath.rb +26 -0
  75. data/test/client/test_helper.rb +5 -0
  76. data/test/provider/models.rb +234 -0
  77. data/test/provider/tc_exceptions.rb +96 -0
  78. data/test/provider/tc_functional_tokens.rb +43 -0
  79. data/test/provider/tc_provider.rb +71 -0
  80. data/test/provider/tc_resumption_tokens.rb +46 -0
  81. data/test/provider/tc_simple_provider.rb +92 -0
  82. data/test/provider/test_helper.rb +36 -0
  83. data/test/test.xml +22 -0
  84. metadata +181 -0
@@ -0,0 +1,474 @@
1
+ #!/usr/local/bin/ruby -rubygems
2
+ require 'camping'
3
+ require 'camping/session'
4
+ require 'oai/provider'
5
+
6
+ # Extremely simple demo Camping application to illustrate OAI Provider integration
7
+ # with Camping.
8
+ #
9
+ # William Groppe 2/1/2007
10
+ #
11
+
12
+ Camping.goes :DublinCore
13
+
14
+ module DublinCore
15
+ include Camping::Session
16
+
17
+ FIELDS = ['title', 'creator', 'subject', 'description',
18
+ 'publisher', 'contributor', 'date', 'type', 'format',
19
+ 'identifier', 'source', 'language', 'relation', 'coverage', 'rights']
20
+
21
+ def DublinCore.create
22
+ Camping::Models::Session.create_schema
23
+ DublinCore::Models.create_schema :assume =>
24
+ (DublinCore::Models::Obj.table_exists? ? 1.0 : 0.0)
25
+ end
26
+
27
+ end
28
+
29
+ module DublinCore::Models
30
+ Base.logger = Logger.new("dublin_core.log")
31
+ Base.inheritance_column = 'field_type'
32
+ Base.default_timezone = :utc
33
+
34
+ class Obj < Base # since Object is reserved
35
+ has_and_belongs_to_many :fields, :join_table => 'dublincore_field_links',
36
+ :foreign_key => 'obj_id', :association_foreign_key => 'field_id'
37
+ DublinCore::FIELDS.each do |field|
38
+ class_eval(%{
39
+ def #{field.pluralize}
40
+ fields.select do |f|
41
+ f if f.field_type == "DC#{field.capitalize}"
42
+ end
43
+ end
44
+ });
45
+ end
46
+ end
47
+
48
+ class Field < Base
49
+ has_and_belongs_to_many :objs, :join_table => 'dublincore_field_links',
50
+ :foreign_key => 'field_id', :association_foreign_key => 'obj_id'
51
+ validates_presence_of :field_type, :message => "can't be blank"
52
+
53
+ # Support sorting by value
54
+ def <=>(other)
55
+ self.to_s <=> other.to_s
56
+ end
57
+
58
+ def to_s
59
+ value
60
+ end
61
+ end
62
+
63
+ DublinCore::FIELDS.each do |field|
64
+ module_eval(%{
65
+ class DC#{field.capitalize} < Field; end
66
+ })
67
+ end
68
+
69
+ # OAI Provider configuration
70
+ class CampingProvider < OAI::Provider::Base
71
+ repository_name 'Camping Test OAI Repository'
72
+ source_model ActiveRecordWrapper.new(Obj)
73
+ end
74
+
75
+ class CreateTheBasics < V 1.0
76
+ def self.up
77
+ create_table :dublincore_objs, :force => true do |t|
78
+ t.column :source, :string
79
+ t.column :created_at, :datetime
80
+ t.column :updated_at, :datetime
81
+ end
82
+
83
+ create_table :dublincore_field_links, :id => false, :force => true do |t|
84
+ t.column :obj_id, :integer, :null => false
85
+ t.column :field_id, :integer, :null => false
86
+ end
87
+
88
+ create_table :dublincore_fields, :force => true do |t|
89
+ t.column :field_type, :string, :limit => 30, :null => false
90
+ t.column :value, :text, :null => false
91
+ end
92
+
93
+ add_index :dublincore_fields, [:field_type, :value], :uniq => true
94
+ add_index :dublincore_field_links, :field_id
95
+ add_index :dublincore_field_links, [:obj_id, :field_id]
96
+ end
97
+
98
+ def self.down
99
+ drop_table :dublincore_objs
100
+ drop_table :dublincore_field_links
101
+ drop_table :dublincore_fields
102
+ end
103
+ end
104
+
105
+ end
106
+
107
+ module DublinCore::Controllers
108
+
109
+ # Now setup a URL('/oai' by default) to handle OAI requests
110
+ class Oai
111
+ def get
112
+ @headers['Content-Type'] = 'text/xml'
113
+ provider = Models::CampingProvider.new
114
+ provider.process_request(@input.merge(:url => "http:"+URL(Oai).to_s))
115
+ end
116
+ end
117
+
118
+ class Index < R '/', '/browse/(\w+)', '/browse/(\w+)/page/(\d+)'
119
+ def get(field = nil, page = 1)
120
+ @field = field
121
+ @page = page.to_i
122
+ @browse = {}
123
+ if !@field
124
+ FIELDS.each do |field|
125
+ @browse[field] = Field.count(
126
+ :conditions => ["field_type = ?", "DC#{field.capitalize}"])
127
+ end
128
+ @home = true
129
+ @count = @browse.keys.size
130
+ else
131
+ @count = Field.count(:conditions => ["field_type = ?", "DC#{@field.capitalize}"])
132
+ fields = Field.find(:all,
133
+ :conditions => ["field_type = ?", "DC#{@field.capitalize}"],
134
+ :order => "value asc", :limit => DublinCore::LIMIT,
135
+ :offset => (@page - 1) * DublinCore::LIMIT)
136
+
137
+ fields.each do |field|
138
+ @browse[field] = field.objs.size
139
+ end
140
+ end
141
+ render :browse
142
+ end
143
+ end
144
+
145
+ class Search < R '/search', '/search/page/(\d+)'
146
+
147
+ def get(page = 1)
148
+ @page = page.to_i
149
+ if input.terms
150
+ @state.terms = input.terms if input.terms
151
+
152
+ start = Time.now
153
+ ids = search(input.terms, @page - 1)
154
+ finish = Time.now
155
+ @search_time = (finish - start)
156
+ @objs = Obj.find(ids)
157
+ else
158
+ @count = 0
159
+ @objs = []
160
+ end
161
+
162
+ render :search
163
+ end
164
+
165
+ end
166
+
167
+ class LinkedTo < R '/linked/(\d+)', '/linked/(\d+)/page/(\d+)'
168
+ def get(field, page = 1)
169
+ @page = page.to_i
170
+ @field = field
171
+ @count = Field.find(field).objs.size
172
+ @objs = Field.find(field).objs.find(:all,
173
+ :limit => DublinCore::LIMIT,
174
+ :offset => (@page - 1) * DublinCore::LIMIT)
175
+ render :records
176
+ end
177
+ end
178
+
179
+ class Add
180
+ def get
181
+ @obj = Obj.create
182
+ render :edit
183
+ end
184
+ end
185
+
186
+ class View < R '/view/(\d+)'
187
+ def get obj_id
188
+ obj = Obj.find(obj_id)
189
+ # Get rid of completely empty records
190
+ obj.destroy if obj.fields.empty?
191
+
192
+ @count = 1
193
+ @objs = [obj]
194
+ if Obj.exists?(obj.id)
195
+ render :records if Obj.exists?(obj.id)
196
+ else
197
+ redirect Index
198
+ end
199
+ end
200
+ end
201
+
202
+ class Edit < R '/edit', '/edit/(\d+)'
203
+ def get obj_id
204
+ @obj = Obj.find obj_id
205
+ render :edit
206
+ end
207
+
208
+ def post
209
+ case input.action
210
+ when 'Save'
211
+ @obj = Obj.find input.obj_id
212
+ @obj.fields.clear
213
+ input.keys.each do |key|
214
+ next unless key =~ /^DublinCore::Models::\w+/
215
+ next unless input[key] && !input[key].empty?
216
+ input[key].to_a.each do |value|
217
+ @obj.fields << key.constantize.find_or_create_by_value(value)
218
+ end
219
+ end
220
+ redirect View, @obj
221
+ when 'Discard'
222
+ @obj = Obj.find input.obj_id
223
+
224
+ # Get rid of completely empty records
225
+ @obj.destroy if @obj.fields.empty?
226
+
227
+ if Obj.exists?(@obj.id)
228
+ redirect View, @obj
229
+ else
230
+ redirect Index
231
+ end
232
+ when 'Delete'
233
+ Obj.find(input.obj_id).destroy
234
+ render :delete_success
235
+ end
236
+ end
237
+ end
238
+
239
+ class DataAdd < R '/data/add'
240
+ def post
241
+ if input.field_value && !input.field_value.empty?
242
+ model = "DublinCore::Models::#{input.field_type}".constantize
243
+ obj = Obj.find(input.obj_id)
244
+ obj.fields << model.find_or_create_by_value(input.field_value)
245
+ end
246
+ redirect Edit, input.obj_id
247
+ end
248
+ end
249
+
250
+ class Style < R '/styles.css'
251
+ def get
252
+ @headers["Content-Type"] = "text/css; charset=utf-8"
253
+ @body = %{
254
+ body { width: 750px; margin: 0; margin-left: auto; margin-right: auto; padding: 0;
255
+ color: black; background-color: white; }
256
+ a { color: #CC6600; text-decoration: none; }
257
+ a:visited { color: #CC6600; text-decoration: none;}
258
+ a:hover { text-decoration: underline; }
259
+ a.stealthy { color: black; }
260
+ a.stealthy:visited { color: black; }
261
+ .header { text-align: right; padding-right: .5em; }
262
+ div.search { text-align: right; position: relative; top: -1em; }
263
+ div.search form input { margin-right: .25em; }
264
+ .small { font-size: 70%; }
265
+ .tiny { font-size: 60%; }
266
+ .totals { font-size: 60%; margin-left: .25em; vertical-align: super; }
267
+ .field_labels { font-size: 60%; margin-left: 1em; vertical-align: super; }
268
+ h2 {color: #CC6600; padding: 0; margin-bottom: .15em; font-size: 160%;}
269
+ h3.header { padding:0; margin:0; position: relative; top: -2.8em;
270
+ padding-bottom: .25em; padding-right: 5em; font-size: 80%; }
271
+ h1.header a { color: #FF9900; text-decoration: none;
272
+ font: bold 250% "Trebuchet MS",Trebuchet,Georgia, Serif;
273
+ letter-spacing:-4px; }
274
+
275
+ div.pagination { text-align: center; }
276
+ ul.pages { list-style: none; padding: 0; display: inline;}
277
+ ul.pages li { display: inline; }
278
+ form.controls { text-align: right; }
279
+ ul.undecorated { list-style: none; padding-left: 1em; margin-bottom: 5em;}
280
+ .content { padding-left: 2em; padding-right: 2em; }
281
+ table { padding: 0; background-color: #CCEECC; font-size: 75%;
282
+ width: 100%; border: 1px solid black; margin: 1em; margin-left: auto; margin-right: auto; }
283
+ table.obj tr.controls { text-align: right; font-size: 100%; background-color: #AACCAA; }
284
+ table.obj td.label { width: 7em; padding-left: .25em; border-right: 1px solid black; }
285
+ table.obj td.value input { width: 80%; margin: .35em; }
286
+ input.button { width: 5em; margin-left: .5em; }
287
+ table.add tr.controls td { padding: .5em; font-size: 100%; background-color: #AACCAA; }
288
+ table.add td { width: 10%; }
289
+ table.add td.value { width: 80%; }
290
+ table.add td.value input { width: 100%; margin: .35em; }
291
+ }
292
+ end
293
+ end
294
+ end
295
+
296
+ module DublinCore::Helpers
297
+
298
+ def paginate(klass, term = nil)
299
+ @total_pages = count/DublinCore::LIMIT + 1
300
+ div.pagination do
301
+ p "#{@page} of #{@total_pages} pages"
302
+ ul.pages do
303
+ li { link_if("<<", klass, term, 1) }
304
+ li { link_if("<", klass, term, @page - 1) }
305
+ page_window.each do |page|
306
+ li { link_if("#{page}", klass, term, page) }
307
+ end
308
+ li { link_if(">", klass, term, @page + 1) }
309
+ li { link_if(">>", klass, term, @total_pages) }
310
+ end
311
+ end
312
+ end
313
+
314
+ private
315
+
316
+ def link_if(string, klass, term, page)
317
+ return "#{string} " if (@page == page || 1 > page || page > @total_pages)
318
+ a(string, :href => term.nil? ? R(klass, page) : R(klass, term, page)) << " "
319
+ end
320
+
321
+ def page_window
322
+ return 1..@total_pages if @total_pages < 9
323
+ size = @total_pages > 9 ? 9 : @total_pages
324
+ start = @page - size/2 > 0 ? @page - size/2 : 1
325
+ start = @total_pages - size if start+size > @total_pages
326
+ start..start+size
327
+ end
328
+
329
+ end
330
+
331
+ module DublinCore::Views
332
+
333
+ def layout
334
+ html do
335
+ head do
336
+ title "Dublin Core - Simple Asset Cataloger"
337
+ link :rel => 'stylesheet', :type => 'text/css',
338
+ :href => '/styles.css', :media => 'screen'
339
+ end
340
+ body do
341
+ h1.header { a 'Nugget Explorer', :href => R(Index) }
342
+ h3.header { "exposing ugly metadata" }
343
+ div.search do
344
+ form({:method => 'get', :action => R(Search)}) do
345
+ input :name => 'terms', :type => 'text'
346
+ input.button :type => :submit, :value => 'Search'
347
+ end
348
+ end
349
+ a("Home", :href => R(Index)) unless @home
350
+ div.content do
351
+ self << yield
352
+ end
353
+ end
354
+ end
355
+ end
356
+
357
+ def browse
358
+ if @browse.empty?
359
+ p 'No objects found, try adding one.'
360
+ else
361
+ h3 "Browsing" << (" '#{@field}'" if @field).to_s
362
+ ul.undecorated do
363
+ @browse.keys.sort.each do |key|
364
+ li { _key_value(key, @browse[key]) }
365
+ end
366
+ end
367
+ paginate(Index, @field) if @count > DublinCore::LIMIT
368
+ end
369
+ end
370
+
371
+ def delete_success
372
+ p "Delete was successful"
373
+ end
374
+
375
+ def search
376
+ p.results { span "#{count} results for '#{@state.terms}'"; span.tiny "(#{@search_time} secs)" }
377
+ ul.undecorated do
378
+ @result.keys.sort.each do |record|
379
+ li do
380
+ a(record.value, :href => R(LinkedTo, record.id))
381
+ span.totals "(#{@result[record]})"
382
+ span.field_labels "#{record.field_type.sub(/^DC/, '').downcase} "
383
+ end
384
+ end
385
+ end
386
+ paginate(Search) if @count > DublinCore::LIMIT
387
+ end
388
+
389
+ def edit
390
+ h3 "Editing Record"
391
+ p "To remove a field entry, just remove it's content."
392
+ _form(@obj, :action => R(Edit, @obj))
393
+ end
394
+
395
+ def records
396
+ @objs.each { |obj| _obj(obj) }
397
+ paginate(LinkedTo, @field) if @count > DublinCore::LIMIT
398
+ end
399
+
400
+ def _obj(obj, edit = false)
401
+ table.obj :cellspacing => 0 do
402
+ _edit_controls(obj, edit)
403
+ DublinCore::FIELDS.each do |field|
404
+ obj.send(field.pluralize.intern).each_with_index do |value, index|
405
+ tr do
406
+ td.label { 0 == index ? "#{field}(s)" : "&nbsp;" }
407
+ if edit
408
+ td.value do
409
+ input :name => value.class,
410
+ :type => 'text',
411
+ :value => value.to_s
412
+ end
413
+ else
414
+ td.value { a.stealthy(value, :href => R(LinkedTo, value.id)) }
415
+ end
416
+ end
417
+ end
418
+ end
419
+ end
420
+ end
421
+
422
+ def _form(obj, action)
423
+ form.controls(:method => 'post', :action => R(Edit)) do
424
+ input :type => 'hidden', :name => 'obj_id', :value => obj.id
425
+ _obj(obj, true)
426
+ input.button :type => :submit, :name => 'action', :value => 'Save'
427
+ input.button :type => :submit, :name => 'action', :value => 'Discard'
428
+ end
429
+ form(:method => 'post', :action => R(DataAdd)) do
430
+ input :type => 'hidden', :name => 'obj_id', :value => obj.id
431
+ table.add :cellspacing => 0 do
432
+ tr.controls do
433
+ td(:colspan => 3) { "Add an entry. (All changes above will be lost, so save them first)" }
434
+ end
435
+ tr do
436
+ td do
437
+ select(:name => 'field_type') do
438
+ DublinCore::FIELDS.each do |field|
439
+ option field, :value => "DC#{field.capitalize}"
440
+ end
441
+ end
442
+ end
443
+ td.value { input :name => 'field_value', :type => 'text' }
444
+ td { input.button :type => 'submit', :value => 'Add' }
445
+ end
446
+ end
447
+ end
448
+ end
449
+
450
+ def _edit_controls(obj, edit)
451
+ tr.controls do
452
+ td :colspan => 2 do
453
+ edit ? input(:type => 'submit', :name => 'action', :value => 'Delete') :
454
+ a('edit', :href => R(Edit, obj))
455
+ end
456
+ end
457
+ end
458
+
459
+
460
+ def _key_value(key, value)
461
+ if value > 0
462
+ if key.kind_of?(DublinCore::Models::Field)
463
+ a(key, :href => R(LinkedTo, key.id))
464
+ else
465
+ a(key.to_s, :href => R(Index, key))
466
+ end
467
+ span.totals "(#{value})"
468
+ else
469
+ span key
470
+ span.totals "(#{value})"
471
+ end
472
+ end
473
+
474
+ end
@@ -0,0 +1,15 @@
1
+ module OAI
2
+ class GetRecordResponse < Response
3
+ include OAI::XPath
4
+ attr_accessor :record
5
+
6
+ def initialize(doc)
7
+ super doc
8
+ @record = OAI::Record.new(xpath_first(doc, './/GetRecord/record'))
9
+ end
10
+
11
+ def deleted?
12
+ return @record.deleted?
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ module OAI
2
+ class Header
3
+ include OAI::XPath
4
+ attr_accessor :status, :identifier, :datestamp, :set_spec
5
+
6
+ def initialize(element)
7
+ @status = get_attribute(element, 'status')
8
+ @identifier = xpath(element, './/identifier')
9
+ @datestamp = xpath(element, './/datestamp')
10
+ @set_spec = xpath_all(element, './/setSpec')
11
+ end
12
+
13
+ def deleted?
14
+ return true if @status.to_s == "deleted"
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,30 @@
1
+ module OAI
2
+ class IdentifyResponse < Response
3
+ include OAI::XPath
4
+ attr_accessor :repository_name, :base_url, :protocol, :admin_email,
5
+ :earliest_datestamp, :deleted_record, :granularity, :compression
6
+
7
+ def initialize(doc)
8
+ super doc
9
+ @repository_name = xpath(doc, './/Identify/repositoryName')
10
+ @base_url = xpath(doc, './/Identify/baseURL')
11
+ @protocol = xpath(doc, './/Identify/protocol')
12
+ @admin_email = xpath(doc, './/Identify/adminEmail')
13
+ @earliest_datestamp = xpath(doc, './/Identify/earliestDatestamp')
14
+ @deleted_record = xpath(doc, './/Identify/deletedRecord')
15
+ @granularity = xpath(doc, './/Identify/granularity')
16
+ @compression = xpath(doc, '..//Identify/compression')
17
+ end
18
+
19
+ def to_s
20
+ return "#{@repository_name} [#{@base_url}]"
21
+ end
22
+
23
+ # returns REXML::Element nodes for each description section
24
+ # if the OAI::Client was configured to use libxml then you will
25
+ # instead get a LibXML::XML::Node object.
26
+ def descriptions
27
+ return xpath_all(doc, './/Identify/description')
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ module OAI
2
+ class ListIdentifiersResponse < Response
3
+ include Enumerable
4
+ include OAI::XPath
5
+
6
+ def each
7
+ for header_element in xpath_all(@doc, './/ListIdentifiers/header')
8
+ yield OAI::Header.new(header_element)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module OAI
2
+ class ListMetadataFormatsResponse < Response
3
+ include Enumerable
4
+ include OAI::XPath
5
+
6
+ def each
7
+ for format in xpath_all(@doc, './/metadataFormat')
8
+ yield MetadataFormat.new(format)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,21 @@
1
+ module OAI
2
+
3
+ # allows for iteration across a list of records
4
+ #
5
+ # for record in client.list_records :metadata_prefix => 'oai_dc':
6
+ # puts record.metadata
7
+ # end
8
+ #
9
+ # you'll need to handle resumption tokens
10
+
11
+ class ListRecordsResponse < Response
12
+ include OAI::XPath
13
+ include Enumerable
14
+
15
+ def each
16
+ for record_element in xpath_all(@doc, './/ListRecords/record')
17
+ yield OAI::Record.new(record_element)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ module OAI
2
+
3
+ # allows for iteration of the sets found in a oai-pmh server
4
+ #
5
+ # for set in client.list_sets
6
+ # puts set
7
+ # end
8
+
9
+ class ListSetsResponse < Response
10
+ include OAI::XPath
11
+ include Enumerable
12
+
13
+ def each
14
+ for set_element in xpath_all(@doc, './/set')
15
+ yield OAI::Set.parse(set_element)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ module OAI
2
+ class MetadataFormat
3
+ include OAI::XPath
4
+ attr_accessor :prefix, :schema, :namespace
5
+
6
+ def initialize(element)
7
+ @prefix = xpath(element, './/metadataPrefix')
8
+ @schema = xpath(element, './/schema')
9
+ @namespace = xpath(element, './/metadataNamespace')
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,26 @@
1
+ module OAI
2
+
3
+ # A class for representing a Record as returned from a GetRecord
4
+ # or ListRecords request. Each record will have a header and metadata
5
+ # attribute. The header is a OAI::Header object and the metadata is
6
+ # a REXML::Element object for that chunk of XML.
7
+ #
8
+ # Note: if your OAI::Client was configured to use the 'libxml' parser
9
+ # metadata will return a XML::Node object instead.
10
+
11
+ class Record
12
+ include OAI::XPath
13
+ attr_accessor :header, :metadata
14
+
15
+ def initialize(element)
16
+ @header = OAI::Header.new xpath_first(element, './/header')
17
+ @metadata = xpath_first(element, './/metadata')
18
+ end
19
+
20
+ # a convenience method which digs into the header status attribute
21
+ # and returns true if the value is set to 'deleted'
22
+ def deleted?
23
+ return @header.deleted?
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,35 @@
1
+ module OAI
2
+ class Response
3
+ include OAI::XPath
4
+ attr_reader :doc, :resumption_token
5
+
6
+ def initialize(doc)
7
+ @doc = doc
8
+ @resumption_token = xpath(doc, './/resumptionToken')
9
+
10
+ # throw an exception if there was an error
11
+ error = xpath_first(doc, './/error')
12
+ return unless error
13
+
14
+ case error.class.to_s
15
+ when 'REXML::Element'
16
+ message = error.text
17
+ code = error.attributes['code']
18
+ when 'LibXML::XML::Node'
19
+ message = error.content
20
+ code = ""
21
+ if defined?(error.property) == nil
22
+ code = error.attributes['code']
23
+ else
24
+ begin
25
+ code = error["code"]
26
+ rescue
27
+ code = error.property('code')
28
+ end
29
+ end
30
+ end
31
+ raise OAI::Exception.new(message, code)
32
+ end
33
+
34
+ end
35
+ end