openurl 0.0.1 → 0.1.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.
- data/README +47 -0
- data/lib/openurl.rb +7 -0
- data/lib/openurl/context_object.rb +364 -284
- data/lib/openurl/context_object_entity.rb +131 -124
- data/lib/openurl/metadata_formats/book.rb +36 -0
- data/lib/openurl/metadata_formats/dissertation.rb +36 -0
- data/lib/openurl/metadata_formats/dublin_core.rb +114 -0
- data/lib/openurl/metadata_formats/journal.rb +36 -0
- data/lib/openurl/metadata_formats/marc.rb +70 -0
- data/lib/openurl/metadata_formats/patent.rb +152 -0
- data/lib/openurl/metadata_formats/scholarly_common.rb +198 -0
- data/lib/openurl/metadata_formats/scholarly_service_type.rb +41 -0
- data/lib/openurl/transport.rb +9 -4
- data/test/context_object_entity_test.rb +41 -0
- data/test/context_object_test.rb +509 -0
- data/test/data/dc_ctx.xml +27 -0
- data/test/data/marc_ctx.xml +101 -0
- data/test/data/metalib_sap2_post_params.yml +1 -0
- data/test/data/scholarly_au_ctx.xml +1 -0
- data/test/data/yu.xml +50 -0
- data/test/test.yml +25 -0
- metadata +70 -35
data/README
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
== openurl
|
2
|
+
|
3
|
+
== DESCRIPTION
|
4
|
+
|
5
|
+
openurl is a Ruby library creating, parsing and using NISO Z39.88 OpenURLs over
|
6
|
+
HTTP. <http://openurl.info/registry>
|
7
|
+
|
8
|
+
While openurl can send requests to OpenURL 1.0 resolvers, there is no 'standard'
|
9
|
+
response format, so parsing the returned value is up to you.
|
10
|
+
|
11
|
+
== USAGE
|
12
|
+
|
13
|
+
require 'openurl'
|
14
|
+
|
15
|
+
# Create your context object
|
16
|
+
context_object = OpenURL::ContextObject.new
|
17
|
+
|
18
|
+
# Add metadata to the Context Object Entities
|
19
|
+
context_object.referent.set_format('journal')
|
20
|
+
context_object.referent.add_identifier('info:doi/10.1016/j.ipm.2005.03.024')
|
21
|
+
context_object.referent.set_metadata('issn', '0306-4573')
|
22
|
+
context_object.referent.set_metadata('aulast', 'Bollen')
|
23
|
+
context_object.referrer.add_identifier('info:sid/google')
|
24
|
+
|
25
|
+
puts context_object.kev
|
26
|
+
|
27
|
+
puts context_object.xml
|
28
|
+
|
29
|
+
# Send the context object to an OpenURL link resolver
|
30
|
+
transport = OpenURL::Transport.new('http://demo.exlibrisgroup.com:9003/lr_3', context_object)
|
31
|
+
transport.get
|
32
|
+
puts tranport.response
|
33
|
+
|
34
|
+
|
35
|
+
== INSTALLATION
|
36
|
+
|
37
|
+
You should be able to install the gem:
|
38
|
+
|
39
|
+
gem install openurl
|
40
|
+
|
41
|
+
The source lives in subversion:
|
42
|
+
|
43
|
+
http://openurl.rubyforge.org/svn/
|
44
|
+
|
45
|
+
== CONTACT
|
46
|
+
|
47
|
+
Bugs and suggestions to Ross Singer <rossfsinger@gmail.com>
|
data/lib/openurl.rb
CHANGED
@@ -2,9 +2,16 @@
|
|
2
2
|
# Can also work with OpenURL 0.1, but your YMMV
|
3
3
|
# See: http://alcme.oclc.org/openurl/docs/implementation_guidelines/
|
4
4
|
# for more information on implementing NISO Z39.88
|
5
|
+
|
5
6
|
require 'date'
|
6
7
|
require 'rexml/document'
|
7
8
|
require 'cgi'
|
8
9
|
require 'openurl/context_object'
|
9
10
|
require 'openurl/context_object_entity'
|
10
11
|
require 'openurl/transport'
|
12
|
+
|
13
|
+
Dir.open(File.dirname(File.expand_path(__FILE__))+'/openurl/metadata_formats').each do | file |
|
14
|
+
next if file.match(/^\./)
|
15
|
+
class_name = file.sub(/\.rb$/,'')
|
16
|
+
require "openurl/metadata_formats/#{class_name}"
|
17
|
+
end
|
@@ -1,35 +1,53 @@
|
|
1
1
|
module OpenURL
|
2
|
-
|
2
|
+
|
3
|
+
|
4
|
+
require 'jcode'
|
5
|
+
|
6
|
+
##
|
3
7
|
# The ContextObject class is intended to both create new OpenURL 1.0 context
|
4
|
-
# objects or parse existing ones, either from Key-Encoded Values (KEVs) or
|
5
|
-
#
|
8
|
+
# objects or parse existing ones, either from Key-Encoded Values (KEVs) or
|
9
|
+
# XML.
|
10
|
+
# == Create a new ContextObject programmatically
|
6
11
|
# require 'openurl/context_object'
|
7
12
|
# include OpenURL
|
13
|
+
#
|
8
14
|
# ctx = ContextObject.new
|
9
|
-
# ctx.referent.set_format('journal')
|
15
|
+
# ctx.referent.set_format('journal') # important to do this FIRST.
|
16
|
+
#
|
10
17
|
# ctx.referent.add_identifier('info:doi/10.1016/j.ipm.2005.03.024')
|
11
18
|
# ctx.referent.set_metadata('issn', '0306-4573')
|
12
19
|
# ctx.referent.set_metadata('aulast', 'Bollen')
|
13
20
|
# ctx.referrer.add_identifier('info:sid/google')
|
14
21
|
# puts ctx.kev
|
15
22
|
# # url_ver=Z39.88-2004&ctx_tim=2007-10-29T12%3A18%3A53-0400&ctx_ver=Z39.88-2004&ctx_enc=info%3Aofi%2Fenc%3AUTF-8&ctx_id=&rft.issn=0306-4573&rft.aulast=Bollen&rft_val_fmt=info%3Aofi%2Ffmt%3Axml%3Axsd%3Ajournal&rft_id=info%3Adoi%2F10.1016%2Fj.ipm.2005.03.024&rfr_id=info%3Asid%2Fgoogle
|
16
|
-
|
23
|
+
#
|
24
|
+
# == Create a new ContextObject from an existing kev or XML serialization:
|
25
|
+
#
|
26
|
+
# ContextObject.new_from_kev( kev_context_object )
|
27
|
+
# ContextObject.new_from_xml( xml_context_object ) # Can be String or REXML::Document
|
28
|
+
#
|
29
|
+
# == Serialize a ContextObject to kev or XML :
|
30
|
+
# ctx.kev
|
31
|
+
# ctx.xml
|
17
32
|
class ContextObject
|
18
33
|
|
19
|
-
|
20
|
-
|
34
|
+
attr_reader :admin, :referent, :referringEntity, :requestor, :referrer,
|
35
|
+
:serviceType, :resolver
|
36
|
+
attr_accessor :foreign_keys
|
37
|
+
|
21
38
|
@@defined_entities = {"rft"=>"referent", "rfr"=>"referrer", "rfe"=>"referring-entity", "req"=>"requestor", "svc"=>"service-type", "res"=>"resolver"}
|
22
39
|
|
23
40
|
# Creates a new ContextObject object and initializes the ContextObjectEntities.
|
24
41
|
|
25
|
-
def initialize()
|
26
|
-
@referent =
|
27
|
-
@
|
28
|
-
@
|
29
|
-
@
|
30
|
-
@serviceType = [
|
31
|
-
@resolver = [
|
32
|
-
@
|
42
|
+
def initialize()
|
43
|
+
@referent = ContextObjectEntity.new
|
44
|
+
@referrer = ContextObjectEntity.new
|
45
|
+
@referringEntity = ContextObjectEntity.new
|
46
|
+
@requestor = ContextObjectEntity.new
|
47
|
+
@serviceType = []
|
48
|
+
@resolver = []
|
49
|
+
@foreign_keys = {}
|
50
|
+
$KCODE='UTF-8'
|
33
51
|
@admin = {"ctx_ver"=>{"label"=>"version", "value"=>"Z39.88-2004"}, "ctx_tim"=>{"label"=>"timestamp", "value"=>DateTime.now().to_s}, "ctx_id"=>{"label"=>"identifier", "value"=>""}, "ctx_enc"=>{"label"=>"encoding", "value"=>"info:ofi/enc:UTF-8"}}
|
34
52
|
end
|
35
53
|
|
@@ -49,34 +67,36 @@ module OpenURL
|
|
49
67
|
coContainer.add_attribute("xsi:schemaLocation", "info:ofi/fmt:xml:xsd:ctx http://www.openurl.info/registry/docs/info:ofi/fmt:xml:xsd:ctx")
|
50
68
|
co = coContainer.add_element "ctx:context-object"
|
51
69
|
@admin.each_key do |k|
|
70
|
+
next if k == "ctx_enc"
|
52
71
|
co.add_attribute(@admin[k]["label"], @admin[k]["value"])
|
53
72
|
end
|
54
73
|
|
55
|
-
[@referent,
|
56
|
-
|
74
|
+
[{@referent=>"rft"},
|
75
|
+
{@referringEntity=>"rfe"}, {@requestor=>"req"},
|
76
|
+
{@referrer=>"rfr"}].each do | entity |
|
77
|
+
|
78
|
+
entity.each do | ent, label |
|
79
|
+
ent.xml(co, label) unless ent.empty?
|
80
|
+
end
|
57
81
|
end
|
58
82
|
|
59
|
-
[@serviceType, @resolver
|
60
|
-
|
61
|
-
|
83
|
+
[{@serviceType=>"svc"}, {@resolver=>"res"}].each do |entity|
|
84
|
+
entity.each do | entCont, label |
|
85
|
+
entCont.each do |ent|
|
86
|
+
ent.xml(co, label) unless ent.empty?
|
87
|
+
end
|
62
88
|
end
|
63
89
|
end
|
64
90
|
|
65
91
|
return doc.to_s
|
66
92
|
end
|
67
93
|
|
68
|
-
# Alias for .xml
|
69
|
-
|
70
|
-
def sap2
|
71
|
-
return xml
|
72
|
-
end
|
73
94
|
|
74
95
|
# Output the ContextObject as a Key-encoded value string. Pass a boolean
|
75
96
|
# true argument if you do not want the ctx_tim key included.
|
76
97
|
|
77
98
|
def kev(no_date=false)
|
78
|
-
|
79
|
-
kevs = ["url_ver=Z39.88-2004"]
|
99
|
+
kevs = ["url_ver=Z39.88-2004", "url_ctx_fmt=#{CGI.escape("info:ofi/fmt:kev:mtx:ctx")}"]
|
80
100
|
|
81
101
|
# Loop through the administrative metadata
|
82
102
|
@admin.each_key do |k|
|
@@ -84,44 +104,45 @@ module OpenURL
|
|
84
104
|
kevs.push(k+"="+CGI.escape(@admin[k]["value"].to_s)) if @admin[k]["value"]
|
85
105
|
end
|
86
106
|
|
87
|
-
|
88
|
-
kevs.push(ent.kev) unless ent.empty?
|
107
|
+
{@referent=>"rft", @referringEntity=>"rfe", @requestor=>"req", @referrer=>"rfr"}.each do | ent, abbr |
|
108
|
+
kevs.push(ent.kev(abbr)) unless ent.empty?
|
89
109
|
end
|
90
110
|
|
91
|
-
|
111
|
+
{@serviceType=>"svc", @resolver=>"res"}.each do |entCont, abbr|
|
92
112
|
entCont.each do |ent|
|
93
|
-
|
113
|
+
next if ent.empty?
|
114
|
+
kevs.push(ent.kev(abbr))
|
94
115
|
end
|
95
116
|
end
|
96
117
|
return kevs.join("&")
|
97
118
|
end
|
98
119
|
|
99
|
-
# Outputs the ContextObject as a ruby hash.
|
100
|
-
|
101
|
-
|
102
|
-
|
120
|
+
# Outputs the ContextObject as a ruby hash---hash version of the kev format.
|
121
|
+
# Outputting a context object as a hash
|
122
|
+
# is imperfect, because context objects can have multiple elements
|
123
|
+
# with the same key--and because some keys depend on SAP1 vs SAP2.
|
124
|
+
# So this function is really deprecated, but here because we have so much
|
125
|
+
# code dependent on it.
|
126
|
+
def to_hash
|
127
|
+
co_hash = {"url_ver"=>"Z39.88-2004", "url_ctx_fmt"=>"info:ofi/fmt:kev:mtx:ctx"}
|
103
128
|
|
104
129
|
@admin.each_key do |k|
|
105
130
|
co_hash[k]=@admin[k]["value"] if @admin[k]["value"]
|
106
131
|
end
|
107
132
|
|
108
|
-
|
109
|
-
co_hash.merge!(ent.to_hash) unless ent.empty?
|
133
|
+
{@referent=>"rft", @referringEntity=>"rfe", @requestor=>"req", @referrer=>"rfr"}.each do | ent, abbr |
|
134
|
+
co_hash.merge!(ent.to_hash(abbr)) unless ent.empty?
|
110
135
|
end
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
136
|
+
|
137
|
+
# svc and res are arrays of ContextObjectEntity
|
138
|
+
{@serviceType=>"svc", @resolver=>"res"}.each do |ent_list, abbr|
|
139
|
+
ent_list.each do |ent|
|
140
|
+
co_hash.merge!(ent.to_hash(abbr)) unless ent.empty?
|
115
141
|
end
|
116
142
|
end
|
117
143
|
return co_hash
|
118
144
|
end
|
119
145
|
|
120
|
-
# Alias for .kev
|
121
|
-
|
122
|
-
def sap1
|
123
|
-
return kev
|
124
|
-
end
|
125
146
|
|
126
147
|
# Outputs a COinS (ContextObject in SPANS) span tag for the ContextObject.
|
127
148
|
# Arguments are any other CSS classes you want included and the innerHTML
|
@@ -130,36 +151,7 @@ module OpenURL
|
|
130
151
|
def coins (classnames=nil, innerHTML=nil)
|
131
152
|
return "<span class='Z3988 #{classnames}' title='"+CGI.escapeHTML(self.kev(true))+"'>#{innerHTML}</span>"
|
132
153
|
end
|
133
|
-
|
134
|
-
# Adds another ServiceType entity to the context object and returns the
|
135
|
-
# array index of the new object.
|
136
|
-
|
137
|
-
def add_service_type_entity
|
138
|
-
@serviceType << ServiceTypeEntity.new
|
139
|
-
return @serviceType.index(@serviceType.last)
|
140
|
-
end
|
141
|
-
|
142
|
-
# Adds another Resolver entity to the context object and returns the
|
143
|
-
# array index of the new object.
|
144
|
-
|
145
|
-
def add_resolver_entity
|
146
|
-
@resolver << ResolverEntity.new
|
147
|
-
return @resolver.index(@resolver.last)
|
148
|
-
end
|
149
|
-
|
150
|
-
# Adds a custom entity to the ContextObject and returns array index of the
|
151
|
-
# new object. Expects an abbreviation and label for KEV and XML output.
|
152
|
-
|
153
|
-
def add_custom_entity(abbr=nil, label=nil)
|
154
|
-
@custom << CustomEntity.new(abbr, label)
|
155
|
-
return @custom.index(@custom.last)
|
156
|
-
end
|
157
|
-
|
158
|
-
# Returns the appropriate CustomEntity for the given entity abbreviation.
|
159
|
-
|
160
|
-
def custom_entity(abbr)
|
161
|
-
return @custom.find { |c| c.abbr == abbr }
|
162
|
-
end
|
154
|
+
|
163
155
|
|
164
156
|
# Sets a ContextObject administration field.
|
165
157
|
|
@@ -174,9 +166,14 @@ module OpenURL
|
|
174
166
|
def import_kev(kev)
|
175
167
|
co = CGI::parse(kev)
|
176
168
|
co2 = {}
|
177
|
-
co.
|
178
|
-
|
179
|
-
|
169
|
+
co.each do |key, val|
|
170
|
+
if val.is_a?(Array)
|
171
|
+
if val.length == 1
|
172
|
+
co2[key] = val[0]
|
173
|
+
else
|
174
|
+
co2[key] = val
|
175
|
+
end
|
176
|
+
end
|
180
177
|
end
|
181
178
|
self.import_hash(co2)
|
182
179
|
end
|
@@ -187,29 +184,73 @@ module OpenURL
|
|
187
184
|
co = self.new
|
188
185
|
co.import_kev(kev)
|
189
186
|
return co
|
190
|
-
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Initialize a new ContextObject object from a CGI.params style hash
|
190
|
+
# Expects a hash with default value being nil though, not [] as CGI.params
|
191
|
+
# actually returns, beware. Can also accept a Rails-style params hash
|
192
|
+
# (single string values, not array values), although this may lose
|
193
|
+
# some context object information.
|
194
|
+
def self.new_from_form_vars(params)
|
195
|
+
co = self.new
|
196
|
+
if ctx_val = (params[:url_ctx_val]||params["url_ctx_val"]) and not ctx_val.empty? # this is where the context object stuff will be
|
197
|
+
co.admin.keys.each do | adm |
|
198
|
+
if params[adm.to_s]
|
199
|
+
if params[adm.to_s].is_a?(Array)
|
200
|
+
co.set_administration_key(adm, params[adm.to_s].first)
|
201
|
+
else
|
202
|
+
co.set_administration_key(adm, params[adm.to_s])
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
if ctx_format = (params["url_ctx_fmt"]||params[:url_ctx_fmt])
|
208
|
+
ctx_format = ctx_format.first if ctx_format.is_a?(Array)
|
209
|
+
ctx_val = ctx_val.first if ctx_val.is_a?(Array)
|
210
|
+
if ctx_format == "info:ofi/fmt:xml:xsd:ctx"
|
211
|
+
co.import_xml(ctx_val)
|
212
|
+
elsif ctx_format == "info:ofi/fmt:kev:mtx:ctx"
|
213
|
+
co.import_kev(ctx_val)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
else # we'll assume this is standard inline kev
|
217
|
+
co.import_hash(params)
|
218
|
+
end
|
219
|
+
return co
|
220
|
+
end
|
191
221
|
|
192
222
|
# Imports an existing XML encoded context object and sets the appropriate
|
193
223
|
# entities
|
194
224
|
|
195
225
|
def import_xml(xml)
|
196
|
-
|
226
|
+
if xml.is_a?(String)
|
227
|
+
doc = REXML::Document.new xml.gsub(/>[\s\t]*\n*[\s\t]*</, '><').strip
|
228
|
+
elsif xml.is_a?(REXML::Document)
|
229
|
+
doc = xml
|
230
|
+
else
|
231
|
+
raise ArgumentError, "Argument must be an REXML::Document or well-formed XML string"
|
232
|
+
end
|
233
|
+
|
197
234
|
# Cut to the context object
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
235
|
+
ctx = REXML::XPath.first(doc, ".//ctx:context-object", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"})
|
236
|
+
|
237
|
+
|
238
|
+
|
239
|
+
|
240
|
+
|
241
|
+
|
242
|
+
ctx.attributes.each do |attr, val|
|
243
|
+
@admin.each do |adm, vals|
|
244
|
+
self.set_administration_key(adm, val) if vals["label"] == attr
|
245
|
+
end
|
246
|
+
end
|
247
|
+
ctx.to_a.each do | ent |
|
248
|
+
if @@defined_entities.value?(ent.name())
|
249
|
+
self.import_entity(ent)
|
250
|
+
else
|
251
|
+
self.import_custom_node(ent)
|
252
|
+
end
|
253
|
+
end
|
213
254
|
end
|
214
255
|
|
215
256
|
# Initialize a new ContextObject object from an existing XML ContextObject
|
@@ -220,44 +261,50 @@ module OpenURL
|
|
220
261
|
return co
|
221
262
|
end
|
222
263
|
|
223
|
-
# Searches the Custom Entities for the key/value pair and returns an array
|
224
|
-
# of the @custom array keys of any matches.
|
225
|
-
|
226
|
-
def search_custom_entities(key, val)
|
227
|
-
matches = []
|
228
|
-
@custom.each do |cus|
|
229
|
-
begin
|
230
|
-
matches << @custom.index(cus) if cus.instance_variable_get('@'+key) == val
|
231
|
-
rescue NameError
|
232
|
-
next
|
233
|
-
end
|
234
|
-
end
|
235
|
-
return matches
|
236
|
-
end
|
237
|
-
|
238
264
|
# Imports an existing hash of ContextObject values and sets the appropriate
|
239
265
|
# entities.
|
240
266
|
|
241
267
|
def import_hash(hash)
|
242
268
|
ref = {}
|
269
|
+
{"@referent"=>"rft", "@referrer"=>"rfr", "@referringEntity"=>"rfe",
|
270
|
+
"@requestor"=>"req"}.each do | ent, abbr |
|
271
|
+
next unless hash["#{abbr}_val_fmt"]
|
272
|
+
val = hash["#{abbr}_val_fmt"]
|
273
|
+
val = val[0] if val.is_a?(Array)
|
274
|
+
self.instance_variable_set(ent.to_sym, ContextObjectEntityFactory.format(val))
|
275
|
+
end
|
276
|
+
|
277
|
+
{"@serviceType"=>"svc","@resolver"=>"res"}.each do | ent, abbr |
|
278
|
+
next unless hash["#{abbr}_val_fmt"]
|
279
|
+
val = hash["#{abbr}_val_fmt"]
|
280
|
+
val = val[0] if val.is_a?(Array)
|
281
|
+
self.instance_variable_set(ent.to_sym, [ContextObjectEntityFactory.format(val)])
|
282
|
+
end
|
283
|
+
|
243
284
|
openurl_keys = ["url_ver", "url_tim", "url_ctx_fmt"]
|
244
|
-
hash.each do |key,
|
285
|
+
hash.each do |key, value|
|
286
|
+
val = value
|
287
|
+
val = value[0] if value.is_a?(Array)
|
288
|
+
|
289
|
+
next if value.nil? || value.empty?
|
290
|
+
|
245
291
|
if openurl_keys.include?(key)
|
246
292
|
next # None of these matter much for our purposes
|
247
|
-
elsif @admin.has_key?(key)
|
248
|
-
self.set_administration_key(key, val)
|
249
|
-
elsif key.
|
250
|
-
|
251
|
-
(entity, v, fmt) = key.split("_")
|
252
|
-
ent = self.translate_abbr(entity)
|
253
|
-
eval("@"+ent).set_format(val)
|
293
|
+
elsif @admin.has_key?(key)
|
294
|
+
self.set_administration_key(key, val)
|
295
|
+
elsif key.match(/^[a-z]{3}_val_fmt/)
|
296
|
+
next
|
254
297
|
elsif key.match(/^[a-z]{3}_ref/)
|
255
298
|
# determines if we have a by-reference context object
|
256
299
|
(entity, v, fmt) = key.split("_")
|
257
300
|
ent = self.translate_abbr(entity)
|
301
|
+
unless ent
|
302
|
+
self.foreign_keys[key] = val
|
303
|
+
next
|
304
|
+
end
|
258
305
|
# by-reference requires two fields, format and location, if this is
|
259
306
|
# the first field we've run across, set a place holder until we get
|
260
|
-
# the other value
|
307
|
+
# the other value
|
261
308
|
unless ref[entity]
|
262
309
|
if fmt
|
263
310
|
ref_key = "format"
|
@@ -276,11 +323,23 @@ module OpenURL
|
|
276
323
|
# Get the entity identifier
|
277
324
|
(entity, v) = key.split("_")
|
278
325
|
ent = self.translate_abbr(entity)
|
279
|
-
|
326
|
+
unless ent
|
327
|
+
self.foreign_keys[key] = val
|
328
|
+
next
|
329
|
+
end
|
330
|
+
# May or may not be an array, turn it into one.
|
331
|
+
[value].flatten.each do | id |
|
332
|
+
eval("@"+ent).add_identifier(id)
|
333
|
+
end
|
334
|
+
|
280
335
|
elsif key.match(/^[a-z]{3}_dat$/)
|
281
|
-
# Get any private data
|
336
|
+
# Get any private data
|
282
337
|
(entity, v) = key.split("_")
|
283
338
|
ent = self.translate_abbr(entity)
|
339
|
+
unless ent
|
340
|
+
self.foreign_keys[key] = val
|
341
|
+
next
|
342
|
+
end
|
284
343
|
eval("@"+ent).set_private_data(val)
|
285
344
|
else
|
286
345
|
# collect the entity metadata
|
@@ -288,12 +347,22 @@ module OpenURL
|
|
288
347
|
if keyparts.length > 1
|
289
348
|
# This is 1.0 OpenURL
|
290
349
|
ent = self.translate_abbr(keyparts[0])
|
350
|
+
unless ent
|
351
|
+
self.foreign_keys[key] = val
|
352
|
+
next
|
353
|
+
end
|
291
354
|
eval("@"+ent).set_metadata(keyparts[1], val)
|
292
355
|
else
|
293
356
|
# This is a 0.1 OpenURL. Your mileage may vary on how accurately
|
294
357
|
# this maps.
|
295
358
|
if key == 'id'
|
296
|
-
|
359
|
+
if value.is_a?(Array)
|
360
|
+
value.each do | id |
|
361
|
+
@referent.add_identifier(id)
|
362
|
+
end
|
363
|
+
else
|
364
|
+
@referent.add_identifier(val)
|
365
|
+
end
|
297
366
|
elsif key == 'sid'
|
298
367
|
@referrer.set_identifier("info:sid/"+val.to_s)
|
299
368
|
else
|
@@ -316,12 +385,12 @@ module OpenURL
|
|
316
385
|
# exist, just call it a journal since most 0.1 OpenURLs would be one,
|
317
386
|
# anyway.
|
318
387
|
unless @referent.format
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
388
|
+
fmt = case @referent.metadata['genre']
|
389
|
+
when /article|journal|issue|proceeding|conference|preprint/ then 'journal'
|
390
|
+
when /book|bookitem|report|document/ then 'book'
|
391
|
+
else 'journal'
|
392
|
+
end
|
393
|
+
@referent.set_format(fmt)
|
325
394
|
end
|
326
395
|
end
|
327
396
|
|
@@ -331,7 +400,7 @@ module OpenURL
|
|
331
400
|
# upon.
|
332
401
|
|
333
402
|
def translate_abbr(abbr)
|
334
|
-
if @@defined_entities.has_key?abbr
|
403
|
+
if @@defined_entities.has_key?(abbr)
|
335
404
|
ent = @@defined_entities[abbr]
|
336
405
|
if ent == "service-type"
|
337
406
|
ent = "serviceType[0]"
|
@@ -341,74 +410,37 @@ module OpenURL
|
|
341
410
|
ent = "referringEntity"
|
342
411
|
end
|
343
412
|
else
|
344
|
-
|
345
|
-
if idx.length == 0
|
346
|
-
self.add_custom_entity(abbr)
|
347
|
-
idx = self.search_custom_entities("abbr", abbr)
|
348
|
-
end
|
349
|
-
ent = "custom["+idx[0].to_s+"]"
|
413
|
+
return nil
|
350
414
|
end
|
351
415
|
return ent
|
352
416
|
end
|
353
417
|
|
418
|
+
def self.entities(term)
|
419
|
+
return @@defined_entities[term] if @@defined_entities.keys.index(term)
|
420
|
+
return @@defined_entities[@@defined_entities.values.index(term)] if @@defined_entities.values.index(term)
|
421
|
+
return nil
|
422
|
+
|
423
|
+
end
|
424
|
+
|
354
425
|
# Imports an existing OpenURL::ContextObject object and sets the appropriate
|
355
426
|
# entity values.
|
356
427
|
|
357
428
|
def import_context_object(context_object)
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
[
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
eval("@"+ent.label.downcase).send('set_'+var,ent.send(var))
|
367
|
-
else
|
368
|
-
@referringEntity.send('set_'+var,ent.send(var))
|
369
|
-
end
|
370
|
-
end
|
371
|
-
}
|
372
|
-
unless ent.reference["format"].nil? or ent.reference["format"].nil?
|
373
|
-
unless ent.kind_of?(OpenURL::ReferringEntity)
|
374
|
-
eval("@"+ent.label.downcase).set_reference(ent.reference["location"], ent.reference["format"])
|
375
|
-
else
|
376
|
-
@referringEntity.set_referent(ent.reference["location"], ent.reference["format"])
|
377
|
-
end
|
378
|
-
end
|
379
|
-
ent.metadata.each_key { |k|
|
380
|
-
unless ent.metadata[k].nil?
|
381
|
-
unless ent.kind_of?(OpenURL::ReferringEntity)
|
382
|
-
eval("@"+ent.label.downcase).set_metadata(k, ent.metadata[k])
|
383
|
-
else
|
384
|
-
@referringEntity.set_metadata(k, ent.metadata[k])
|
385
|
-
end
|
386
|
-
end
|
387
|
-
}
|
388
|
-
end
|
389
|
-
}
|
390
|
-
context_object.serviceType.each { |svc|
|
391
|
-
if @serviceType[0].empty?
|
392
|
-
@serviceType[0] = svc
|
393
|
-
else
|
394
|
-
idx = self.add_service_type_entity
|
395
|
-
@serviceType[idx] = svc
|
396
|
-
end
|
397
|
-
|
429
|
+
@admin.each_key { |k|
|
430
|
+
self.set_administration_key(k, context_object.admin[k]["value"])
|
431
|
+
}
|
432
|
+
["@referent", "@referringEntity", "@requestor", "@referrer"].each do | ent |
|
433
|
+
self.instance_variable_set(ent.to_sym, Marshal::load(Marshal.dump(context_object.instance_variable_get(ent.to_sym))))
|
434
|
+
end
|
435
|
+
context_object.serviceType.each { |svc|
|
436
|
+
@serviceType << Marshal::load(Marshal.dump(svc))
|
398
437
|
}
|
399
438
|
context_object.resolver.each { |res|
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
end
|
406
|
-
|
407
|
-
}
|
408
|
-
context_object.custom.each { |cus|
|
409
|
-
idx = self.add_custom_entity(cus.abbr, cus.label)
|
410
|
-
@custom[idx] = cus
|
411
|
-
}
|
439
|
+
@resolver << Marshal::load(Marshal.dump(res))
|
440
|
+
}
|
441
|
+
context_object.foreign_keys.each do | key, val |
|
442
|
+
self.foreign_keys[key] = val
|
443
|
+
end
|
412
444
|
end
|
413
445
|
|
414
446
|
# Initialize a new ContextObject object from an existing
|
@@ -420,111 +452,159 @@ module OpenURL
|
|
420
452
|
return co
|
421
453
|
end
|
422
454
|
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
self.import_xml_common(@referent, node)
|
427
|
-
self.import_xml_mbv_ref(@referent, node)
|
455
|
+
def referent=(entity)
|
456
|
+
raise ArgumentError, "Referent must be an OpenURL::ContextObjectEntity" unless entity.is_a?(OpenURL::ContextObjectEntity)
|
457
|
+
@referent=entity
|
428
458
|
end
|
429
459
|
|
430
|
-
def
|
431
|
-
|
432
|
-
|
433
|
-
end
|
460
|
+
def referrer=(entity)
|
461
|
+
raise ArgumentError, "Referrer must be an OpenURL::ContextObjectEntity" unless entity.is_a?(OpenURL::ContextObjectEntity)
|
462
|
+
@referrer=entity
|
463
|
+
end
|
434
464
|
|
435
|
-
def
|
436
|
-
|
437
|
-
|
438
|
-
end
|
465
|
+
def referringEntity=(entity)
|
466
|
+
raise ArgumentError, "Referring-Entity must be an OpenURL::ContextObjectEntity" unless entity.is_a?(OpenURL::ContextObjectEntity)
|
467
|
+
@referringEntity=entity
|
468
|
+
end
|
439
469
|
|
440
|
-
def
|
441
|
-
|
442
|
-
|
470
|
+
def requestor=(entity)
|
471
|
+
raise ArgumentError, "Requestor must be an OpenURL::ContextObjectEntity" unless entity.is_a?(OpenURL::ContextObjectEntity)
|
472
|
+
@requestor=entity
|
443
473
|
end
|
474
|
+
|
475
|
+
protected
|
476
|
+
|
477
|
+
def import_entity(node)
|
478
|
+
entities = {"rft"=>:@referent, "rfr"=>:@referrer, "rfe"=>:@referringEntity,"req"=>:@requestor,
|
479
|
+
"svc"=>:@serviceType,"res"=>:@resolver}
|
480
|
+
|
481
|
+
ent = @@defined_entities.keys[@@defined_entities.values.index(node.name())]
|
482
|
+
|
483
|
+
|
484
|
+
metalib_workaround(node)
|
485
|
+
|
486
|
+
unless ["svc","res"].index(ent)
|
487
|
+
self.instance_variable_set(entities[ent], self.set_typed_entity(node))
|
488
|
+
entity = self.instance_variable_get(entities[ent])
|
489
|
+
|
490
|
+
|
491
|
+
|
492
|
+
self.import_xml_common(entity, node)
|
493
|
+
entity.import_xml_metadata(node)
|
494
|
+
end
|
495
|
+
end
|
444
496
|
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
497
|
+
def import_svc_node(node)
|
498
|
+
if @serviceType[0].empty?
|
499
|
+
key = 0
|
500
|
+
else
|
501
|
+
key = self.add_service_type_entity
|
502
|
+
end
|
503
|
+
self.import_xml_common(@serviceType[key], node)
|
504
|
+
self.import_xml_mbv(@serviceType[key], node)
|
505
|
+
end
|
454
506
|
|
455
|
-
def import_custom_node(node)
|
456
|
-
key = self.add_custom_entity(node.name())
|
457
|
-
self.import_xml_commom(@custom[key], node)
|
458
|
-
self.import_xml_mbv(@custom[key], node)
|
459
|
-
end
|
460
|
-
|
461
507
|
def import_res_node(node)
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
508
|
+
if @resolver[0].empty?
|
509
|
+
key = 0
|
510
|
+
else
|
511
|
+
key = self.add_resolver_entity
|
512
|
+
end
|
513
|
+
self.import_xml_common(@resolver[key], node)
|
514
|
+
self.import_xml_mbv(@resolver[key], node)
|
515
|
+
end
|
516
|
+
|
517
|
+
# Determines the proper subclass of ContextObjectEntity to use
|
518
|
+
# for given format. Input is an REXML node representing a ctx:referent.
|
519
|
+
# Returns ContextObjectEntity.
|
520
|
+
def set_typed_entity(node)
|
521
|
+
fmt = REXML::XPath.first(node, "./ctx:metadata-by-val/ctx:format", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"})
|
522
|
+
|
523
|
+
fmt_val = fmt.get_text.value if fmt && fmt.has_text?
|
524
|
+
|
525
|
+
# Special weird workaround for info sent from metalib.
|
526
|
+
# "info:ofi/fmt:xml:xsd" is not actually a legal format
|
527
|
+
# identifier, it should have more on the end.
|
528
|
+
# XPath should really end in "rft:*" for maximal generality, but
|
529
|
+
# REXML doesn't like that.
|
530
|
+
if (false && fmt_val && fmt_val == "info:ofi/fmt:xml:xsd")
|
531
|
+
metalib_evidence = REXML::XPath.first( node, "./ctx:metadata-by-val/ctx:metadata/rft:journal", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx", "rft"=>"info:ofi/fmt:xml:xsd:journal"})
|
532
|
+
|
533
|
+
# Okay, even if we don't have that one, do we have a REALLY bad one
|
534
|
+
# where Metalib puts an illegal namespace identifier in too?
|
535
|
+
metalib_evidence = REXML::XPath.first( node, "./ctx:metadata-by-val/ctx:metadata/rft:journal", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx", "rft"=>"info:ofi/fmt:xml:xsd"}) unless metalib_evidence
|
536
|
+
|
537
|
+
# metalib didn't advertise it properly, but it's really
|
538
|
+
# journal format.
|
539
|
+
fmt_val = "info:ofi/fmt:xml:xsd:journal" if metalib_evidence
|
540
|
+
end
|
541
|
+
|
542
|
+
if fmt_val
|
543
|
+
return OpenURL::ContextObjectEntityFactory.format(fmt_val)
|
544
|
+
else
|
545
|
+
return OpenURL::ContextObjectEntity.new
|
546
|
+
end
|
469
547
|
end
|
470
548
|
|
471
|
-
# Parses the data that should apply to all XML context objects
|
472
|
-
|
473
|
-
def import_xml_common(ent, node)
|
474
|
-
fmt = REXML::XPath.first(node, ".//ctx:format", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"})
|
475
|
-
ent.set_format(fmt.get_text.value) if fmt and fmt.has_text
|
476
|
-
|
477
|
-
id = REXML::XPath.first(node, ".//ctx:identifier", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"})
|
478
|
-
ent.set_identifier(id.get_text.value) if id and id.has_text?
|
479
|
-
|
480
|
-
priv = REXML::XPath.first(node, ".//ctx:private-data", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"})
|
481
|
-
ent.set_private_data(priv.get_text.value) if priv and priv.has_text?
|
482
|
-
|
483
|
-
ref = REXML::XPath.first(node, ".//ctx:metadata-by-ref", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"})
|
484
|
-
if ref
|
485
|
-
ref.to_a.each do |r|
|
486
|
-
if r.name() == "format"
|
487
|
-
format = r.get_text.value
|
488
|
-
else
|
489
|
-
location = r.get_text.value
|
490
|
-
end
|
491
|
-
ent.set_reference(location, format)
|
492
|
-
end
|
493
|
-
end
|
494
|
-
end
|
495
|
-
|
496
|
-
# Parses metadata-by-val data
|
497
|
-
|
498
|
-
def import_xml_mbv(ent, node)
|
499
|
-
mbv = REXML::XPath.first(node, ".//ctx:metadata-by-val", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"})
|
549
|
+
# Parses the data that should apply to all XML context objects
|
550
|
+
def import_xml_common(ent, node)
|
500
551
|
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
552
|
+
|
553
|
+
REXML::XPath.each(node, "./ctx:identifier", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"}) do | id |
|
554
|
+
ent.add_identifier(id.get_text.value) if id and id.has_text?
|
555
|
+
end
|
556
|
+
|
557
|
+
priv = REXML::XPath.first(node, "./ctx:private-data", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"})
|
558
|
+
ent.set_private_data(priv.get_text.value) if priv and priv.has_text?
|
559
|
+
|
560
|
+
ref = REXML::XPath.first(node, "./ctx:metadata-by-ref", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"})
|
561
|
+
if ref
|
562
|
+
reference = {}
|
563
|
+
ref.to_a.each do |r|
|
564
|
+
if r.name() == "format"
|
565
|
+
reference[:format] = r.get_text.value if r.get_text
|
566
|
+
else
|
567
|
+
reference[:location] = r.get_text.value
|
568
|
+
end
|
569
|
+
end
|
570
|
+
ent.set_reference(reference[:location], reference[:format])
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
# Pass in a REXML element representing an entity.
|
575
|
+
# Special weird workaround for info sent from metalib.
|
576
|
+
# Metalib uses "info:ofi/fmt:xml:xsd" as a format identifier, and
|
577
|
+
# sometimes even as a namespace identifier for a <journal> element.
|
578
|
+
# It's not legal for either. It messes up our parsing. The identifier
|
579
|
+
# should have something else on the end ":journal", ":book", etc.
|
580
|
+
# We tack ":journal" on the end if we find this unspecified
|
581
|
+
# but it contains a <journal> element.
|
582
|
+
# XPath should really end in "rft:*" for maximal generality, but
|
583
|
+
# REXML doesn't like that.
|
584
|
+
def metalib_workaround(node)
|
585
|
+
# Metalib fix
|
586
|
+
# Fix awful illegal Metalib XML
|
587
|
+
fmt = REXML::XPath.first(node, "./ctx:metadata-by-val/ctx:format", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"})
|
588
|
+
if ( fmt && fmt.text == "info:ofi/fmt:xml:xsd")
|
589
|
+
metadata_by_val = node.children.find {|e| e.respond_to?(:name) && e.name == 'metadata-by-val' }
|
590
|
+
|
591
|
+
# Find a "journal" element to make sure forcing to ":journal" is a good
|
592
|
+
# idea, and to later
|
593
|
+
# fix the journal namespace if needed
|
594
|
+
metadata = metadata_by_val.children.find {|e| e.respond_to?(:name) && e.name == 'metadata' } if metadata_by_val
|
595
|
+
journal = metadata.find {|e| e.respond_to?(:name) && e.name == 'journal' } if metadata
|
596
|
+
|
597
|
+
# Fix the format only if there's a <journal> element in there.
|
598
|
+
fmt = metadata_by_val.children.find {|e| e.respond_to?(:name) && e.name == 'format' } if metadata_by_val && journal
|
599
|
+
fmt.text = "info:ofi/fmt:xml:xsd:journal" if fmt
|
600
|
+
|
601
|
+
if (journal && journal.namespace == "info:ofi/fmt:xml:xsd")
|
602
|
+
journal.add_namespace("xmlns:rft", "info:ofi/fmt:xml:xsd:journal")
|
603
|
+
end
|
604
|
+
end
|
605
|
+
end
|
606
|
+
|
607
|
+
end
|
608
|
+
|
609
|
+
|
530
610
|
end
|