iiif-presentation 1.4.2 → 1.5.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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +8 -1
- data/.standard.yml +1 -0
- data/Gemfile +5 -5
- data/README.md +1 -1
- data/Rakefile +4 -2
- data/VERSION +1 -1
- data/bin/standardrb +16 -0
- data/iiif-presentation.gemspec +23 -23
- data/lib/iiif/hash_behaviours.rb +29 -33
- data/lib/iiif/ordered_hash.rb +33 -33
- data/lib/iiif/presentation/abstract_resource.rb +17 -22
- data/lib/iiif/presentation/annotation.rb +9 -11
- data/lib/iiif/presentation/annotation_list.rb +8 -10
- data/lib/iiif/presentation/canvas.rb +11 -13
- data/lib/iiif/presentation/collection.rb +7 -9
- data/lib/iiif/presentation/image_resource.rb +33 -35
- data/lib/iiif/presentation/layer.rb +8 -10
- data/lib/iiif/presentation/manifest.rb +9 -12
- data/lib/iiif/presentation/range.rb +8 -10
- data/lib/iiif/presentation/resource.rb +5 -7
- data/lib/iiif/presentation/sequence.rb +8 -11
- data/lib/iiif/presentation/service.rb +1 -2
- data/lib/iiif/presentation.rb +7 -7
- data/lib/iiif/service.rb +121 -102
- data/lib/iiif/v3/abstract_resource.rb +127 -130
- data/lib/iiif/v3/presentation/annotation.rb +20 -21
- data/lib/iiif/v3/presentation/annotation_collection.rb +7 -9
- data/lib/iiif/v3/presentation/annotation_page.rb +14 -15
- data/lib/iiif/v3/presentation/canvas.rb +21 -22
- data/lib/iiif/v3/presentation/choice.rb +14 -15
- data/lib/iiif/v3/presentation/collection.rb +7 -8
- data/lib/iiif/v3/presentation/image_resource.rb +21 -22
- data/lib/iiif/v3/presentation/manifest.rb +18 -19
- data/lib/iiif/v3/presentation/nav_place.rb +125 -29
- data/lib/iiif/v3/presentation/range.rb +10 -10
- data/lib/iiif/v3/presentation/resource.rb +4 -5
- data/lib/iiif/v3/presentation/sequence.rb +10 -11
- data/lib/iiif/v3/presentation/service.rb +12 -13
- data/lib/iiif/v3/presentation.rb +8 -8
- data/spec/integration/iiif/presentation/image_resource_spec.rb +54 -58
- data/spec/integration/iiif/service_spec.rb +76 -77
- data/spec/integration/iiif/v3/abstract_resource_spec.rb +72 -73
- data/spec/integration/iiif/v3/presentation/image_resource_spec.rb +53 -56
- data/spec/spec_helper.rb +56 -64
- data/spec/unit/iiif/hash_behaviours_spec.rb +384 -387
- data/spec/unit/iiif/ordered_hash_spec.rb +86 -91
- data/spec/unit/iiif/presentation/abstract_resource_spec.rb +64 -70
- data/spec/unit/iiif/presentation/annotation_list_spec.rb +1 -3
- data/spec/unit/iiif/presentation/annotation_spec.rb +1 -3
- data/spec/unit/iiif/presentation/canvas_spec.rb +11 -14
- data/spec/unit/iiif/presentation/collection_spec.rb +26 -33
- data/spec/unit/iiif/presentation/image_resource_spec.rb +4 -6
- data/spec/unit/iiif/presentation/layer_spec.rb +16 -20
- data/spec/unit/iiif/presentation/manifest_spec.rb +45 -48
- data/spec/unit/iiif/presentation/range_spec.rb +18 -22
- data/spec/unit/iiif/presentation/resource_spec.rb +3 -6
- data/spec/unit/iiif/presentation/sequence_spec.rb +58 -63
- data/spec/unit/iiif/presentation/shared_examples/abstract_resource_only_keys.rb +4 -10
- data/spec/unit/iiif/presentation/shared_examples/any_type_keys.rb +5 -9
- data/spec/unit/iiif/presentation/shared_examples/array_only_keys.rb +9 -17
- data/spec/unit/iiif/presentation/shared_examples/int_only_keys.rb +6 -14
- data/spec/unit/iiif/presentation/shared_examples/string_only_keys.rb +6 -13
- data/spec/unit/iiif/service_spec.rb +8 -13
- data/spec/unit/iiif/v3/abstract_resource_define_methods_for_spec.rb +39 -37
- data/spec/unit/iiif/v3/abstract_resource_spec.rb +124 -131
- data/spec/unit/iiif/v3/presentation/annotation_collection_spec.rb +14 -17
- data/spec/unit/iiif/v3/presentation/annotation_page_spec.rb +71 -69
- data/spec/unit/iiif/v3/presentation/annotation_spec.rb +249 -233
- data/spec/unit/iiif/v3/presentation/canvas_spec.rb +211 -193
- data/spec/unit/iiif/v3/presentation/choice_spec.rb +81 -77
- data/spec/unit/iiif/v3/presentation/collection_spec.rb +24 -29
- data/spec/unit/iiif/v3/presentation/image_resource_spec.rb +70 -71
- data/spec/unit/iiif/v3/presentation/manifest_spec.rb +248 -235
- data/spec/unit/iiif/v3/presentation/nav_place_spec.rb +249 -49
- data/spec/unit/iiif/v3/presentation/range_spec.rb +28 -29
- data/spec/unit/iiif/v3/presentation/resource_spec.rb +83 -82
- data/spec/unit/iiif/v3/presentation/sequence_spec.rb +125 -119
- data/spec/unit/iiif/v3/presentation/service_spec.rb +123 -124
- data/spec/unit/iiif/v3/presentation/shared_examples/abstract_resource_only_keys.rb +4 -8
- data/spec/unit/iiif/v3/presentation/shared_examples/any_type_keys.rb +5 -7
- data/spec/unit/iiif/v3/presentation/shared_examples/array_only_keys.rb +9 -13
- data/spec/unit/iiif/v3/presentation/shared_examples/hash_only_keys.rb +9 -13
- data/spec/unit/iiif/v3/presentation/shared_examples/int_only_keys.rb +6 -10
- data/spec/unit/iiif/v3/presentation/shared_examples/numeric_only_keys.rb +6 -10
- data/spec/unit/iiif/v3/presentation/shared_examples/string_only_keys.rb +6 -10
- data/spec/unit/iiif/v3/presentation/shared_examples/uri_only_keys.rb +9 -13
- metadata +26 -65
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require_relative
|
|
1
|
+
require_relative "../hash_behaviours"
|
|
2
2
|
|
|
3
3
|
module IIIF
|
|
4
4
|
module V3
|
|
@@ -6,19 +6,19 @@ module IIIF
|
|
|
6
6
|
include IIIF::HashBehaviours
|
|
7
7
|
|
|
8
8
|
# properties used by content resources only
|
|
9
|
-
CONTENT_RESOURCE_PROPERTIES = %w
|
|
9
|
+
CONTENT_RESOURCE_PROPERTIES = %w[format height width duration]
|
|
10
10
|
|
|
11
11
|
# used by Collection, AnnotationCollection
|
|
12
|
-
PAGING_PROPERTIES = %w
|
|
12
|
+
PAGING_PROPERTIES = %w[first last next prev total start_index]
|
|
13
13
|
|
|
14
14
|
# subclasses should override required_keys as appropriate, e.g. super + %w{ id }
|
|
15
15
|
def required_keys
|
|
16
|
-
%w
|
|
16
|
+
%w[type]
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# subclasses should override prohibited_keys as appropriate, e.g. super + PAGING_PROPERTIES
|
|
20
20
|
def prohibited_keys
|
|
21
|
-
%w
|
|
21
|
+
%w[]
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# NOTE: keys associated with a single resource type are not included below in xxx_keys methods:
|
|
@@ -27,37 +27,37 @@ module IIIF
|
|
|
27
27
|
def any_type_keys
|
|
28
28
|
# values *may* be multivalued
|
|
29
29
|
# NOTE: for id: "Resources that do not require URIs [for ids] may be assigned blank node identifiers"
|
|
30
|
-
%w
|
|
30
|
+
%w[id logo viewing_hint related see_also within]
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def string_only_keys
|
|
34
|
-
%w
|
|
34
|
+
%w[nav_date type format rights viewing_direction start_canvas]
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def array_only_keys
|
|
38
|
-
%w
|
|
38
|
+
%w[metadata thumbnail rendering first last next prev items service]
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def hash_only_keys
|
|
42
|
-
%w
|
|
42
|
+
%w[label requiredStatement summary]
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def int_only_keys
|
|
46
|
-
%w
|
|
46
|
+
%w[height width total start_index]
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def numeric_only_keys
|
|
50
|
-
%w
|
|
50
|
+
%w[duration]
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def uri_only_keys
|
|
54
|
-
%w
|
|
54
|
+
%w[]
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
# Not every subclass is allowed to have viewingDirect, but when it is,
|
|
58
58
|
# it must be one of these values
|
|
59
59
|
def legal_viewing_direction_values
|
|
60
|
-
%w
|
|
60
|
+
%w[left-to-right right-to-left top-to-bottom bottom-to-top]
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def legal_viewing_hint_values
|
|
@@ -69,19 +69,19 @@ module IIIF
|
|
|
69
69
|
# Order is only guaranteed if an ActiveSupport::OrderedHash is passed.
|
|
70
70
|
# @param [boolean] include_context (default: false). Pass true if the
|
|
71
71
|
# context should be included.
|
|
72
|
-
def initialize(hsh={})
|
|
73
|
-
if
|
|
72
|
+
def initialize(hsh = {})
|
|
73
|
+
if instance_of?(IIIF::V3::AbstractResource)
|
|
74
74
|
raise "#{self.class} is an abstract class. Please use one of its subclasses."
|
|
75
75
|
end
|
|
76
76
|
@data = IIIF::OrderedHash[hsh]
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
define_methods_for_any_type_keys
|
|
78
|
+
define_methods_for_string_only_keys
|
|
79
|
+
define_methods_for_array_only_keys
|
|
80
|
+
define_methods_for_hash_only_keys
|
|
81
|
+
define_methods_for_int_only_keys
|
|
82
|
+
define_methods_for_numeric_only_keys
|
|
83
|
+
define_methods_for_uri_only_keys
|
|
84
|
+
snakeize_keys
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
# Static methods / alternative constructors
|
|
@@ -89,34 +89,34 @@ module IIIF
|
|
|
89
89
|
# Parse from a file path, string, or existing hash
|
|
90
90
|
def parse(s)
|
|
91
91
|
ordered_hash = nil
|
|
92
|
-
if s.
|
|
92
|
+
if s.is_a?(String) && File.exist?(s)
|
|
93
93
|
ordered_hash = IIIF::OrderedHash[JSON.parse(IO.read(s))]
|
|
94
|
-
elsif s.
|
|
94
|
+
elsif s.is_a?(String) && !File.exist?(s)
|
|
95
95
|
ordered_hash = IIIF::OrderedHash[JSON.parse(s)]
|
|
96
|
-
elsif s.
|
|
96
|
+
elsif s.is_a?(Hash)
|
|
97
97
|
ordered_hash = IIIF::OrderedHash[s]
|
|
98
98
|
else
|
|
99
|
-
m =
|
|
99
|
+
m = "#parse takes a path to a file, a JSON String, or a Hash, "
|
|
100
100
|
m += "argument was a #{s.class}."
|
|
101
|
-
if s.
|
|
102
|
-
m+= "If you were trying to point to a file, does it exist?"
|
|
101
|
+
if s.is_a?(String)
|
|
102
|
+
m += "If you were trying to point to a file, does it exist?"
|
|
103
103
|
end
|
|
104
104
|
raise ArgumentError, m
|
|
105
105
|
end
|
|
106
|
-
|
|
106
|
+
IIIF::V3::Presentation::Service.from_ordered_hash(ordered_hash)
|
|
107
107
|
end
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
def validate
|
|
111
|
-
|
|
112
|
-
unless
|
|
111
|
+
required_keys.each do |k|
|
|
112
|
+
unless has_key?(k)
|
|
113
113
|
m = "A(n) #{k} is required for each #{self.class}"
|
|
114
114
|
raise IIIF::V3::Presentation::MissingRequiredKeyError, m
|
|
115
115
|
end
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
if
|
|
118
|
+
prohibited_keys.each do |k|
|
|
119
|
+
if has_key?(k)
|
|
120
120
|
m = "#{k} is a prohibited key in #{self.class}"
|
|
121
121
|
raise IIIF::V3::Presentation::ProhibitedKeyError, m
|
|
122
122
|
end
|
|
@@ -133,90 +133,91 @@ module IIIF
|
|
|
133
133
|
# e.g. my_image_resource['format'] = 'image/jpeg'
|
|
134
134
|
|
|
135
135
|
# Viewing Direction values
|
|
136
|
-
if
|
|
137
|
-
unless
|
|
136
|
+
if has_key?("viewing_direction")
|
|
137
|
+
unless legal_viewing_direction_values.include?(self["viewing_direction"])
|
|
138
138
|
m = "viewingDirection must be one of #{legal_viewing_direction_values}"
|
|
139
139
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
140
140
|
end
|
|
141
141
|
end
|
|
142
142
|
# Viewing Hint can be an Array ("Any resource type may have one or more viewing hints")
|
|
143
|
-
if
|
|
144
|
-
viewing_hint_val = self[
|
|
143
|
+
if has_key?("viewing_hint")
|
|
144
|
+
viewing_hint_val = self["viewing_hint"]
|
|
145
145
|
[*viewing_hint_val].each { |vh_val|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
valid = legal_viewing_hint_values.include?(vh_val) || (vh_val.is_a?(String) && vh_val =~ URI::RFC2396_PARSER.make_regexp)
|
|
147
|
+
unless valid
|
|
148
|
+
m = "viewingHint for #{self.class} must be one or more of #{legal_viewing_hint_values} or a URI"
|
|
149
|
+
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
149
150
|
end
|
|
150
151
|
}
|
|
151
152
|
end
|
|
152
153
|
# Metadata is Array; each entry is a Hash containing (only) 'label' and 'value' properties
|
|
153
|
-
if
|
|
154
|
-
unless self[
|
|
155
|
-
m =
|
|
154
|
+
if has_key?("metadata") && self["metadata"]
|
|
155
|
+
unless self["metadata"].all? { |entry| entry.is_a?(Hash) }
|
|
156
|
+
m = "metadata must be an Array with Hash members"
|
|
156
157
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
157
158
|
end
|
|
158
|
-
self[
|
|
159
|
+
self["metadata"].each do |entry|
|
|
159
160
|
md_keys = entry.keys
|
|
160
|
-
unless md_keys.size == 2 && md_keys.include?(
|
|
161
|
+
unless md_keys.size == 2 && md_keys.include?("label") && md_keys.include?("value")
|
|
161
162
|
m = "metadata members must be a Hash of keys 'label' and 'value'"
|
|
162
163
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
163
164
|
end
|
|
164
165
|
end
|
|
165
166
|
end
|
|
166
167
|
# Thumbnail is Array; each entry is a Hash or ImageResource containing (at least) 'id' and 'type' keys
|
|
167
|
-
if
|
|
168
|
-
unless self[
|
|
169
|
-
m =
|
|
168
|
+
if has_key?("thumbnail") && self["thumbnail"]
|
|
169
|
+
unless self["thumbnail"].all? { |entry| entry.is_a?(IIIF::V3::Presentation::ImageResource) || entry.is_a?(Hash) }
|
|
170
|
+
m = "thumbnail must be an Array with Hash or ImageResource members"
|
|
170
171
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
171
172
|
end
|
|
172
|
-
self[
|
|
173
|
+
self["thumbnail"].each do |entry|
|
|
173
174
|
thumb_keys = entry.keys
|
|
174
|
-
unless thumb_keys.include?(
|
|
175
|
+
unless thumb_keys.include?("id") && thumb_keys.include?("type")
|
|
175
176
|
m = 'thumbnail members must include keys "id" and "type"'
|
|
176
177
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
177
178
|
end
|
|
178
179
|
end
|
|
179
180
|
end
|
|
180
181
|
# NavDate (navigation date)
|
|
181
|
-
if
|
|
182
|
+
if has_key?("nav_date")
|
|
182
183
|
begin
|
|
183
|
-
Date.strptime(self[
|
|
184
|
+
Date.strptime(self["nav_date"], "%Y-%m-%dT%H:%M:%SZ")
|
|
184
185
|
rescue ArgumentError
|
|
185
186
|
m = "nav_date must be of form YYYY-MM-DDThh:mm:ssZ"
|
|
186
187
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
187
188
|
end
|
|
188
189
|
end
|
|
189
190
|
|
|
190
|
-
if key?(
|
|
191
|
-
m =
|
|
191
|
+
if key?("rights") && !self["rights"].is_a?(String)
|
|
192
|
+
m = "rights must be a String"
|
|
192
193
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
193
194
|
end
|
|
194
195
|
|
|
195
196
|
# rendering is Array; each entry is a Hash containing 'label' and 'format' keys
|
|
196
|
-
if
|
|
197
|
-
unless self[
|
|
198
|
-
m =
|
|
197
|
+
if has_key?("rendering") && self["rendering"]
|
|
198
|
+
unless self["rendering"].all? { |entry| entry.is_a?(Hash) }
|
|
199
|
+
m = "rendering must be an Array with Hash members"
|
|
199
200
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
200
201
|
end
|
|
201
|
-
self[
|
|
202
|
+
self["rendering"].each do |entry|
|
|
202
203
|
rendering_keys = entry.keys
|
|
203
|
-
unless rendering_keys.include?(
|
|
204
|
+
unless rendering_keys.include?("label") && rendering_keys.include?("format")
|
|
204
205
|
m = 'rendering members must be a Hash including keys "label" and "format"'
|
|
205
206
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
206
207
|
end
|
|
207
208
|
end
|
|
208
209
|
end
|
|
209
210
|
# startCanvas is a String with a URI value
|
|
210
|
-
if
|
|
211
|
-
validate_uri(self[
|
|
211
|
+
if has_key?("start_canvas") && self["start_canvas"].is_a?(String)
|
|
212
|
+
validate_uri(self["start_canvas"], "startCanvas") # raises IllegalValueError
|
|
212
213
|
end
|
|
213
214
|
end
|
|
214
215
|
|
|
215
216
|
# Options
|
|
216
217
|
# * pretty: (true|false). Should the JSON be pretty-printed? (default: false)
|
|
217
218
|
# * All options available in #to_ordered_hash
|
|
218
|
-
def to_json(opts={})
|
|
219
|
-
hsh =
|
|
219
|
+
def to_json(opts = {})
|
|
220
|
+
hsh = to_ordered_hash(opts)
|
|
220
221
|
if opts.fetch(:pretty, false)
|
|
221
222
|
JSON.pretty_generate(hsh)
|
|
222
223
|
else
|
|
@@ -230,23 +231,23 @@ module IIIF
|
|
|
230
231
|
# document if it doesn't exist. Default: true.
|
|
231
232
|
# * sort_json_ld_keys: (true|false). Brings all properties starting with
|
|
232
233
|
# '@'. Default: true. to the top of the document and sorts them.
|
|
233
|
-
def to_ordered_hash(opts={})
|
|
234
|
+
def to_ordered_hash(opts = {})
|
|
234
235
|
include_context = opts.fetch(:include_context, true)
|
|
235
|
-
if include_context && !
|
|
236
|
-
self[
|
|
236
|
+
if include_context && !has_key?("@context")
|
|
237
|
+
self["@context"] = IIIF::V3::Presentation::CONTEXT
|
|
237
238
|
end
|
|
238
239
|
force = opts.fetch(:force, false)
|
|
239
240
|
sort_json_ld_keys = opts.fetch(:sort_json_ld_keys, true)
|
|
240
241
|
|
|
241
242
|
unless force
|
|
242
|
-
|
|
243
|
+
validate
|
|
243
244
|
end
|
|
244
245
|
|
|
245
246
|
export_hash = IIIF::OrderedHash.new
|
|
246
247
|
|
|
247
248
|
if sort_json_ld_keys
|
|
248
|
-
|
|
249
|
-
export_hash[k] =
|
|
249
|
+
keys.select { |k| k.start_with?("@") }.sort!.each do |k|
|
|
250
|
+
export_hash[k] = data[k]
|
|
250
251
|
end
|
|
251
252
|
end
|
|
252
253
|
|
|
@@ -255,25 +256,24 @@ module IIIF
|
|
|
255
256
|
sort_json_ld_keys: sort_json_ld_keys,
|
|
256
257
|
force: force
|
|
257
258
|
}
|
|
258
|
-
|
|
259
|
-
unless sort_json_ld_keys && k.start_with?(
|
|
260
|
-
if
|
|
261
|
-
export_hash[k] =
|
|
259
|
+
keys.each do |k|
|
|
260
|
+
unless sort_json_ld_keys && k.start_with?("@")
|
|
261
|
+
if data[k].respond_to?(:to_ordered_hash) # .respond_to?(:to_ordered_hash)
|
|
262
|
+
export_hash[k] = data[k].to_ordered_hash(sub_opts)
|
|
262
263
|
|
|
263
|
-
elsif
|
|
264
|
+
elsif data[k].is_a?(Hash)
|
|
264
265
|
export_hash[k] = IIIF::OrderedHash.new
|
|
265
|
-
|
|
266
|
-
|
|
266
|
+
data[k].each do |sub_k, v|
|
|
267
267
|
if v.respond_to?(:to_ordered_hash)
|
|
268
268
|
export_hash[k][sub_k] = v.to_ordered_hash(sub_opts)
|
|
269
269
|
|
|
270
|
-
elsif v.
|
|
270
|
+
elsif v.is_a?(Array)
|
|
271
271
|
export_hash[k][sub_k] = []
|
|
272
272
|
v.each do |member|
|
|
273
|
-
if member.respond_to?(:to_ordered_hash)
|
|
274
|
-
|
|
273
|
+
export_hash[k][sub_k] << if member.respond_to?(:to_ordered_hash)
|
|
274
|
+
member.to_ordered_hash(sub_opts)
|
|
275
275
|
else
|
|
276
|
-
|
|
276
|
+
member
|
|
277
277
|
end
|
|
278
278
|
end
|
|
279
279
|
else
|
|
@@ -281,29 +281,28 @@ module IIIF
|
|
|
281
281
|
end
|
|
282
282
|
end
|
|
283
283
|
|
|
284
|
-
elsif
|
|
284
|
+
elsif data[k].is_a?(Array)
|
|
285
285
|
export_hash[k] = []
|
|
286
286
|
|
|
287
|
-
|
|
287
|
+
data[k].each do |member|
|
|
288
288
|
if member.respond_to?(:to_ordered_hash)
|
|
289
289
|
export_hash[k] << member.to_ordered_hash(sub_opts)
|
|
290
290
|
|
|
291
|
-
elsif member.
|
|
291
|
+
elsif member.is_a?(Hash)
|
|
292
292
|
hsh = IIIF::OrderedHash.new
|
|
293
293
|
export_hash[k] << hsh
|
|
294
|
-
member.each do |sub_k,v|
|
|
295
|
-
|
|
294
|
+
member.each do |sub_k, v|
|
|
296
295
|
if v.respond_to?(:to_ordered_hash)
|
|
297
296
|
hsh[sub_k] = v.to_ordered_hash(sub_opts)
|
|
298
297
|
|
|
299
|
-
elsif v.
|
|
298
|
+
elsif v.is_a?(Array)
|
|
300
299
|
hsh[sub_k] = []
|
|
301
300
|
|
|
302
301
|
v.each do |sub_member|
|
|
303
|
-
if sub_member.respond_to?(:to_ordered_hash)
|
|
304
|
-
|
|
302
|
+
hsh[sub_k] << if sub_member.respond_to?(:to_ordered_hash)
|
|
303
|
+
sub_member.to_ordered_hash(sub_opts)
|
|
305
304
|
else
|
|
306
|
-
|
|
305
|
+
sub_member
|
|
307
306
|
end
|
|
308
307
|
end
|
|
309
308
|
else
|
|
@@ -317,7 +316,7 @@ module IIIF
|
|
|
317
316
|
end
|
|
318
317
|
end
|
|
319
318
|
else
|
|
320
|
-
export_hash[k] =
|
|
319
|
+
export_hash[k] = data[k]
|
|
321
320
|
end
|
|
322
321
|
|
|
323
322
|
end
|
|
@@ -327,33 +326,33 @@ module IIIF
|
|
|
327
326
|
export_hash
|
|
328
327
|
end
|
|
329
328
|
|
|
330
|
-
def self.from_ordered_hash(hsh, default_klass=IIIF::OrderedHash)
|
|
329
|
+
def self.from_ordered_hash(hsh, default_klass = IIIF::OrderedHash)
|
|
331
330
|
# Create a new object (new_object)
|
|
332
331
|
type = nil
|
|
333
|
-
if hsh.has_key?(
|
|
334
|
-
type = IIIF::V3::AbstractResource.get_descendant_class_by_jld_type(hsh[
|
|
332
|
+
if hsh.has_key?("type")
|
|
333
|
+
type = IIIF::V3::AbstractResource.get_descendant_class_by_jld_type(hsh["type"])
|
|
335
334
|
end
|
|
336
335
|
new_object = type.nil? ? default_klass.new : type.new
|
|
337
336
|
|
|
338
337
|
hsh.keys.each do |key|
|
|
339
|
-
new_key = key.underscore == key ? key : key.underscore
|
|
340
|
-
if new_key ==
|
|
338
|
+
new_key = (key.underscore == key) ? key : key.underscore
|
|
339
|
+
if new_key == "service"
|
|
341
340
|
new_object[new_key] = hsh[key].map do |entry|
|
|
342
341
|
IIIF::V3::AbstractResource.from_ordered_hash(entry, IIIF::V3::Presentation::Service)
|
|
343
342
|
end
|
|
344
|
-
elsif new_key ==
|
|
343
|
+
elsif new_key == "body"
|
|
345
344
|
new_object[new_key] = IIIF::V3::AbstractResource.from_ordered_hash(hsh[key], IIIF::V3::Presentation::Resource)
|
|
346
|
-
elsif hsh[key].
|
|
345
|
+
elsif hsh[key].is_a?(Hash)
|
|
347
346
|
new_object[new_key] = IIIF::V3::AbstractResource.from_ordered_hash(hsh[key])
|
|
348
|
-
elsif hsh[key].
|
|
347
|
+
elsif hsh[key].is_a?(Array)
|
|
349
348
|
new_object[new_key] = []
|
|
350
349
|
hsh[key].each do |member|
|
|
351
|
-
if new_key ==
|
|
352
|
-
|
|
353
|
-
elsif member.
|
|
354
|
-
|
|
350
|
+
new_object[new_key] << if new_key == "service"
|
|
351
|
+
IIIF::V3::AbstractResource.from_ordered_hash(member, IIIF::V3::Presentation::Service)
|
|
352
|
+
elsif member.is_a?(Hash)
|
|
353
|
+
IIIF::V3::AbstractResource.from_ordered_hash(member)
|
|
355
354
|
else
|
|
356
|
-
|
|
355
|
+
member
|
|
357
356
|
# Again, no nested arrays, right?
|
|
358
357
|
end
|
|
359
358
|
end
|
|
@@ -364,8 +363,6 @@ module IIIF
|
|
|
364
363
|
new_object
|
|
365
364
|
end
|
|
366
365
|
|
|
367
|
-
protected
|
|
368
|
-
|
|
369
366
|
def self.get_descendant_class_by_jld_type(type)
|
|
370
367
|
IIIF::V3::AbstractResource.all_known_subclasses.find do |klass|
|
|
371
368
|
klass.const_defined?(:TYPE) && klass.const_get(:TYPE) == type
|
|
@@ -376,13 +373,11 @@ module IIIF
|
|
|
376
373
|
@all_known_subclasses ||= IIIF::V3::AbstractResource.descendants.reject(&:singleton_class?)
|
|
377
374
|
end
|
|
378
375
|
|
|
379
|
-
|
|
380
|
-
@data = hsh
|
|
381
|
-
end
|
|
376
|
+
protected
|
|
382
377
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
378
|
+
attr_writer :data
|
|
379
|
+
|
|
380
|
+
attr_reader :data
|
|
386
381
|
|
|
387
382
|
def define_methods_for_any_type_keys
|
|
388
383
|
define_accessor_methods(*any_type_keys)
|
|
@@ -391,14 +386,14 @@ module IIIF
|
|
|
391
386
|
# an array for empty values.
|
|
392
387
|
any_type_keys.each do |key|
|
|
393
388
|
define_singleton_method(key) do
|
|
394
|
-
|
|
389
|
+
send(:[], key)
|
|
395
390
|
end
|
|
396
391
|
end
|
|
397
392
|
end
|
|
398
393
|
|
|
399
394
|
def define_methods_for_array_only_keys
|
|
400
395
|
define_accessor_methods(*array_only_keys) do |key, val|
|
|
401
|
-
unless val.
|
|
396
|
+
unless val.is_a?(Array)
|
|
402
397
|
m = "#{key} must be an Array."
|
|
403
398
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
404
399
|
end
|
|
@@ -407,7 +402,7 @@ module IIIF
|
|
|
407
402
|
|
|
408
403
|
def define_methods_for_hash_only_keys
|
|
409
404
|
define_accessor_methods(*hash_only_keys) do |key, val|
|
|
410
|
-
unless val.
|
|
405
|
+
unless val.is_a?(Hash)
|
|
411
406
|
m = "#{key} must be a Hash."
|
|
412
407
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
413
408
|
end
|
|
@@ -416,7 +411,7 @@ module IIIF
|
|
|
416
411
|
|
|
417
412
|
def define_methods_for_string_only_keys
|
|
418
413
|
define_accessor_methods(*string_only_keys) do |key, val|
|
|
419
|
-
unless val.
|
|
414
|
+
unless val.is_a?(String)
|
|
420
415
|
m = "#{key} must be a String."
|
|
421
416
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
422
417
|
end
|
|
@@ -425,7 +420,7 @@ module IIIF
|
|
|
425
420
|
|
|
426
421
|
def define_methods_for_int_only_keys
|
|
427
422
|
define_accessor_methods(*int_only_keys) do |key, val|
|
|
428
|
-
unless val.
|
|
423
|
+
unless val.is_a?(Integer) && val > 0
|
|
429
424
|
m = "#{key} must be a positive Integer."
|
|
430
425
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
431
426
|
end
|
|
@@ -434,7 +429,7 @@ module IIIF
|
|
|
434
429
|
|
|
435
430
|
def define_methods_for_numeric_only_keys
|
|
436
431
|
define_accessor_methods(*numeric_only_keys) do |key, val|
|
|
437
|
-
unless val.
|
|
432
|
+
unless val.is_a?(Numeric) && val > 0
|
|
438
433
|
m = "#{key} must be a positive Integer or Float."
|
|
439
434
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
440
435
|
end
|
|
@@ -447,15 +442,17 @@ module IIIF
|
|
|
447
442
|
|
|
448
443
|
def define_accessor_methods(*keys, &validation)
|
|
449
444
|
keys.each do |key|
|
|
445
|
+
camelized_key = key.camelize(:lower)
|
|
446
|
+
|
|
450
447
|
# Setter
|
|
451
448
|
define_singleton_method("#{key}=") do |val|
|
|
452
449
|
validation.call(key, val) if block_given?
|
|
453
|
-
|
|
450
|
+
send(:[]=, key, val)
|
|
454
451
|
end
|
|
455
|
-
if
|
|
456
|
-
define_singleton_method("#{
|
|
452
|
+
if camelized_key != key
|
|
453
|
+
define_singleton_method("#{camelized_key}=") do |val|
|
|
457
454
|
validation.call(key, val) if block_given?
|
|
458
|
-
|
|
455
|
+
send(:[]=, key, val)
|
|
459
456
|
end
|
|
460
457
|
end
|
|
461
458
|
# Getter
|
|
@@ -463,22 +460,22 @@ module IIIF
|
|
|
463
460
|
self[key] ||= []
|
|
464
461
|
self[key]
|
|
465
462
|
end
|
|
466
|
-
if
|
|
467
|
-
define_singleton_method(
|
|
468
|
-
|
|
463
|
+
if camelized_key != key
|
|
464
|
+
define_singleton_method(camelized_key) do
|
|
465
|
+
send(:[], key)
|
|
469
466
|
end
|
|
470
467
|
end
|
|
471
468
|
end
|
|
472
469
|
end
|
|
473
470
|
|
|
474
471
|
private
|
|
472
|
+
|
|
475
473
|
def validate_uri(val, key)
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
474
|
+
raise ArgumentError if URI(val).scheme.nil?
|
|
475
|
+
rescue URI::Error, ArgumentError
|
|
476
|
+
m = "#{key} value must be a String containing a URI for #{self.class}"
|
|
477
|
+
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
480
478
|
end
|
|
481
|
-
|
|
482
479
|
end
|
|
483
480
|
end
|
|
484
481
|
end
|
|
@@ -2,68 +2,67 @@ module IIIF
|
|
|
2
2
|
module V3
|
|
3
3
|
module Presentation
|
|
4
4
|
class Annotation < IIIF::V3::AbstractResource
|
|
5
|
-
|
|
6
|
-
TYPE = 'Annotation'.freeze
|
|
5
|
+
TYPE = "Annotation".freeze
|
|
7
6
|
|
|
8
7
|
def required_keys
|
|
9
|
-
super + %w
|
|
8
|
+
super + %w[id motivation target]
|
|
10
9
|
end
|
|
11
10
|
|
|
12
11
|
def prohibited_keys
|
|
13
12
|
super + CONTENT_RESOURCE_PROPERTIES + PAGING_PROPERTIES +
|
|
14
|
-
|
|
13
|
+
%w[nav_date viewing_direction start_canvas content_annotations]
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
def any_type_keys
|
|
18
|
-
super + %w
|
|
17
|
+
super + %w[body target]
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
def uri_only_keys
|
|
22
|
-
super + %w
|
|
21
|
+
super + %w[id]
|
|
23
22
|
end
|
|
24
23
|
|
|
25
24
|
def string_only_keys
|
|
26
|
-
super + %w
|
|
25
|
+
super + %w[motivation time_mode]
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
def legal_time_mode_values
|
|
30
|
-
%w
|
|
29
|
+
%w[trim scale loop].freeze
|
|
31
30
|
end
|
|
32
31
|
|
|
33
32
|
def legal_viewing_hint_values
|
|
34
|
-
super + %w
|
|
33
|
+
super + %w[none]
|
|
35
34
|
end
|
|
36
35
|
|
|
37
|
-
def initialize(hsh={})
|
|
38
|
-
hsh[
|
|
39
|
-
hsh[
|
|
40
|
-
super
|
|
36
|
+
def initialize(hsh = {})
|
|
37
|
+
hsh["type"] = TYPE unless hsh.has_key? "type"
|
|
38
|
+
hsh["motivation"] = "painting" unless hsh.has_key? "motivation"
|
|
39
|
+
super
|
|
41
40
|
end
|
|
42
41
|
|
|
43
42
|
def validate
|
|
44
43
|
super
|
|
45
44
|
|
|
46
|
-
if
|
|
45
|
+
if has_key?("body") && self["body"].is_a?(IIIF::V3::Presentation::ImageResource)
|
|
47
46
|
img_res_class_str = "IIIF::V3::Presentation::ImageResource"
|
|
48
47
|
|
|
49
|
-
unless
|
|
48
|
+
unless motivation == "painting"
|
|
50
49
|
m = "#{self.class} motivation must be 'painting' when body is a kind of #{img_res_class_str}"
|
|
51
50
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
52
51
|
end
|
|
53
52
|
|
|
54
|
-
body_resource = self[
|
|
55
|
-
body_id = body_resource[
|
|
53
|
+
body_resource = self["body"]
|
|
54
|
+
body_id = body_resource["id"]
|
|
56
55
|
if body_id && body_id =~ /^https?:/
|
|
57
|
-
validate_uri(body_id,
|
|
56
|
+
validate_uri(body_id, "anno body ImageResource id") # can raise IllegalValueError
|
|
58
57
|
else
|
|
59
58
|
m = "when #{self.class} body is a kind of #{img_res_class_str}, ImageResource id must be an http(s) URI"
|
|
60
59
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
61
60
|
end
|
|
62
61
|
end
|
|
63
62
|
|
|
64
|
-
if
|
|
65
|
-
unless
|
|
66
|
-
m = "timeMode for #{self.class} must be one of #{
|
|
63
|
+
if has_key?("time_mode")
|
|
64
|
+
unless legal_time_mode_values.include?(self["time_mode"])
|
|
65
|
+
m = "timeMode for #{self.class} must be one of #{legal_time_mode_values}."
|
|
67
66
|
raise IIIF::V3::Presentation::IllegalValueError, m
|
|
68
67
|
end
|
|
69
68
|
end
|