openurl 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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 XML.
5
- # Usage:
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
- attr_accessor(:referent, :referringEntity, :requestor, :referrer, :serviceType, :resolver, :custom)
20
- attr_reader(:admin)
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 = ReferentEntity.new()
27
- @referringEntity = ReferringEntity.new()
28
- @requestor = RequestorEntity.new()
29
- @referrer = ReferrerEntity.new()
30
- @serviceType = [ServiceTypeEntity.new()]
31
- @resolver = [ResolverEntity.new()]
32
- @custom = []
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, @referringEntity, @requestor, @referrer].each do | ent |
56
- ent.xml(co) unless ent.empty?
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, @custom].each do |entCont|
60
- entCont.each do |ent|
61
- ent.xml(co) unless ent.empty?
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
- require 'cgi'
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
- [@referent, @referringEntity, @requestor, @referrer].each do | ent |
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
- [@serviceType, @resolver, @custom].each do |entCont|
111
+ {@serviceType=>"svc", @resolver=>"res"}.each do |entCont, abbr|
92
112
  entCont.each do |ent|
93
- kevs.push(ent.kev) unless ent.empty?
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
- def to_hash
102
- co_hash = {"url_ver"=>"Z39.88-2004"}
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
- [@referent, @referringEntity, @requestor, @referrer].each do | ent |
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
- [@serviceType, @resolver, @custom].each do |entCont|
113
- entCont.each do |ent|
114
- co_hash.merge!(ent.to_hash) unless ent.empty?
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.each_key do |k|
178
- # Only take the first value from the value array
179
- co2[k] = co[k][0]
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
- doc = REXML::Document.new xml
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
- ctx = REXML::XPath.first(doc, ".//ctx:context-object", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"})
199
- ctx.attributes.each do |attr, val|
200
- @admin.each do |adm, vals|
201
- self.set_administration_key(adm, val) if vals["label"] == attr
202
- end
203
- end
204
- ctx.to_a.each do | ent |
205
- if @@defined_entities.value?(ent.name())
206
- var = @@defined_entities.keys[@@defined_entities.values.index(ent.name())]
207
- meth = "import_#{var}_node"
208
- self.send(meth, ent)
209
- else
210
- self.import_custom_node(ent)
211
- end
212
- end
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, val|
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.downcase.match(/^[a-z]{3}_val_fmt$/)
250
- # Realistically should only be rft or rfe: get the format
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
- eval("@"+ent).set_identifier(val)
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
- @referent.set_identifier(val)
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
- fmt = case @referent.metadata['genre']
320
- when /article|journal|issue|proceeding|conference|preprint/ then 'journal'
321
- when /book|bookitem|report|document/ then 'book'
322
- else 'journal'
323
- end
324
- @referent.set_format(fmt)
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
- idx = self.search_custom_entities("abbr", abbr)
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
- @admin.each_key { |k|
359
- self.set_administration_key(k, context_object.admin[k]["value"])
360
- }
361
- [context_object.referent, context_object.referringEntity, context_object.requestor, context_object.referrer].each {| ent |
362
- unless ent.empty?
363
- ['identifier', 'format', 'private_data'].each { |var|
364
- unless ent.send(var).nil?
365
- unless ent.kind_of?(OpenURL::ReferringEntity)
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
- if @resolver[0].empty?
401
- @resolver[0] = res
402
- else
403
- idx = self.add_resolver_entity
404
- @resolver[idx] = res
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
- protected
424
-
425
- def import_rft_node(node)
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 import_rfe_node(node)
431
- self.import_xml_common(@referringEntity, node)
432
- self.import_xml_mbv_ref(@referringEntity, node)
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 import_rfr_node(node)
436
- self.import_xml_common(@referrer, node)
437
- self.import_xml_mbv(@referrer, node)
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 import_req_node(node)
441
- self.import_xml_common(@requestor, node)
442
- self.import_xml_mbv(@requestor, node)
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
- def import_svc_node(node)
446
- if @serviceType[0].empty?
447
- key = 0
448
- else
449
- key = self.add_service_type_entity
450
- end
451
- self.import_xml_common(@serviceType[key], node)
452
- self.import_xml_mbv(@serviceType[key], node)
453
- end
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
- if @resolver[0].empty?
463
- key = 0
464
- else
465
- key = self.add_resolver_entity
466
- end
467
- self.import_xml_common(@resolver[key], node)
468
- self.import_xml_mbv(@resolver[key], node)
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
- if mbv
502
- mbv.to_a.each { |m|
503
- ent.set_metadata(m.name(), m.get_text.value)
504
- }
505
- end
506
- end
507
-
508
- # Referent and ReferringEntities place their metadata-by-val inside
509
- # the format element
510
-
511
- def import_xml_mbv_ref(ent, node)
512
- ns = "info:ofi/fmt:xml:xsd:"+ent.format
513
- mbv = REXML::XPath.first(node, ".//fmt:"+ent.format, {"fmt"=>ns})
514
- if mbv
515
- mbv.to_a.each { |m|
516
- if m.has_text?
517
- ent.set_metadata(m.name(), m.get_text.value)
518
- end
519
- if m.has_elements?
520
- m.to_a.each { | md |
521
- if md.has_text?
522
- ent.set_metadata(md.name(), md.get_text.value)
523
- end
524
- }
525
- end
526
- }
527
- end
528
- end
529
- end
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