iiif-presentation 1.0.0 → 1.2.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 +35 -0
- data/.gitignore +1 -0
- data/Gemfile +2 -0
- data/README.md +22 -2
- data/VERSION +1 -1
- data/iiif-presentation.gemspec +1 -1
- data/lib/iiif/hash_behaviours.rb +1 -1
- data/lib/iiif/presentation/canvas.rb +4 -0
- data/lib/iiif/presentation/service.rb +12 -0
- data/lib/iiif/presentation.rb +5 -4
- data/lib/iiif/service.rb +40 -105
- data/lib/iiif/v3/abstract_resource.rb +491 -0
- data/lib/iiif/v3/presentation/annotation.rb +74 -0
- data/lib/iiif/v3/presentation/annotation_collection.rb +38 -0
- data/lib/iiif/v3/presentation/annotation_page.rb +53 -0
- data/lib/iiif/v3/presentation/canvas.rb +82 -0
- data/lib/iiif/v3/presentation/choice.rb +51 -0
- data/lib/iiif/v3/presentation/collection.rb +52 -0
- data/lib/iiif/v3/presentation/image_resource.rb +110 -0
- data/lib/iiif/v3/presentation/manifest.rb +82 -0
- data/lib/iiif/v3/presentation/range.rb +39 -0
- data/lib/iiif/v3/presentation/resource.rb +30 -0
- data/lib/iiif/v3/presentation/sequence.rb +66 -0
- data/lib/iiif/v3/presentation/service.rb +51 -0
- data/lib/iiif/v3/presentation.rb +36 -0
- data/spec/fixtures/v3/manifests/complete_from_spec.json +195 -0
- data/spec/fixtures/v3/manifests/minimal.json +49 -0
- data/spec/fixtures/v3/manifests/service_only.json +14 -0
- data/spec/fixtures/vcr_cassettes/pul_loris_cassette.json +1 -1
- data/spec/fixtures/vcr_cassettes/pul_loris_cassette_v3.json +1 -0
- data/spec/integration/iiif/presentation/image_resource_spec.rb +0 -1
- data/spec/integration/iiif/service_spec.rb +17 -32
- data/spec/integration/iiif/v3/abstract_resource_spec.rb +202 -0
- data/spec/integration/iiif/v3/presentation/image_resource_spec.rb +118 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/unit/iiif/presentation/canvas_spec.rb +0 -1
- data/spec/unit/iiif/presentation/manifest_spec.rb +1 -1
- data/spec/unit/iiif/v3/abstract_resource_define_methods_for_spec.rb +78 -0
- data/spec/unit/iiif/v3/abstract_resource_spec.rb +293 -0
- data/spec/unit/iiif/v3/presentation/annotation_collection_spec.rb +36 -0
- data/spec/unit/iiif/v3/presentation/annotation_page_spec.rb +131 -0
- data/spec/unit/iiif/v3/presentation/annotation_spec.rb +389 -0
- data/spec/unit/iiif/v3/presentation/canvas_spec.rb +337 -0
- data/spec/unit/iiif/v3/presentation/choice_spec.rb +120 -0
- data/spec/unit/iiif/v3/presentation/collection_spec.rb +55 -0
- data/spec/unit/iiif/v3/presentation/image_resource_spec.rb +189 -0
- data/spec/unit/iiif/v3/presentation/manifest_spec.rb +370 -0
- data/spec/unit/iiif/v3/presentation/range_spec.rb +54 -0
- data/spec/unit/iiif/v3/presentation/resource_spec.rb +174 -0
- data/spec/unit/iiif/v3/presentation/sequence_spec.rb +222 -0
- data/spec/unit/iiif/v3/presentation/service_spec.rb +220 -0
- data/spec/unit/iiif/v3/presentation/shared_examples/abstract_resource_only_keys.rb +41 -0
- data/spec/unit/iiif/v3/presentation/shared_examples/any_type_keys.rb +31 -0
- data/spec/unit/iiif/v3/presentation/shared_examples/array_only_keys.rb +40 -0
- data/spec/unit/iiif/v3/presentation/shared_examples/hash_only_keys.rb +40 -0
- data/spec/unit/iiif/v3/presentation/shared_examples/int_only_keys.rb +45 -0
- data/spec/unit/iiif/v3/presentation/shared_examples/numeric_only_keys.rb +45 -0
- data/spec/unit/iiif/v3/presentation/shared_examples/string_only_keys.rb +26 -0
- data/spec/unit/iiif/v3/presentation/shared_examples/uri_only_keys.rb +31 -0
- metadata +82 -11
- data/.travis.yml +0 -11
@@ -0,0 +1,82 @@
|
|
1
|
+
module IIIF
|
2
|
+
module V3
|
3
|
+
module Presentation
|
4
|
+
class Canvas < IIIF::V3::AbstractResource
|
5
|
+
|
6
|
+
TYPE = 'Canvas'.freeze
|
7
|
+
|
8
|
+
def required_keys
|
9
|
+
super + %w{ id label }
|
10
|
+
end
|
11
|
+
|
12
|
+
def prohibited_keys
|
13
|
+
super + PAGING_PROPERTIES + %w{ viewing_direction format nav_date start_canvas content_annotations }
|
14
|
+
end
|
15
|
+
|
16
|
+
def int_only_keys
|
17
|
+
super + %w{ depth }
|
18
|
+
end
|
19
|
+
|
20
|
+
def array_only_keys
|
21
|
+
super + %w{ content }
|
22
|
+
end
|
23
|
+
|
24
|
+
def legal_viewing_hint_values
|
25
|
+
super + %w{ paged continuous non-paged facing-pages auto-advance }
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(hsh={})
|
29
|
+
hsh['type'] = TYPE unless hsh.has_key? 'type'
|
30
|
+
super(hsh)
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate
|
34
|
+
super
|
35
|
+
|
36
|
+
id_uri = URI.parse(self['id'])
|
37
|
+
unless self['id'] =~ /^https?:/ && id_uri.fragment.nil?
|
38
|
+
err_msg = "id must be an http(s) URI without a fragment for #{self.class}"
|
39
|
+
raise IIIF::V3::Presentation::IllegalValueError, err_msg
|
40
|
+
end
|
41
|
+
|
42
|
+
content = self['content']
|
43
|
+
if content && content.any?
|
44
|
+
unless content.all? { |entry| entry.instance_of?(IIIF::V3::Presentation::AnnotationPage) }
|
45
|
+
err_msg = 'All entries in the content list must be a IIIF::V3::Presentation::AnnotationPage'
|
46
|
+
raise IIIF::V3::Presentation::IllegalValueError, err_msg
|
47
|
+
end
|
48
|
+
content.each do |anno_page|
|
49
|
+
annos = anno_page['items']
|
50
|
+
if annos && annos.any?
|
51
|
+
unless annos.all? { |anno| anno.target == self.id }
|
52
|
+
err_msg = 'URI of the canvas must be repeated in the target field of included Annotations'
|
53
|
+
raise IIIF::V3::Presentation::IllegalValueError, err_msg
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# "A canvas MUST have exactly one width and one height, or exactly one duration.
|
60
|
+
# It may have width, height and duration.""
|
61
|
+
height = self['height']
|
62
|
+
width = self['width']
|
63
|
+
if (!!height ^ !!width) # this is an exclusive or: forces height and width to boolean
|
64
|
+
extent_err_msg = "#{self.class} requires both height and width or neither"
|
65
|
+
raise IIIF::V3::Presentation::IllegalValueError, extent_err_msg
|
66
|
+
end
|
67
|
+
# NOTE: relaxing requirement for (exactly one width and one height, and/or exactly one duration)
|
68
|
+
# as Stanford has objects (such as txt files) for which this makes no sense
|
69
|
+
# (see sul-dlss/purl/issues/169)
|
70
|
+
# duration = self['duration']
|
71
|
+
# unless (height && width) || duration
|
72
|
+
# extent_err_msg = "#{self.class} must have (a height and a width) and/or a duration"
|
73
|
+
# raise IIIF::V3::Presentation::IllegalValueError, extent_err_msg
|
74
|
+
# end
|
75
|
+
|
76
|
+
# TODO: Content must not be associated with space or time outside of the Canvas’s dimensions,
|
77
|
+
# such as at coordinates below 0,0, greater than the height or width, before 0 seconds, or after the duration.
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module IIIF
|
2
|
+
module V3
|
3
|
+
module Presentation
|
4
|
+
class Choice < IIIF::V3::AbstractResource
|
5
|
+
|
6
|
+
TYPE = 'Choice'.freeze
|
7
|
+
|
8
|
+
def prohibited_keys
|
9
|
+
super + CONTENT_RESOURCE_PROPERTIES + PAGING_PROPERTIES +
|
10
|
+
%w{ nav_date viewing_direction start_canvas content_annotations }
|
11
|
+
end
|
12
|
+
|
13
|
+
def any_type_keys
|
14
|
+
super + %w{ default }
|
15
|
+
end
|
16
|
+
|
17
|
+
def string_only_keys
|
18
|
+
super + %w{ choice_hint }
|
19
|
+
end
|
20
|
+
|
21
|
+
def array_only_keys;
|
22
|
+
super + %w{ items };
|
23
|
+
end
|
24
|
+
|
25
|
+
def legal_viewing_hint_values
|
26
|
+
%w{ none }
|
27
|
+
end
|
28
|
+
|
29
|
+
def legal_choice_hint_values
|
30
|
+
%w{ client user }
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(hsh={})
|
34
|
+
hsh['type'] = TYPE unless hsh.has_key? 'type'
|
35
|
+
super(hsh)
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate
|
39
|
+
super
|
40
|
+
|
41
|
+
if self.has_key?('choice_hint')
|
42
|
+
unless self.legal_choice_hint_values.include?(self['choice_hint'])
|
43
|
+
m = "choiceHint for #{self.class} must be one of #{self.legal_choice_hint_values}."
|
44
|
+
raise IIIF::V3::Presentation::IllegalValueError, m
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module IIIF
|
2
|
+
module V3
|
3
|
+
module Presentation
|
4
|
+
class Collection < IIIF::V3::AbstractResource
|
5
|
+
|
6
|
+
TYPE = 'Collection'.freeze
|
7
|
+
|
8
|
+
def required_keys
|
9
|
+
super + %w{ id label }
|
10
|
+
end
|
11
|
+
|
12
|
+
def array_only_keys
|
13
|
+
super + %w{ collections manifests }
|
14
|
+
end
|
15
|
+
|
16
|
+
# TODO: navDate (collection or manifest only) - The value must be an xsd:dateTime literal in UTC, expressed in the form “YYYY-MM-DDThh:mm:ssZ”; There must be at most one navDate associated with any given resource.
|
17
|
+
|
18
|
+
# TODO: paging properties
|
19
|
+
# Collection, AnnotationCollection, (formerly layer --> AnnotationPage???) allow; forbidden o.w.
|
20
|
+
# ---
|
21
|
+
# first, last, next, prev
|
22
|
+
# id is URI, but may have other info
|
23
|
+
# total, startIndex
|
24
|
+
# The value must be a non-negative integer.
|
25
|
+
|
26
|
+
def legal_viewing_hint_values
|
27
|
+
%w{ auto-advance together }
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(hsh={})
|
31
|
+
hsh['type'] = TYPE unless hsh.has_key? 'type'
|
32
|
+
super(hsh)
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate
|
36
|
+
super
|
37
|
+
# TODO: each member of collections and manifests must be a Hash
|
38
|
+
# TODO: each member of collections and manifests MUST have id, type, and label
|
39
|
+
# TODO: navDate (collection or manifest only) - The value must be an xsd:dateTime literal in UTC, expressed in the form “YYYY-MM-DDThh:mm:ssZ”; There must be at most one navDate associated with any given resource.
|
40
|
+
|
41
|
+
# TODO: paging properties
|
42
|
+
# Collection, AnnotationCollection, (formerly layer --> AnnotationPage???) allow; forbidden o.w.
|
43
|
+
# ---
|
44
|
+
# first, last, next, prev
|
45
|
+
# id is URI, but may have other info
|
46
|
+
# total, startIndex
|
47
|
+
# The value must be a non-negative integer.
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module IIIF
|
4
|
+
module V3
|
5
|
+
module Presentation
|
6
|
+
class ImageResource < Resource
|
7
|
+
|
8
|
+
TYPE = 'Image'.freeze
|
9
|
+
|
10
|
+
def initialize(hsh={})
|
11
|
+
hsh['type'] = TYPE unless hsh.has_key? 'type'
|
12
|
+
super(hsh)
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
IMAGE_API_DEFAULT_PARAMS = '/full/!200,200/0/default.jpg'.freeze
|
17
|
+
DEFAULT_FORMAT = 'image/jpeg'.freeze
|
18
|
+
# Create a new ImageResource that includes a IIIF Image API Service
|
19
|
+
# See http://iiif.io/api/presentation/2.0/#image-resources
|
20
|
+
#
|
21
|
+
# Params
|
22
|
+
# * :service_id (required) - The base URI for the image on the image
|
23
|
+
# server.
|
24
|
+
# * :resource_id - The id for the resource; if supplied this should
|
25
|
+
# resolve to an actual image. Default:
|
26
|
+
# "#{:service_id}/full/!200,200/0/default.jpg"
|
27
|
+
# * :format - The format of the image that is returned when
|
28
|
+
# `:resource_id` is resolved. Default: 'image/jpeg'
|
29
|
+
# * :height (Integer)
|
30
|
+
# * :profile (String)
|
31
|
+
# * :width (Integer) - If width, height, and profile are not supplied,
|
32
|
+
# this method will try to get the info from the server (based on
|
33
|
+
# :resource_id) and raise an Exception if this is not possible for
|
34
|
+
# some reason.
|
35
|
+
# * :copy_info (bool)- Even if width and height are supplied, try to
|
36
|
+
# get the info.json from the server and copy it in. Default: false
|
37
|
+
#
|
38
|
+
# Raises:
|
39
|
+
# * KeyError if `:service_id` is not supplied
|
40
|
+
# * Expections related to HTTP problems if a call to an image server fails
|
41
|
+
#
|
42
|
+
# The result is something like this:
|
43
|
+
#
|
44
|
+
# {
|
45
|
+
# "id":"http://www.example.org/iiif/book1/res/page1.jpg",
|
46
|
+
# "type": "Image",
|
47
|
+
# "format": "image/jpeg",
|
48
|
+
# "service": {
|
49
|
+
# "@context": "http://iiif.io/api/image/2/context.json",
|
50
|
+
# "id":"http://www.example.org/images/book1-page1",
|
51
|
+
# "profile":"http://iiif.io/api/image/2/profiles/level2.json",
|
52
|
+
# },
|
53
|
+
# "height":2000,
|
54
|
+
# "width":1500
|
55
|
+
# }
|
56
|
+
#
|
57
|
+
def create_image_api_image_resource(params={})
|
58
|
+
|
59
|
+
service_id = params.fetch(:service_id)
|
60
|
+
resource_id_default = "#{service_id}#{IMAGE_API_DEFAULT_PARAMS}"
|
61
|
+
resource_id = params.fetch(:resource_id, resource_id_default)
|
62
|
+
format = params.fetch(:format, DEFAULT_FORMAT)
|
63
|
+
height = params.fetch(:height, nil)
|
64
|
+
profile = params.fetch(:profile, nil)
|
65
|
+
width = params.fetch(:width, nil)
|
66
|
+
copy_info = params.fetch(:copy_info, false)
|
67
|
+
|
68
|
+
have_whp = [width, height, profile].all? { |prop| !prop.nil? }
|
69
|
+
|
70
|
+
remote_info = get_info(service_id) if !have_whp || copy_info
|
71
|
+
|
72
|
+
resource = self.new
|
73
|
+
resource['id'] = resource_id
|
74
|
+
resource.format = format
|
75
|
+
resource.width = width.nil? ? remote_info['width'] : width
|
76
|
+
resource.height = height.nil? ? remote_info['height'] : height
|
77
|
+
resource_service = Service.new
|
78
|
+
if copy_info
|
79
|
+
resource_service.merge!(remote_info)
|
80
|
+
resource_service['id'] ||= resource_service.delete('@id')
|
81
|
+
else
|
82
|
+
resource_service['id'] = service_id
|
83
|
+
if profile.nil?
|
84
|
+
if remote_info['profile'].kind_of?(Array)
|
85
|
+
resource_service['profile'] = remote_info['profile'][0]
|
86
|
+
else
|
87
|
+
resource_service['profile'] = remote_info['profile']
|
88
|
+
end
|
89
|
+
else
|
90
|
+
resource_service['profile'] = profile
|
91
|
+
end
|
92
|
+
end
|
93
|
+
resource.service = [resource_service]
|
94
|
+
return resource
|
95
|
+
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
def get_info(svc_id)
|
99
|
+
conn = Faraday.new("#{svc_id}/info.json") do |c|
|
100
|
+
c.use Faraday::Response::RaiseError
|
101
|
+
c.adapter :net_http
|
102
|
+
end
|
103
|
+
resp = conn.get # raises exceptions that indicate HTTP problems
|
104
|
+
JSON.parse(resp.body)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module IIIF
|
2
|
+
module V3
|
3
|
+
module Presentation
|
4
|
+
class Manifest < IIIF::V3::AbstractResource
|
5
|
+
|
6
|
+
TYPE = 'Manifest'.freeze
|
7
|
+
|
8
|
+
def required_keys
|
9
|
+
# NOTE: relaxing requirement for items as Universal Viewer currently only accepts sequences
|
10
|
+
# see https://github.com/sul-dlss/osullivan/issues/27, sul-dlss/purl/issues/167
|
11
|
+
# super + %w{ id label items }
|
12
|
+
super + %w{ id label }
|
13
|
+
end
|
14
|
+
|
15
|
+
def prohibited_keys
|
16
|
+
super + CONTENT_RESOURCE_PROPERTIES + PAGING_PROPERTIES + %w{ start_canvas content_annotation }
|
17
|
+
end
|
18
|
+
|
19
|
+
def uri_only_keys
|
20
|
+
super + %w{ id }
|
21
|
+
end
|
22
|
+
|
23
|
+
def array_only_keys
|
24
|
+
# NOTE: allowing 'items' or 'sequences' as Universal Viewer currently only accepts sequences
|
25
|
+
# see https://github.com/sul-dlss/osullivan/issues/27, sul-dlss/purl/issues/167
|
26
|
+
# super + %w{ items structures }
|
27
|
+
super + %w{ items structures sequences }
|
28
|
+
end
|
29
|
+
|
30
|
+
def legal_viewing_hint_values
|
31
|
+
%w{ individuals paged continuous auto-advance }
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(hsh={})
|
35
|
+
hsh['type'] = TYPE unless hsh.has_key? 'type'
|
36
|
+
super(hsh)
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate
|
40
|
+
super # also checks navDate format
|
41
|
+
|
42
|
+
unless self['id'] =~ /^https?:/
|
43
|
+
err_msg = "id must be an http(s) URI for #{self.class}"
|
44
|
+
raise IIIF::V3::Presentation::IllegalValueError, err_msg
|
45
|
+
end
|
46
|
+
|
47
|
+
# Items object list
|
48
|
+
unless self&.[]('items')&.any?
|
49
|
+
m = 'The items list must have at least one entry (and it must be a IIIF::V3::Presentation::Canvas)'
|
50
|
+
raise IIIF::V3::Presentation::MissingRequiredKeyError, m
|
51
|
+
end
|
52
|
+
validate_items_list(self['items']) if self['items']
|
53
|
+
|
54
|
+
# TODO: when embedding a sequence without any extensions within a manifest, the sequence must not have the @context field.
|
55
|
+
|
56
|
+
# TODO: AnnotationLists must not be embedded within the manifest
|
57
|
+
|
58
|
+
if self['structures']
|
59
|
+
unless self['structures'].all? { |entry| entry.instance_of?(IIIF::V3::Presentation::Range)}
|
60
|
+
m = 'All entries in the structures list must be a IIIF::V3::Presentation::Range'
|
61
|
+
raise IIIF::V3::Presentation::IllegalValueError, m
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# NOTE: allowing 'items' or 'sequences' as Universal Viewer currently only accepts sequences
|
67
|
+
# see https://github.com/sul-dlss/osullivan/issues/27, sul-dlss/purl/issues/167
|
68
|
+
def validate_items_list(items_array)
|
69
|
+
unless items_array.size >= 1
|
70
|
+
m = 'The items list must have at least one entry (and it must be a IIIF::V3::Presentation::Canvas)'
|
71
|
+
raise IIIF::V3::Presentation::MissingRequiredKeyError, m
|
72
|
+
end
|
73
|
+
|
74
|
+
unless items_array.all? { |entry| entry.instance_of?(IIIF::V3::Presentation::Canvas) }
|
75
|
+
m = 'All entries in the items list must be a IIIF::V3::Presentation::Canvas'
|
76
|
+
raise IIIF::V3::Presentation::IllegalValueError, m
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module IIIF
|
2
|
+
module V3
|
3
|
+
module Presentation
|
4
|
+
# Ranges are linked or embedded within the manifest in a structures field
|
5
|
+
class Range < Sequence
|
6
|
+
|
7
|
+
TYPE = 'Range'.freeze
|
8
|
+
|
9
|
+
def required_keys
|
10
|
+
super + %w{ id label }
|
11
|
+
end
|
12
|
+
|
13
|
+
# TODO: contentAnnotations: links to AnnotationCollection
|
14
|
+
# TODO: startCanvas: A link from a Sequence or Range to a Canvas that is contained within it
|
15
|
+
|
16
|
+
def array_only_keys
|
17
|
+
super + %w{ members }
|
18
|
+
end
|
19
|
+
|
20
|
+
def legal_viewing_hint_values
|
21
|
+
super + %w{ top }
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(hsh={})
|
25
|
+
hsh['type'] = TYPE unless hsh.has_key? 'type'
|
26
|
+
super(hsh)
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate
|
30
|
+
super
|
31
|
+
# TODO: Ranges must have URIs and they should be http(s) URIs.
|
32
|
+
# TODO: Values of the members array must be canvas or range
|
33
|
+
# TODO: contentAnnotations: links to AnnotationCollection
|
34
|
+
# TODO: startCanvas: A link from a Sequence or Range to a Canvas that is contained within it
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module IIIF
|
2
|
+
module V3
|
3
|
+
module Presentation
|
4
|
+
# class for generic content resource
|
5
|
+
class Resource < IIIF::V3::AbstractResource
|
6
|
+
|
7
|
+
def required_keys
|
8
|
+
super + %w{ id }
|
9
|
+
end
|
10
|
+
|
11
|
+
def prohibited_keys
|
12
|
+
super + PAGING_PROPERTIES + %w{ nav_date viewing_direction start_canvas content_annotations}
|
13
|
+
end
|
14
|
+
|
15
|
+
def uri_only_keys
|
16
|
+
super + %w{ id }
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate
|
20
|
+
super
|
21
|
+
|
22
|
+
unless self['id'] =~ /^https?:/
|
23
|
+
err_msg = "id must be an http(s) URI for #{self.class}"
|
24
|
+
raise IIIF::V3::Presentation::IllegalValueError, err_msg
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module IIIF
|
2
|
+
module V3
|
3
|
+
module Presentation
|
4
|
+
class Sequence < IIIF::V3::AbstractResource
|
5
|
+
|
6
|
+
TYPE = 'Sequence'.freeze
|
7
|
+
|
8
|
+
# NOTE: relaxing requirement for items as Universal Viewer currently only accepts canvases
|
9
|
+
# see https://github.com/sul-dlss/osullivan/issues/27, sul-dlss/purl/issues/167
|
10
|
+
# def required_keys
|
11
|
+
# super + %w{ items }
|
12
|
+
# end
|
13
|
+
|
14
|
+
def prohibited_keys
|
15
|
+
super + CONTENT_RESOURCE_PROPERTIES + PAGING_PROPERTIES + %w{ nav_date content_annotations }
|
16
|
+
end
|
17
|
+
|
18
|
+
# NOTE: allowing 'items' or 'canvases' as Universal Viewer currently only accepts canvases
|
19
|
+
# see https://github.com/sul-dlss/osullivan/issues/27, sul-dlss/purl/issues/167
|
20
|
+
def array_only_keys
|
21
|
+
super + %w{ canvases }
|
22
|
+
end
|
23
|
+
|
24
|
+
def legal_viewing_hint_values
|
25
|
+
%w{ individuals paged continuous auto-advance }
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(hsh={})
|
29
|
+
hsh['type'] = TYPE unless hsh.has_key? 'type'
|
30
|
+
super(hsh)
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate
|
34
|
+
super
|
35
|
+
|
36
|
+
# Canvas object list
|
37
|
+
# NOTE: allowing 'items' or 'canvases' as Universal Viewer currently only accepts canvases
|
38
|
+
# see https://github.com/sul-dlss/osullivan/issues/27, sul-dlss/purl/issues/167
|
39
|
+
unless (self['items'] && self['items'].any?) ||
|
40
|
+
(self['canvases'] && self['canvases'].any?)
|
41
|
+
m = 'The (items or canvases) list must have at least one entry (and it must be a IIIF::V3::Presentation::Canvas)'
|
42
|
+
raise IIIF::V3::Presentation::MissingRequiredKeyError, m
|
43
|
+
end
|
44
|
+
validate_canvas_list(self['items']) if self['items']
|
45
|
+
validate_canvas_list(self['canvases']) if self['canvases']
|
46
|
+
|
47
|
+
# TODO: startCanvas: A link from a Sequence or Range to a Canvas that is contained within it
|
48
|
+
|
49
|
+
# TODO: All external Sequences must have a dereference-able http(s) URI
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate_canvas_list(canvas_array)
|
53
|
+
unless canvas_array.size >= 1
|
54
|
+
m = 'The (items or canvases) list must have at least one entry (and it must be a IIIF::V3::Presentation::Canvas)'
|
55
|
+
raise IIIF::V3::Presentation::MissingRequiredKeyError, m
|
56
|
+
end
|
57
|
+
|
58
|
+
unless canvas_array.all? { |entry| entry.instance_of?(IIIF::V3::Presentation::Canvas) }
|
59
|
+
m = 'All entries in the (items or canvases) list must be a IIIF::V3::Presentation::Canvas'
|
60
|
+
raise IIIF::V3::Presentation::IllegalValueError, m
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module IIIF
|
2
|
+
module V3
|
3
|
+
module Presentation
|
4
|
+
# See http://prezi3.iiif.io/api/annex/services for more info
|
5
|
+
class Service < AbstractResource
|
6
|
+
|
7
|
+
# constants included here for convenience
|
8
|
+
IIIF_IMAGE_V2_TYPE = 'ImageService2'.freeze
|
9
|
+
IIIF_IMAGE_V2_LEVEL1_PROFILE = 'http://iiif.io/api/image/2/level1.json'.freeze
|
10
|
+
IIIF_AUTHENTICATION_V1_LOGIN_PROFILE = 'http://iiif.io/api/auth/1/login'.freeze
|
11
|
+
IIIF_AUTHENTICATION_V1_TOKEN_PROFILE = 'http://iiif.io/api/auth/1/token'.freeze
|
12
|
+
|
13
|
+
# service class doesn't require type
|
14
|
+
def required_keys
|
15
|
+
super.reject {|el| el == 'type' }
|
16
|
+
end
|
17
|
+
|
18
|
+
def prohibited_keys
|
19
|
+
super + CONTENT_RESOURCE_PROPERTIES + PAGING_PROPERTIES +
|
20
|
+
%w{ nav_date viewing_direction start_canvas content_annotations }
|
21
|
+
end
|
22
|
+
|
23
|
+
def uri_only_keys
|
24
|
+
super + %w{ @context id @id }
|
25
|
+
end
|
26
|
+
|
27
|
+
def any_type_keys
|
28
|
+
super + %w{ profile }
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate
|
32
|
+
super
|
33
|
+
if IIIF_IMAGE_V2_TYPE == self['type'] || IIIF_IMAGE_V2_TYPE == self['@type']
|
34
|
+
unless self.has_key?('id') || self.has_key?('@id')
|
35
|
+
m = "id or @id values are required for IIIF::V3::Presentation::Service with type or @type #{IIIF_IMAGE_V2_TYPE}"
|
36
|
+
raise IIIF::V3::Presentation::MissingRequiredKeyError, m
|
37
|
+
end
|
38
|
+
if self.has_key?('id') && self.has_key?('@id') && (self['@id'] != self['id'])
|
39
|
+
m = "id and @id values must match for IIIF::V3::Presentation::Service with type or @type #{IIIF_IMAGE_V2_TYPE}"
|
40
|
+
raise IIIF::V3::Presentation::IllegalValueError, m
|
41
|
+
end
|
42
|
+
unless self.has_key?('profile')
|
43
|
+
m = "profile should be present for IIIF::V3::Presentation::Service with type or @type #{IIIF_IMAGE_V2_TYPE}"
|
44
|
+
raise IIIF::V3::Presentation::MissingRequiredKeyError, m
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative 'abstract_resource'
|
2
|
+
require_relative '../ordered_hash'
|
3
|
+
|
4
|
+
# NOTE: image_resource must follow resource due to inheritance
|
5
|
+
# NOTE: range must follow sequence due to inheritance
|
6
|
+
%w{
|
7
|
+
annotation
|
8
|
+
annotation_collection
|
9
|
+
annotation_page
|
10
|
+
canvas
|
11
|
+
choice
|
12
|
+
collection
|
13
|
+
manifest
|
14
|
+
resource
|
15
|
+
image_resource
|
16
|
+
sequence
|
17
|
+
range
|
18
|
+
service
|
19
|
+
}.each do |f|
|
20
|
+
require File.join(File.dirname(__FILE__), 'presentation', f)
|
21
|
+
end
|
22
|
+
|
23
|
+
module IIIF
|
24
|
+
module V3
|
25
|
+
module Presentation
|
26
|
+
CONTEXT ||= [
|
27
|
+
'http://www.w3.org/ns/anno.jsonld',
|
28
|
+
'http://iiif.io/api/presentation/3/context.json'
|
29
|
+
]
|
30
|
+
|
31
|
+
class MissingRequiredKeyError < StandardError; end
|
32
|
+
class ProhibitedKeyError < StandardError; end
|
33
|
+
class IllegalValueError < StandardError; end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|