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 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