oai_talia 0.0.13
Sign up to get free protection for your applications and to get access to all the features.
- 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
|