openurl 0.4.2 → 0.4.3
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.
- checksums.yaml +15 -0
- data/lib/openurl/context_object.rb +31 -28
- data/test/context_object_test.rb +5 -0
- metadata +6 -12
- data/lib/openurl/context_object-SAVED +0 -663
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NWViZjIwOWQ1ODhmMzYzYzc1NTg5ZDI1MDQwODc2Y2FmNGNjMDk4Ng==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
YmU1ODM5YTA4NzM1ZDk3MmUxMmJlYjBkZTg2YmQzM2ZhNTJhZTRjYQ==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YzVhZDBiNzAwNGYyYThiZGM2NTk0NmU0NWExNjJiYjNhYTRhNmRmNWYxYzFm
|
10
|
+
ZGRjNjdlNjRlOTgwMWY0ODA1ZjFjMjY3MmJlMjRkY2M0NzFkNTZkNDE5ZmEx
|
11
|
+
YjlmYzgwMGMzY2Q0MDFlODI3M2JkOTc2OGQyNTFmMDVmOWM2ZDc=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YjRjNDE2MjJmYzI1MDMyNDkyOGM2YzE3MDc5ZjczYTk2ZWMwNTY4YzNiYjc5
|
14
|
+
YWVkMGRmMWUwMTYyNDAwYWM0MWFmMzk2MTU4NTU2YTM2OTJkZTYxYTA4ZWEw
|
15
|
+
YzI2OWRlNzEwNmZkM2UxOTNiZjk2NzQ4MTBkZWEwOTFkYTBkYzI=
|
@@ -352,7 +352,7 @@ module OpenURL
|
|
352
352
|
elsif key.match(/^[a-z]{3}_ref/)
|
353
353
|
# determines if we have a by-reference context object
|
354
354
|
(entity, v, fmt) = key.split("_")
|
355
|
-
ent = self.
|
355
|
+
ent = self.get_entity_obj(entity)
|
356
356
|
unless ent
|
357
357
|
self.foreign_keys[key] = val
|
358
358
|
next
|
@@ -368,45 +368,45 @@ module OpenURL
|
|
368
368
|
end
|
369
369
|
ref[entity] = [ref_key, val]
|
370
370
|
else
|
371
|
-
if ref[entity][0] == "format"
|
372
|
-
|
371
|
+
if ref[entity][0] == "format"
|
372
|
+
instance_variable_get("@#{ent}").set_reference(val, ref[entity][1])
|
373
373
|
else
|
374
|
-
|
374
|
+
instance_variable_get("@#{ent}").set_reference(ref[entity][1], val)
|
375
375
|
end
|
376
376
|
end
|
377
377
|
elsif key.match(/^[a-z]{3}_id$/)
|
378
378
|
# Get the entity identifier
|
379
379
|
(entity, v) = key.split("_")
|
380
|
-
ent = self.
|
380
|
+
ent = self.get_entity_obj(entity)
|
381
381
|
unless ent
|
382
382
|
self.foreign_keys[key] = val
|
383
383
|
next
|
384
384
|
end
|
385
385
|
# May or may not be an array, turn it into one.
|
386
386
|
[value].flatten.each do | id |
|
387
|
-
|
387
|
+
ent.add_identifier(id)
|
388
388
|
end
|
389
389
|
|
390
390
|
elsif key.match(/^[a-z]{3}_dat$/)
|
391
391
|
# Get any private data
|
392
392
|
(entity, v) = key.split("_")
|
393
|
-
ent = self.
|
393
|
+
ent = self.get_entity_obj(entity)
|
394
394
|
unless ent
|
395
395
|
self.foreign_keys[key] = val
|
396
396
|
next
|
397
397
|
end
|
398
|
-
|
398
|
+
ent.set_private_data(val)
|
399
399
|
else
|
400
400
|
# collect the entity metadata
|
401
401
|
keyparts = key.split(".")
|
402
402
|
if keyparts.length > 1
|
403
403
|
# This is 1.0 OpenURL
|
404
|
-
ent = self.
|
404
|
+
ent = self.get_entity_obj(keyparts[0])
|
405
405
|
unless ent
|
406
406
|
self.foreign_keys[key] = val
|
407
407
|
next
|
408
408
|
end
|
409
|
-
|
409
|
+
ent.set_metadata(keyparts[1], val)
|
410
410
|
else
|
411
411
|
# This is a 0.1 OpenURL. Your mileage may vary on how accurately
|
412
412
|
# this maps.
|
@@ -452,26 +452,29 @@ module OpenURL
|
|
452
452
|
end
|
453
453
|
end
|
454
454
|
|
455
|
-
#
|
456
|
-
#
|
457
|
-
#
|
458
|
-
#
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
455
|
+
# Takes a string abbreviated entity ('rfr', 'rft', etc.), returns
|
456
|
+
# the appropriate ContextObjectEntity object for this ContextObject,
|
457
|
+
# in some cases lazily creating and assigning it.
|
458
|
+
# @@defined_entities = {"rft"=>"referent", "rfr"=>"referrer", "rfe"=>"referring-entity", "req"=>"requestor", "svc"=>"service-type", "res"=>"resolver"}
|
459
|
+
def get_entity_obj(abbr)
|
460
|
+
ivar_name = @@defined_entities[abbr]
|
461
|
+
|
462
|
+
return nil unless ivar_name
|
463
|
+
|
464
|
+
return case ivar_name
|
465
|
+
when "service-type"
|
466
|
+
@serviceType << ContextObjectEntity.new if @serviceType.empty?
|
467
|
+
@serviceType.first
|
468
|
+
when "resolver"
|
469
|
+
@resolver << ContextObjectEntity.new if @resolver.empty?
|
470
|
+
@resolver.first
|
471
|
+
when "referring-entity"
|
472
|
+
instance_variable_get("@referringEntity")
|
473
|
+
else
|
474
|
+
instance_variable_get("@#{ivar_name}")
|
469
475
|
end
|
470
|
-
else
|
471
|
-
return nil
|
472
|
-
end
|
473
|
-
return ent
|
474
476
|
end
|
477
|
+
|
475
478
|
|
476
479
|
def self.entities(term)
|
477
480
|
return @@defined_entities[term] if @@defined_entities.keys.index(term)
|
data/test/context_object_test.rb
CHANGED
@@ -371,6 +371,11 @@ class ContextObjectTest < Test::Unit::TestCase
|
|
371
371
|
assert_match(/ctx_ver=&/, kev)
|
372
372
|
end
|
373
373
|
|
374
|
+
def test_kev_res_id
|
375
|
+
kev = "url_ver=Z39.88-2004&url_ctx_fmt=info:ofi/fmt:kev:mtx:ctx&rfr_id=info:sid/aph&res_id=http://openurl.ac.uk/ukfed:dur.ac.uk&rft_val_fmt=info:ofi/fmt:kev:mtx:journal&rft.aulast=Rance&rft.aufirst=Philip&rft.atitle=De%20militari%20scientia »%20or%20Müller%20Fragment%20as%20a%20philological%20resource&rft.jtitle=Glotta&rft.stitle=Glotta&rft.date=2010&rft.volume=86&rft.spage=63&rft.epage=92&rft.issn=0017-1298&rft_id=info:oclcnum/1714662&rft.genre=article&sid=default:none"
|
376
|
+
ctx = OpenURL::ContextObject.new_from_kev(kev)
|
377
|
+
assert_equal("http://openurl.ac.uk/ukfed:dur.ac.uk", ctx.resolver.first.identifier)
|
378
|
+
end
|
374
379
|
protected
|
375
380
|
|
376
381
|
# Make sure ctx1 and ctx2 don't share the same data objects.
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openurl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
5
|
-
prerelease:
|
4
|
+
version: 0.4.3
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Jonathan Rochkind
|
@@ -10,12 +9,11 @@ authors:
|
|
10
9
|
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date:
|
12
|
+
date: 2013-04-17 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: marc
|
17
16
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
17
|
requirements:
|
20
18
|
- - ! '>='
|
21
19
|
- !ruby/object:Gem::Version
|
@@ -23,7 +21,6 @@ dependencies:
|
|
23
21
|
type: :runtime
|
24
22
|
prerelease: false
|
25
23
|
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
24
|
requirements:
|
28
25
|
- - ! '>='
|
29
26
|
- !ruby/object:Gem::Version
|
@@ -31,7 +28,6 @@ dependencies:
|
|
31
28
|
- !ruby/object:Gem::Dependency
|
32
29
|
name: ensure_valid_encoding
|
33
30
|
requirement: !ruby/object:Gem::Requirement
|
34
|
-
none: false
|
35
31
|
requirements:
|
36
32
|
- - ! '>='
|
37
33
|
- !ruby/object:Gem::Version
|
@@ -39,7 +35,6 @@ dependencies:
|
|
39
35
|
type: :runtime
|
40
36
|
prerelease: false
|
41
37
|
version_requirements: !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
38
|
requirements:
|
44
39
|
- - ! '>='
|
45
40
|
- !ruby/object:Gem::Version
|
@@ -63,7 +58,6 @@ files:
|
|
63
58
|
- lib/openurl/metadata_formats/marc.rb
|
64
59
|
- lib/openurl/metadata_formats/patent.rb
|
65
60
|
- lib/openurl/metadata_formats/dissertation.rb
|
66
|
-
- lib/openurl/context_object-SAVED
|
67
61
|
- lib/openurl/transport.rb
|
68
62
|
- lib/openurl/context_object.rb
|
69
63
|
- test/data/metalib_sap2_post_params.yml
|
@@ -79,27 +73,26 @@ files:
|
|
79
73
|
- README.md
|
80
74
|
homepage: https://github.com/openurl/openurl
|
81
75
|
licenses: []
|
76
|
+
metadata: {}
|
82
77
|
post_install_message:
|
83
78
|
rdoc_options: []
|
84
79
|
require_paths:
|
85
80
|
- lib
|
86
81
|
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
-
none: false
|
88
82
|
requirements:
|
89
83
|
- - ! '>='
|
90
84
|
- !ruby/object:Gem::Version
|
91
85
|
version: '0'
|
92
86
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
-
none: false
|
94
87
|
requirements:
|
95
88
|
- - ! '>='
|
96
89
|
- !ruby/object:Gem::Version
|
97
90
|
version: '0'
|
98
91
|
requirements: []
|
99
92
|
rubyforge_project:
|
100
|
-
rubygems_version:
|
93
|
+
rubygems_version: 2.0.3
|
101
94
|
signing_key:
|
102
|
-
specification_version:
|
95
|
+
specification_version: 4
|
103
96
|
summary: a Ruby library to create, parse and use NISO Z39.88 OpenURLs
|
104
97
|
test_files:
|
105
98
|
- test/data/metalib_sap2_post_params.yml
|
@@ -112,3 +105,4 @@ test_files:
|
|
112
105
|
- test/context_object_test.rb
|
113
106
|
- test/encoding_test.rb
|
114
107
|
- test/scholarly_common_test.rb
|
108
|
+
has_rdoc:
|
@@ -1,663 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
require 'ensure_valid_encoding'
|
4
|
-
|
5
|
-
module OpenURL
|
6
|
-
|
7
|
-
if RUBY_VERSION < '1.9'
|
8
|
-
require 'jcode'
|
9
|
-
$KCODE='UTF-8'
|
10
|
-
end
|
11
|
-
|
12
|
-
##
|
13
|
-
# The ContextObject class is intended to both create new OpenURL 1.0 context
|
14
|
-
# objects or parse existing ones, either from Key-Encoded Values (KEVs) or
|
15
|
-
# XML.
|
16
|
-
# == Create a new ContextObject programmatically
|
17
|
-
# require 'openurl/context_object'
|
18
|
-
# include OpenURL
|
19
|
-
#
|
20
|
-
# ctx = ContextObject.new
|
21
|
-
# ctx.referent.set_format('journal') # important to do this FIRST.
|
22
|
-
#
|
23
|
-
# ctx.referent.add_identifier('info:doi/10.1016/j.ipm.2005.03.024')
|
24
|
-
# ctx.referent.set_metadata('issn', '0306-4573')
|
25
|
-
# ctx.referent.set_metadata('aulast', 'Bollen')
|
26
|
-
# ctx.referrer.add_identifier('info:sid/google')
|
27
|
-
# puts ctx.kev
|
28
|
-
# # 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
|
29
|
-
#
|
30
|
-
# == Create a new ContextObject from an existing kev or XML serialization:
|
31
|
-
#
|
32
|
-
# ContextObject.new_from_kev( kev_context_object )
|
33
|
-
# ContextObject.new_from_xml( xml_context_object ) # Can be String or REXML::Document
|
34
|
-
#
|
35
|
-
# == Serialize a ContextObject to kev or XML :
|
36
|
-
# ctx.kev
|
37
|
-
# ctx.xml
|
38
|
-
class ContextObject
|
39
|
-
include EnsureValidEncoding
|
40
|
-
|
41
|
-
attr_reader :admin, :referent, :referringEntity, :requestor, :referrer,
|
42
|
-
:serviceType, :resolver
|
43
|
-
attr_accessor :foreign_keys, :openurl_ver
|
44
|
-
|
45
|
-
@@defined_entities = {"rft"=>"referent", "rfr"=>"referrer", "rfe"=>"referring-entity", "req"=>"requestor", "svc"=>"service-type", "res"=>"resolver"}
|
46
|
-
|
47
|
-
# Creates a new ContextObject object and initializes the ContextObjectEntities.
|
48
|
-
|
49
|
-
def initialize()
|
50
|
-
@referent = ContextObjectEntity.new
|
51
|
-
@referrer = ContextObjectEntity.new
|
52
|
-
@referringEntity = ContextObjectEntity.new
|
53
|
-
@requestor = ContextObjectEntity.new
|
54
|
-
@serviceType = []
|
55
|
-
@resolver = []
|
56
|
-
@foreign_keys = {}
|
57
|
-
@openurl_ver = "Z39.88-2004"
|
58
|
-
@admin = {"ctx_ver"=>{"label"=>"version", "value"=>@openurl_ver}, "ctx_tim"=>{"label"=>"timestamp", "value"=>DateTime.now().to_s}, "ctx_id"=>{"label"=>"identifier", "value"=>""}, "ctx_enc"=>{"label"=>"encoding", "value"=>"info:ofi/enc:UTF-8"}}
|
59
|
-
end
|
60
|
-
|
61
|
-
# Any legal OpenURL 1.0 sends url_ver=Z39.88-2004, and usually
|
62
|
-
# ctx_ver=Z39.88-2004 too. However, sometimes we need to send
|
63
|
-
# an illegal OpenURL with a different openurl ver string, to deal
|
64
|
-
# with weird agents, for instance to trick SFX into doing the right thing.
|
65
|
-
def openurl_ver=(ver)
|
66
|
-
@openurl_ver = ver
|
67
|
-
@admin["ctx_ver"]["value"] = ver
|
68
|
-
end
|
69
|
-
|
70
|
-
def deep_copy
|
71
|
-
cloned = ContextObject.new
|
72
|
-
cloned.import_context_object( self )
|
73
|
-
return cloned
|
74
|
-
end
|
75
|
-
|
76
|
-
# Serialize the ContextObject to XML.
|
77
|
-
|
78
|
-
def xml
|
79
|
-
doc = REXML::Document.new()
|
80
|
-
coContainer = doc.add_element "ctx:context-objects"
|
81
|
-
coContainer.add_namespace("ctx","info:ofi/fmt:xml:xsd:ctx")
|
82
|
-
coContainer.add_namespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
|
83
|
-
coContainer.add_attribute("xsi:schemaLocation", "info:ofi/fmt:xml:xsd:ctx http://www.openurl.info/registry/docs/info:ofi/fmt:xml:xsd:ctx")
|
84
|
-
co = coContainer.add_element "ctx:context-object"
|
85
|
-
@admin.each_key do |k|
|
86
|
-
next if k == "ctx_enc"
|
87
|
-
co.add_attribute(@admin[k]["label"], @admin[k]["value"])
|
88
|
-
end
|
89
|
-
|
90
|
-
[{@referent=>"rft"},
|
91
|
-
{@referringEntity=>"rfe"}, {@requestor=>"req"},
|
92
|
-
{@referrer=>"rfr"}].each do | entity |
|
93
|
-
|
94
|
-
entity.each do | ent, label |
|
95
|
-
ent.xml(co, label) unless ent.empty?
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
[{@serviceType=>"svc"}, {@resolver=>"res"}].each do |entity|
|
100
|
-
entity.each do | entCont, label |
|
101
|
-
entCont.each do |ent|
|
102
|
-
ent.xml(co, label) unless ent.empty?
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
return doc.to_s
|
108
|
-
end
|
109
|
-
|
110
|
-
|
111
|
-
# Output the ContextObject as a Key-encoded value string. Pass a boolean
|
112
|
-
# true argument if you do not want the ctx_tim key included.
|
113
|
-
|
114
|
-
def kev(no_date=false)
|
115
|
-
kevs = ["url_ver=#{self.openurl_ver}", "url_ctx_fmt=#{CGI.escape("info:ofi/fmt:kev:mtx:ctx")}"]
|
116
|
-
|
117
|
-
# Loop through the administrative metadata
|
118
|
-
@admin.each_key do |k|
|
119
|
-
next if k == "ctx_tim" && no_date
|
120
|
-
kevs.push(k+"="+CGI.escape(@admin[k]["value"].to_s)) if @admin[k]["value"]
|
121
|
-
end
|
122
|
-
|
123
|
-
{@referent=>"rft", @referringEntity=>"rfe", @requestor=>"req", @referrer=>"rfr"}.each do | ent, abbr |
|
124
|
-
kevs.push(ent.kev(abbr)) unless ent.empty?
|
125
|
-
end
|
126
|
-
|
127
|
-
{@serviceType=>"svc", @resolver=>"res"}.each do |entCont, abbr|
|
128
|
-
entCont.each do |ent|
|
129
|
-
next if ent.empty?
|
130
|
-
kevs.push(ent.kev(abbr))
|
131
|
-
end
|
132
|
-
end
|
133
|
-
return kevs.join("&")
|
134
|
-
end
|
135
|
-
|
136
|
-
# Outputs the ContextObject as a ruby hash---hash version of the kev format.
|
137
|
-
# Outputting a context object as a hash
|
138
|
-
# is imperfect, because context objects can have multiple elements
|
139
|
-
# with the same key--and because some keys depend on SAP1 vs SAP2.
|
140
|
-
# So this function is really deprecated, but here because we have so much
|
141
|
-
# code dependent on it.
|
142
|
-
def to_hash
|
143
|
-
co_hash = {"url_ver"=>self.openurl_ver, "url_ctx_fmt"=>"info:ofi/fmt:kev:mtx:ctx"}
|
144
|
-
|
145
|
-
@admin.each_key do |k|
|
146
|
-
co_hash[k]=@admin[k]["value"] if @admin[k]["value"]
|
147
|
-
end
|
148
|
-
|
149
|
-
{@referent=>"rft", @referringEntity=>"rfe", @requestor=>"req", @referrer=>"rfr"}.each do | ent, abbr |
|
150
|
-
co_hash.merge!(ent.to_hash(abbr)) unless ent.empty?
|
151
|
-
end
|
152
|
-
|
153
|
-
# svc and res are arrays of ContextObjectEntity
|
154
|
-
{@serviceType=>"svc", @resolver=>"res"}.each do |ent_list, abbr|
|
155
|
-
ent_list.each do |ent|
|
156
|
-
co_hash.merge!(ent.to_hash(abbr)) unless ent.empty?
|
157
|
-
end
|
158
|
-
end
|
159
|
-
return co_hash
|
160
|
-
end
|
161
|
-
|
162
|
-
|
163
|
-
# Outputs a COinS (ContextObject in SPANS) span tag for the ContextObject.
|
164
|
-
# Arguments are any other CSS classes you want included and the innerHTML
|
165
|
-
# content.
|
166
|
-
|
167
|
-
def coins (classnames=nil, innerHTML=nil)
|
168
|
-
return "<span class='Z3988 #{classnames}' title='"+CGI.escapeHTML(self.kev(true))+"'>#{innerHTML}</span>"
|
169
|
-
end
|
170
|
-
|
171
|
-
|
172
|
-
# Sets a ContextObject administration field.
|
173
|
-
|
174
|
-
def set_administration_key(key, val)
|
175
|
-
raise ArgumentException, "#{key} is not a valid admin key!" unless @admin.keys.index(key)
|
176
|
-
@admin[key]["value"] = val
|
177
|
-
end
|
178
|
-
|
179
|
-
# Imports an existing Key-encoded value string and sets the appropriate
|
180
|
-
# entities.
|
181
|
-
|
182
|
-
def import_kev(kev)
|
183
|
-
co = CGI::parse(kev)
|
184
|
-
co2 = {}
|
185
|
-
co.each do |key, val|
|
186
|
-
if val.is_a?(Array)
|
187
|
-
if val.length == 1
|
188
|
-
co2[key] = val[0]
|
189
|
-
else
|
190
|
-
co2[key] = val
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
194
|
-
self.import_hash(co2)
|
195
|
-
end
|
196
|
-
|
197
|
-
# Initialize a new ContextObject object from an existing KEV
|
198
|
-
|
199
|
-
def self.new_from_kev(kev)
|
200
|
-
co = self.new
|
201
|
-
co.import_kev(kev)
|
202
|
-
return co
|
203
|
-
end
|
204
|
-
|
205
|
-
# Initialize a new ContextObject object from a CGI.params style hash
|
206
|
-
# Expects a hash with default value being nil though, not [] as CGI.params
|
207
|
-
# actually returns, beware. Can also accept a Rails-style params hash
|
208
|
-
# (single string values, not array values), although this may lose
|
209
|
-
# some context object information.
|
210
|
-
def self.new_from_form_vars(params)
|
211
|
-
co = self.new
|
212
|
-
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
|
213
|
-
co.admin.keys.each do | adm |
|
214
|
-
if params[adm.to_s]
|
215
|
-
if params[adm.to_s].is_a?(Array)
|
216
|
-
co.set_administration_key(adm, params[adm.to_s].first)
|
217
|
-
else
|
218
|
-
co.set_administration_key(adm, params[adm.to_s])
|
219
|
-
end
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
if ctx_format = (params["url_ctx_fmt"]||params[:url_ctx_fmt])
|
224
|
-
ctx_format = ctx_format.first if ctx_format.is_a?(Array)
|
225
|
-
ctx_val = ctx_val.first if ctx_val.is_a?(Array)
|
226
|
-
if ctx_format == "info:ofi/fmt:xml:xsd:ctx"
|
227
|
-
co.import_xml(ctx_val)
|
228
|
-
elsif ctx_format == "info:ofi/fmt:kev:mtx:ctx"
|
229
|
-
co.import_kev(ctx_val)
|
230
|
-
end
|
231
|
-
end
|
232
|
-
else # we'll assume this is standard inline kev
|
233
|
-
co.import_hash(params)
|
234
|
-
end
|
235
|
-
return co
|
236
|
-
end
|
237
|
-
|
238
|
-
# Imports an existing XML encoded context object and sets the appropriate
|
239
|
-
# entities
|
240
|
-
|
241
|
-
def import_xml(xml)
|
242
|
-
if xml.is_a?(String)
|
243
|
-
xml.force_encoding("UTF-8")
|
244
|
-
ensure_valid_encoding!(xml, :invalid => :replace)
|
245
|
-
doc = REXML::Document.new xml.gsub(/>[\s\t]*\n*[\s\t]*</, '><').strip
|
246
|
-
elsif xml.is_a?(REXML::Document)
|
247
|
-
doc = xml
|
248
|
-
else
|
249
|
-
raise ArgumentError, "Argument must be an REXML::Document or well-formed XML string"
|
250
|
-
end
|
251
|
-
|
252
|
-
# Cut to the context object
|
253
|
-
ctx = REXML::XPath.first(doc, ".//ctx:context-object", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"})
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
ctx.attributes.each do |attr, val|
|
261
|
-
@admin.each do |adm, vals|
|
262
|
-
self.set_administration_key(adm, val) if vals["label"] == attr
|
263
|
-
end
|
264
|
-
end
|
265
|
-
ctx.to_a.each do | ent |
|
266
|
-
if @@defined_entities.value?(ent.name())
|
267
|
-
self.import_entity(ent)
|
268
|
-
else
|
269
|
-
self.import_custom_node(ent)
|
270
|
-
end
|
271
|
-
end
|
272
|
-
end
|
273
|
-
|
274
|
-
# Initialize a new ContextObject object from an existing XML ContextObject
|
275
|
-
|
276
|
-
def self.new_from_xml(xml)
|
277
|
-
co = self.new
|
278
|
-
co.import_xml(xml)
|
279
|
-
return co
|
280
|
-
end
|
281
|
-
|
282
|
-
|
283
|
-
# Takes a hash of openurl key/values, as output by CGI.parse
|
284
|
-
# from a query string for example (values can be strings or arrays
|
285
|
-
# of string). Mutates hash in place.
|
286
|
-
#
|
287
|
-
# Force encodes to UTF8 or 8859-1, depending on ctx_enc
|
288
|
-
# presence and value.
|
289
|
-
#
|
290
|
-
# Replaces any illegal bytes with replacement chars,
|
291
|
-
# transcodes to UTF-8 if needed to ensure UTF8 on way out.
|
292
|
-
def clean_char_encoding!(hash)
|
293
|
-
source_encoding = if hash["ctx_ver"] == "info:ofi/enc:ISO-8859-1"
|
294
|
-
"ISO-8859-1"
|
295
|
-
else
|
296
|
-
"UTF-8"
|
297
|
-
end
|
298
|
-
|
299
|
-
hash.each_pair do |key, values|
|
300
|
-
# get a list of all terminal values, whether wrapped
|
301
|
-
# in arrays or not. We're going to mutate them.
|
302
|
-
[values].flatten.each do | v |
|
303
|
-
v.force_encoding(source_encoding)
|
304
|
-
if source_encoding == "UTF-8"
|
305
|
-
ensure_valid_encoding!(v, :invalid => :replace)
|
306
|
-
else
|
307
|
-
# transcode, replacing any bad chars.
|
308
|
-
v.encode!("UTF-8", :invalid => :replace)
|
309
|
-
end
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|
313
|
-
end
|
314
|
-
|
315
|
-
# Imports an existing hash of ContextObject values and sets the appropriate
|
316
|
-
# entities.
|
317
|
-
def import_hash(hash)
|
318
|
-
clean_char_encoding!(hash)
|
319
|
-
|
320
|
-
ref = {}
|
321
|
-
{"@referent"=>"rft", "@referrer"=>"rfr", "@referringEntity"=>"rfe",
|
322
|
-
"@requestor"=>"req"}.each do | ent, abbr |
|
323
|
-
next unless hash["#{abbr}_val_fmt"]
|
324
|
-
val = hash["#{abbr}_val_fmt"]
|
325
|
-
val = val[0] if val.is_a?(Array)
|
326
|
-
self.instance_variable_set(ent.to_sym, ContextObjectEntityFactory.format(val))
|
327
|
-
end
|
328
|
-
|
329
|
-
{"@serviceType"=>"svc","@resolver"=>"res"}.each do | ent, abbr |
|
330
|
-
next unless hash["#{abbr}_val_fmt"]
|
331
|
-
val = hash["#{abbr}_val_fmt"]
|
332
|
-
val = val[0] if val.is_a?(Array)
|
333
|
-
self.instance_variable_set(ent.to_sym, [ContextObjectEntityFactory.format(val)])
|
334
|
-
end
|
335
|
-
|
336
|
-
openurl_keys = ["url_ver", "url_tim", "url_ctx_fmt"]
|
337
|
-
hash.each do |key, value|
|
338
|
-
val = value
|
339
|
-
val = value[0] if value.is_a?(Array)
|
340
|
-
|
341
|
-
next if value.nil? || value.empty?
|
342
|
-
|
343
|
-
if openurl_keys.include?(key)
|
344
|
-
next # None of these matter much for our purposes
|
345
|
-
elsif @admin.has_key?(key)
|
346
|
-
self.set_administration_key(key, val)
|
347
|
-
elsif key.match(/^[a-z]{3}_val_fmt/)
|
348
|
-
next
|
349
|
-
elsif key.match(/^[a-z]{3}_ref/)
|
350
|
-
# determines if we have a by-reference context object
|
351
|
-
(entity, v, fmt) = key.split("_")
|
352
|
-
ent = self.translate_abbr(entity)
|
353
|
-
unless ent
|
354
|
-
self.foreign_keys[key] = val
|
355
|
-
next
|
356
|
-
end
|
357
|
-
# by-reference requires two fields, format and location, if this is
|
358
|
-
# the first field we've run across, set a place holder until we get
|
359
|
-
# the other value
|
360
|
-
unless ref[entity]
|
361
|
-
if fmt
|
362
|
-
ref_key = "format"
|
363
|
-
else
|
364
|
-
ref_key = "location"
|
365
|
-
end
|
366
|
-
ref[entity] = [ref_key, val]
|
367
|
-
else
|
368
|
-
if ref[entity][0] == "format"
|
369
|
-
eval("@"+ent).set_reference(val, ref[entity][1])
|
370
|
-
else
|
371
|
-
eval("@"+ent).set_reference(ref[entity][1], val)
|
372
|
-
end
|
373
|
-
end
|
374
|
-
elsif key.match(/^[a-z]{3}_id$/)
|
375
|
-
# Get the entity identifier
|
376
|
-
(entity, v) = key.split("_")
|
377
|
-
ent = self.translate_abbr(entity)
|
378
|
-
unless ent
|
379
|
-
self.foreign_keys[key] = val
|
380
|
-
next
|
381
|
-
end
|
382
|
-
# May or may not be an array, turn it into one.
|
383
|
-
[value].flatten.each do | id |
|
384
|
-
eval("@"+ent).add_identifier(id)
|
385
|
-
end
|
386
|
-
|
387
|
-
elsif key.match(/^[a-z]{3}_dat$/)
|
388
|
-
# Get any private data
|
389
|
-
(entity, v) = key.split("_")
|
390
|
-
ent = self.translate_abbr(entity)
|
391
|
-
unless ent
|
392
|
-
self.foreign_keys[key] = val
|
393
|
-
next
|
394
|
-
end
|
395
|
-
eval("@"+ent).set_private_data(val)
|
396
|
-
else
|
397
|
-
# collect the entity metadata
|
398
|
-
keyparts = key.split(".")
|
399
|
-
if keyparts.length > 1
|
400
|
-
# This is 1.0 OpenURL
|
401
|
-
ent = self.translate_abbr(keyparts[0])
|
402
|
-
unless ent
|
403
|
-
self.foreign_keys[key] = val
|
404
|
-
next
|
405
|
-
end
|
406
|
-
eval("@"+ent).set_metadata(keyparts[1], val)
|
407
|
-
else
|
408
|
-
# This is a 0.1 OpenURL. Your mileage may vary on how accurately
|
409
|
-
# this maps.
|
410
|
-
if key == 'id'
|
411
|
-
if value.is_a?(Array)
|
412
|
-
value.each do | id |
|
413
|
-
@referent.add_identifier(id)
|
414
|
-
end
|
415
|
-
else
|
416
|
-
@referent.add_identifier(val)
|
417
|
-
end
|
418
|
-
elsif key == 'sid'
|
419
|
-
@referrer.set_identifier("info:sid/"+val.to_s)
|
420
|
-
elsif key == 'pid'
|
421
|
-
@referent.set_private_data(val.to_s)
|
422
|
-
else
|
423
|
-
@referent.set_metadata(key, val)
|
424
|
-
end
|
425
|
-
end
|
426
|
-
end
|
427
|
-
end
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
# Initialize a new ContextObject object from an existing key/value hash
|
432
|
-
co = self.new
|
433
|
-
co.import_hash(hash)
|
434
|
-
return co
|
435
|
-
end
|
436
|
-
|
437
|
-
# if we don't have a referent format (most likely because we have a 0.1
|
438
|
-
# OpenURL), try to determine something from the genre. If that doesn't
|
439
|
-
# exist, just call it a journal since most 0.1 OpenURLs would be one,
|
440
|
-
# anyway.
|
441
|
-
unless @referent.format
|
442
|
-
fmt = case @referent.metadata['genre']
|
443
|
-
when /article|journal|issue|proceeding|conference|preprint/ then 'journal'
|
444
|
-
when /book|bookitem|report|document/ then 'book'
|
445
|
-
else 'journal'
|
446
|
-
end
|
447
|
-
@referent.set_format(fmt)
|
448
|
-
end
|
449
|
-
end
|
450
|
-
|
451
|
-
# Translates the abbreviated entity (rft, rfr, etc.) to the associated class
|
452
|
-
# name. For repeatable entities, uses the first object in the array. Returns
|
453
|
-
# a string of the object name which would then be eval'ed to call a method
|
454
|
-
# upon.
|
455
|
-
|
456
|
-
def translate_abbr(abbr)
|
457
|
-
if @@defined_entities.has_key?(abbr)
|
458
|
-
ent = @@defined_entities[abbr]
|
459
|
-
if ent == "service-type"
|
460
|
-
ent = "serviceType[0]"
|
461
|
-
elsif ent == "resolver"
|
462
|
-
ent = "resolver[0]"
|
463
|
-
elsif ent == "referring-entity"
|
464
|
-
ent = "referringEntity"
|
465
|
-
end
|
466
|
-
else
|
467
|
-
return nil
|
468
|
-
end
|
469
|
-
return ent
|
470
|
-
end
|
471
|
-
|
472
|
-
def self.entities(term)
|
473
|
-
return @@defined_entities[term] if @@defined_entities.keys.index(term)
|
474
|
-
return @@defined_entities[@@defined_entities.values.index(term)] if @@defined_entities.values.index(term)
|
475
|
-
return nil
|
476
|
-
|
477
|
-
end
|
478
|
-
|
479
|
-
# Imports an existing OpenURL::ContextObject object and sets the appropriate
|
480
|
-
# entity values.
|
481
|
-
|
482
|
-
def import_context_object(context_object)
|
483
|
-
@admin.each_key { |k|
|
484
|
-
self.set_administration_key(k, context_object.admin[k]["value"])
|
485
|
-
}
|
486
|
-
["@referent", "@referringEntity", "@requestor", "@referrer"].each do | ent |
|
487
|
-
self.instance_variable_set(ent.to_sym, Marshal::load(Marshal.dump(context_object.instance_variable_get(ent.to_sym))))
|
488
|
-
end
|
489
|
-
context_object.serviceType.each { |svc|
|
490
|
-
@serviceType << Marshal::load(Marshal.dump(svc))
|
491
|
-
}
|
492
|
-
context_object.resolver.each { |res|
|
493
|
-
@resolver << Marshal::load(Marshal.dump(res))
|
494
|
-
}
|
495
|
-
context_object.foreign_keys.each do | key, val |
|
496
|
-
self.foreign_keys[key] = val
|
497
|
-
end
|
498
|
-
end
|
499
|
-
|
500
|
-
# Initialize a new ContextObject object from an existing
|
501
|
-
# OpenURL::ContextObject
|
502
|
-
|
503
|
-
def self.new_from_context_object(context_object)
|
504
|
-
co = self.new
|
505
|
-
co.import_context_object(context_object)
|
506
|
-
return co
|
507
|
-
end
|
508
|
-
|
509
|
-
def referent=(entity)
|
510
|
-
raise ArgumentError, "Referent must be an OpenURL::ContextObjectEntity" unless entity.is_a?(OpenURL::ContextObjectEntity)
|
511
|
-
@referent=entity
|
512
|
-
end
|
513
|
-
|
514
|
-
def referrer=(entity)
|
515
|
-
raise ArgumentError, "Referrer must be an OpenURL::ContextObjectEntity" unless entity.is_a?(OpenURL::ContextObjectEntity)
|
516
|
-
@referrer=entity
|
517
|
-
end
|
518
|
-
|
519
|
-
def referringEntity=(entity)
|
520
|
-
raise ArgumentError, "Referring-Entity must be an OpenURL::ContextObjectEntity" unless entity.is_a?(OpenURL::ContextObjectEntity)
|
521
|
-
@referringEntity=entity
|
522
|
-
end
|
523
|
-
|
524
|
-
def requestor=(entity)
|
525
|
-
raise ArgumentError, "Requestor must be an OpenURL::ContextObjectEntity" unless entity.is_a?(OpenURL::ContextObjectEntity)
|
526
|
-
@requestor=entity
|
527
|
-
end
|
528
|
-
|
529
|
-
protected
|
530
|
-
|
531
|
-
def import_entity(node)
|
532
|
-
entities = {"rft"=>:@referent, "rfr"=>:@referrer, "rfe"=>:@referringEntity,"req"=>:@requestor,
|
533
|
-
"svc"=>:@serviceType,"res"=>:@resolver}
|
534
|
-
|
535
|
-
ent = @@defined_entities.keys[@@defined_entities.values.index(node.name())]
|
536
|
-
|
537
|
-
|
538
|
-
metalib_workaround(node)
|
539
|
-
|
540
|
-
unless ["svc","res"].index(ent)
|
541
|
-
self.instance_variable_set(entities[ent], self.set_typed_entity(node))
|
542
|
-
entity = self.instance_variable_get(entities[ent])
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
self.import_xml_common(entity, node)
|
547
|
-
entity.import_xml_metadata(node)
|
548
|
-
end
|
549
|
-
end
|
550
|
-
|
551
|
-
def import_svc_node(node)
|
552
|
-
if @serviceType[0].empty?
|
553
|
-
key = 0
|
554
|
-
else
|
555
|
-
key = self.add_service_type_entity
|
556
|
-
end
|
557
|
-
self.import_xml_common(@serviceType[key], node)
|
558
|
-
self.import_xml_mbv(@serviceType[key], node)
|
559
|
-
end
|
560
|
-
|
561
|
-
def import_res_node(node)
|
562
|
-
if @resolver[0].empty?
|
563
|
-
key = 0
|
564
|
-
else
|
565
|
-
key = self.add_resolver_entity
|
566
|
-
end
|
567
|
-
self.import_xml_common(@resolver[key], node)
|
568
|
-
self.import_xml_mbv(@resolver[key], node)
|
569
|
-
end
|
570
|
-
|
571
|
-
# Determines the proper subclass of ContextObjectEntity to use
|
572
|
-
# for given format. Input is an REXML node representing a ctx:referent.
|
573
|
-
# Returns ContextObjectEntity.
|
574
|
-
def set_typed_entity(node)
|
575
|
-
fmt = REXML::XPath.first(node, "./ctx:metadata-by-val/ctx:format", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"})
|
576
|
-
|
577
|
-
fmt_val = fmt.get_text.value if fmt && fmt.has_text?
|
578
|
-
|
579
|
-
# Special weird workaround for info sent from metalib.
|
580
|
-
# "info:ofi/fmt:xml:xsd" is not actually a legal format
|
581
|
-
# identifier, it should have more on the end.
|
582
|
-
# XPath should really end in "rft:*" for maximal generality, but
|
583
|
-
# REXML doesn't like that.
|
584
|
-
if (false && fmt_val && fmt_val == "info:ofi/fmt:xml:xsd")
|
585
|
-
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"})
|
586
|
-
|
587
|
-
# Okay, even if we don't have that one, do we have a REALLY bad one
|
588
|
-
# where Metalib puts an illegal namespace identifier in too?
|
589
|
-
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
|
590
|
-
|
591
|
-
# metalib didn't advertise it properly, but it's really
|
592
|
-
# journal format.
|
593
|
-
fmt_val = "info:ofi/fmt:xml:xsd:journal" if metalib_evidence
|
594
|
-
end
|
595
|
-
|
596
|
-
if fmt_val
|
597
|
-
return OpenURL::ContextObjectEntityFactory.format(fmt_val)
|
598
|
-
else
|
599
|
-
return OpenURL::ContextObjectEntity.new
|
600
|
-
end
|
601
|
-
end
|
602
|
-
|
603
|
-
# Parses the data that should apply to all XML context objects
|
604
|
-
def import_xml_common(ent, node)
|
605
|
-
|
606
|
-
REXML::XPath.each(node, "./ctx:identifier", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"}) do | id |
|
607
|
-
ent.add_identifier(id.get_text.value) if id and id.has_text?
|
608
|
-
end
|
609
|
-
|
610
|
-
priv = REXML::XPath.first(node, "./ctx:private-data", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"})
|
611
|
-
ent.set_private_data(priv.get_text.value) if priv and priv.has_text?
|
612
|
-
|
613
|
-
ref = REXML::XPath.first(node, "./ctx:metadata-by-ref", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"})
|
614
|
-
if ref
|
615
|
-
reference = {}
|
616
|
-
ref.to_a.each do |r|
|
617
|
-
if r.name() == "format"
|
618
|
-
reference[:format] = r.get_text.value if r.get_text
|
619
|
-
else
|
620
|
-
reference[:location] = r.get_text.value
|
621
|
-
end
|
622
|
-
end
|
623
|
-
ent.set_reference(reference[:location], reference[:format])
|
624
|
-
end
|
625
|
-
end
|
626
|
-
|
627
|
-
# Pass in a REXML element representing an entity.
|
628
|
-
# Special weird workaround for info sent from metalib.
|
629
|
-
# Metalib uses "info:ofi/fmt:xml:xsd" as a format identifier, and
|
630
|
-
# sometimes even as a namespace identifier for a <journal> element.
|
631
|
-
# It's not legal for either. It messes up our parsing. The identifier
|
632
|
-
# should have something else on the end ":journal", ":book", etc.
|
633
|
-
# We tack ":journal" on the end if we find this unspecified
|
634
|
-
# but it contains a <journal> element.
|
635
|
-
# XPath should really end in "rft:*" for maximal generality, but
|
636
|
-
# REXML doesn't like that.
|
637
|
-
def metalib_workaround(node)
|
638
|
-
# Metalib fix
|
639
|
-
# Fix awful illegal Metalib XML
|
640
|
-
fmt = REXML::XPath.first(node, "./ctx:metadata-by-val/ctx:format", {"ctx"=>"info:ofi/fmt:xml:xsd:ctx"})
|
641
|
-
if ( fmt && fmt.text == "info:ofi/fmt:xml:xsd")
|
642
|
-
metadata_by_val = node.children.find {|e| e.respond_to?(:name) && e.name == 'metadata-by-val' }
|
643
|
-
|
644
|
-
# Find a "journal" element to make sure forcing to ":journal" is a good
|
645
|
-
# idea, and to later
|
646
|
-
# fix the journal namespace if needed
|
647
|
-
metadata = metadata_by_val.children.find {|e| e.respond_to?(:name) && e.name == 'metadata' } if metadata_by_val
|
648
|
-
journal = metadata.find {|e| e.respond_to?(:name) && e.name == 'journal' } if metadata
|
649
|
-
|
650
|
-
# Fix the format only if there's a <journal> element in there.
|
651
|
-
fmt = metadata_by_val.children.find {|e| e.respond_to?(:name) && e.name == 'format' } if metadata_by_val && journal
|
652
|
-
fmt.text = "info:ofi/fmt:xml:xsd:journal" if fmt
|
653
|
-
|
654
|
-
if (journal && journal.namespace == "info:ofi/fmt:xml:xsd")
|
655
|
-
journal.add_namespace("xmlns:rft", "info:ofi/fmt:xml:xsd:journal")
|
656
|
-
end
|
657
|
-
end
|
658
|
-
end
|
659
|
-
|
660
|
-
end
|
661
|
-
|
662
|
-
|
663
|
-
end
|