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.
- data/README +81 -0
- data/Rakefile +127 -0
- data/bin/oai +68 -0
- data/examples/models/file_model.rb +63 -0
- data/examples/providers/dublin_core.rb +474 -0
- data/lib/oai/client/get_record.rb +15 -0
- data/lib/oai/client/header.rb +18 -0
- data/lib/oai/client/identify.rb +30 -0
- data/lib/oai/client/list_identifiers.rb +12 -0
- data/lib/oai/client/list_metadata_formats.rb +12 -0
- data/lib/oai/client/list_records.rb +21 -0
- data/lib/oai/client/list_sets.rb +19 -0
- data/lib/oai/client/metadata_format.rb +12 -0
- data/lib/oai/client/record.rb +26 -0
- data/lib/oai/client/response.rb +35 -0
- data/lib/oai/client.rb +301 -0
- data/lib/oai/constants.rb +34 -0
- data/lib/oai/exception.rb +75 -0
- data/lib/oai/harvester/config.rb +41 -0
- data/lib/oai/harvester/harvest.rb +150 -0
- data/lib/oai/harvester/logging.rb +70 -0
- data/lib/oai/harvester/mailer.rb +17 -0
- data/lib/oai/harvester/shell.rb +338 -0
- data/lib/oai/harvester.rb +39 -0
- data/lib/oai/provider/metadata_format/oai_dc.rb +29 -0
- data/lib/oai/provider/metadata_format/oai_europeana.rb +38 -0
- data/lib/oai/provider/metadata_format.rb +143 -0
- data/lib/oai/provider/model/activerecord_caching_wrapper.rb +134 -0
- data/lib/oai/provider/model/activerecord_wrapper.rb +139 -0
- data/lib/oai/provider/model.rb +74 -0
- data/lib/oai/provider/partial_result.rb +18 -0
- data/lib/oai/provider/response/error.rb +16 -0
- data/lib/oai/provider/response/get_record.rb +26 -0
- data/lib/oai/provider/response/identify.rb +25 -0
- data/lib/oai/provider/response/list_identifiers.rb +35 -0
- data/lib/oai/provider/response/list_metadata_formats.rb +34 -0
- data/lib/oai/provider/response/list_records.rb +34 -0
- data/lib/oai/provider/response/list_sets.rb +23 -0
- data/lib/oai/provider/response/record_response.rb +70 -0
- data/lib/oai/provider/response.rb +161 -0
- data/lib/oai/provider/resumption_token.rb +106 -0
- data/lib/oai/provider.rb +304 -0
- data/lib/oai/set.rb +29 -0
- data/lib/oai/xpath.rb +75 -0
- data/lib/oai.rb +8 -0
- data/lib/test.rb +25 -0
- data/test/activerecord_provider/config/connection.rb +5 -0
- data/test/activerecord_provider/config/database.yml +6 -0
- data/test/activerecord_provider/database/ar_migration.rb +59 -0
- data/test/activerecord_provider/database/oaipmhtest +0 -0
- data/test/activerecord_provider/fixtures/dc.yml +1501 -0
- data/test/activerecord_provider/helpers/providers.rb +44 -0
- data/test/activerecord_provider/helpers/set_provider.rb +36 -0
- data/test/activerecord_provider/models/dc_field.rb +7 -0
- data/test/activerecord_provider/models/dc_set.rb +6 -0
- data/test/activerecord_provider/models/oai_token.rb +3 -0
- data/test/activerecord_provider/tc_ar_provider.rb +113 -0
- data/test/activerecord_provider/tc_ar_sets_provider.rb +72 -0
- data/test/activerecord_provider/tc_caching_paging_provider.rb +55 -0
- data/test/activerecord_provider/tc_simple_paging_provider.rb +57 -0
- data/test/activerecord_provider/test_helper.rb +4 -0
- data/test/client/helpers/provider.rb +68 -0
- data/test/client/helpers/test_wrapper.rb +11 -0
- data/test/client/tc_exception.rb +36 -0
- data/test/client/tc_get_record.rb +37 -0
- data/test/client/tc_identify.rb +13 -0
- data/test/client/tc_libxml.rb +61 -0
- data/test/client/tc_list_identifiers.rb +52 -0
- data/test/client/tc_list_metadata_formats.rb +18 -0
- data/test/client/tc_list_records.rb +13 -0
- data/test/client/tc_list_sets.rb +19 -0
- data/test/client/tc_low_resolution_dates.rb +14 -0
- data/test/client/tc_utf8_escaping.rb +11 -0
- data/test/client/tc_xpath.rb +26 -0
- data/test/client/test_helper.rb +5 -0
- data/test/provider/models.rb +234 -0
- data/test/provider/tc_exceptions.rb +96 -0
- data/test/provider/tc_functional_tokens.rb +43 -0
- data/test/provider/tc_provider.rb +71 -0
- data/test/provider/tc_resumption_tokens.rb +46 -0
- data/test/provider/tc_simple_provider.rb +92 -0
- data/test/provider/test_helper.rb +36 -0
- data/test/test.xml +22 -0
- 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)" : " " }
|
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,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
|