iiif-presentation 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +23 -0
  7. data/README.md +173 -0
  8. data/Rakefile +12 -0
  9. data/VERSION +1 -0
  10. data/gemfiles/rails3.gemfile +5 -0
  11. data/gemfiles/rails4.gemfile +5 -0
  12. data/iiif-presentation.gemspec +28 -0
  13. data/lib/active_support/ordered_hash.rb +147 -0
  14. data/lib/iiif/hash_behaviours.rb +150 -0
  15. data/lib/iiif/presentation.rb +25 -0
  16. data/lib/iiif/presentation/abstract_resource.rb +75 -0
  17. data/lib/iiif/presentation/annotation.rb +25 -0
  18. data/lib/iiif/presentation/annotation_list.rb +28 -0
  19. data/lib/iiif/presentation/canvas.rb +45 -0
  20. data/lib/iiif/presentation/collection.rb +29 -0
  21. data/lib/iiif/presentation/image_resource.rb +115 -0
  22. data/lib/iiif/presentation/layer.rb +34 -0
  23. data/lib/iiif/presentation/manifest.rb +39 -0
  24. data/lib/iiif/presentation/range.rb +32 -0
  25. data/lib/iiif/presentation/resource.rb +21 -0
  26. data/lib/iiif/presentation/sequence.rb +35 -0
  27. data/lib/iiif/service.rb +418 -0
  28. data/spec/fixtures/manifests/complete_from_spec.json +171 -0
  29. data/spec/fixtures/manifests/minimal.json +40 -0
  30. data/spec/fixtures/manifests/service_only.json +11 -0
  31. data/spec/fixtures/vcr_cassettes/pul_loris_cassette.json +159 -0
  32. data/spec/integration/iiif/presentation/image_resource_spec.rb +123 -0
  33. data/spec/integration/iiif/service_spec.rb +211 -0
  34. data/spec/spec_helper.rb +104 -0
  35. data/spec/unit/active_support/ordered_hash_spec.rb +155 -0
  36. data/spec/unit/iiif/hash_behaviours_spec.rb +569 -0
  37. data/spec/unit/iiif/presentation/abstract_resource_spec.rb +133 -0
  38. data/spec/unit/iiif/presentation/annotation_list_spec.rb +7 -0
  39. data/spec/unit/iiif/presentation/annotation_spec.rb +7 -0
  40. data/spec/unit/iiif/presentation/canvas_spec.rb +40 -0
  41. data/spec/unit/iiif/presentation/collection_spec.rb +54 -0
  42. data/spec/unit/iiif/presentation/image_resource_spec.rb +13 -0
  43. data/spec/unit/iiif/presentation/layer_spec.rb +38 -0
  44. data/spec/unit/iiif/presentation/manifest_spec.rb +89 -0
  45. data/spec/unit/iiif/presentation/range_spec.rb +43 -0
  46. data/spec/unit/iiif/presentation/resource_spec.rb +16 -0
  47. data/spec/unit/iiif/presentation/sequence_spec.rb +110 -0
  48. data/spec/unit/iiif/presentation/shared_examples/abstract_resource_only_keys.rb +43 -0
  49. data/spec/unit/iiif/presentation/shared_examples/any_type_keys.rb +33 -0
  50. data/spec/unit/iiif/presentation/shared_examples/array_only_keys.rb +44 -0
  51. data/spec/unit/iiif/presentation/shared_examples/int_only_keys.rb +49 -0
  52. data/spec/unit/iiif/presentation/shared_examples/string_only_keys.rb +29 -0
  53. data/spec/unit/iiif/service_spec.rb +10 -0
  54. metadata +262 -0
@@ -0,0 +1,25 @@
1
+ require File.join(File.dirname(__FILE__), 'service')
2
+ %w{
3
+ abstract_resource
4
+ annotation
5
+ annotation_list
6
+ canvas
7
+ collection
8
+ layer
9
+ manifest
10
+ resource
11
+ image_resource
12
+ sequence
13
+ range
14
+ }.each do |f|
15
+ require File.join(File.dirname(__FILE__), 'presentation', f)
16
+ end
17
+
18
+ module IIIF
19
+ module Presentation
20
+ CONTEXT ||= 'http://iiif.io/api/presentation/2/context.json'
21
+
22
+ class MissingRequiredKeyError < StandardError; end
23
+ class IllegalValueError < StandardError; end
24
+ end
25
+ end
@@ -0,0 +1,75 @@
1
+ require File.join(File.dirname(__FILE__), '../service')
2
+
3
+ module IIIF
4
+ module Presentation
5
+ class AbstractResource < Service
6
+
7
+ # Every subclass should override the following five methods where
8
+ # appropriate, see Subclasses for how.
9
+ def required_keys
10
+ %w{ @type }
11
+ end
12
+
13
+ def any_type_keys # these are allowed on all classes
14
+ %w{ label description thumbnail attribution license logo see_also
15
+ related within }
16
+ end
17
+
18
+ def string_only_keys
19
+ %w{ viewing_hint } # should any of the any_type_keys be here?
20
+ end
21
+
22
+ def array_only_keys
23
+ %w{ metadata }
24
+ end
25
+
26
+ def abstract_resource_only_keys
27
+ super + [ { key: 'service', type: IIIF::Service } ]
28
+ end
29
+
30
+ def hash_only_keys
31
+ %w{ }
32
+ end
33
+
34
+ def int_only_keys
35
+ %w{ }
36
+ end
37
+
38
+ # Not every subclass is allowed to have viewingDirect, but when it is,
39
+ # it must be one of these values
40
+ def legal_viewing_direction_values
41
+ %w{ left-to-right right-to-left top-to-bottom bottom-to-top }
42
+ end
43
+
44
+ # Initialize a Presentation node
45
+ # @param [Hash] hsh - Anything in this hash will be added to the Object.'
46
+ # Order is only guaranteed if an ActiveSupport::OrderedHash is passed.
47
+ # @param [boolean] include_context (default: false). Pass true if the'
48
+ # context should be included.
49
+ def initialize(hsh={})
50
+ if self.class == IIIF::Presentation::AbstractResource
51
+ raise "#{self.class} is an abstract class. Please use one of its subclasses."
52
+ end
53
+ super(hsh)
54
+ end
55
+
56
+
57
+ # Options:
58
+ # * force: (true|false). Skips validations.
59
+ # * include_context: (true|false). Adds the @context to the top of the
60
+ # document if it doesn't exist. Default: true.
61
+ # * sort_json_ld_keys: (true|false). Brings all properties starting with
62
+ # '@'. Default: true. to the top of the document and sorts them.
63
+ def to_ordered_hash(opts={})
64
+ include_context = opts.fetch(:include_context, true)
65
+ if include_context && !self.has_key?('@context')
66
+ self['@context'] = IIIF::Presentation::CONTEXT
67
+ end
68
+ super(opts)
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+ end
75
+
@@ -0,0 +1,25 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_resource')
2
+
3
+ module IIIF
4
+ module Presentation
5
+ class Annotation < AbstractResource
6
+
7
+ TYPE = 'oa:Annotation'
8
+
9
+ def required_keys
10
+ super + %w{ motivation }
11
+ end
12
+
13
+ def abstract_resource_only_keys
14
+ super + [ { key: 'resource', type: IIIF::Presentation::Resource } ]
15
+ end
16
+
17
+ def initialize(hsh={})
18
+ hsh['@type'] = TYPE unless hsh.has_key? '@type'
19
+ hsh['motivation'] = 'sc:painting' unless hsh.has_key? 'motivation'
20
+ super(hsh)
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_resource')
2
+
3
+ module IIIF
4
+ module Presentation
5
+ class AnnotationList < AbstractResource
6
+
7
+ TYPE = 'sc:AnnotationList'
8
+
9
+ def required_keys
10
+ super + %w{ @id }
11
+ end
12
+
13
+ def array_only_keys;
14
+ super + %w{ resources };
15
+ end
16
+
17
+ def initialize(hsh={})
18
+ hsh['@type'] = TYPE unless hsh.has_key? '@type'
19
+ super(hsh)
20
+ end
21
+
22
+ def validate
23
+ # Each member or resources must be a kind of Annotation
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,45 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_resource')
2
+
3
+ module IIIF
4
+ module Presentation
5
+ class Canvas < AbstractResource
6
+
7
+ # TODO (?) a simple 'Image Canvas' constructor.
8
+
9
+ TYPE = 'sc:Canvas'
10
+
11
+ def required_keys
12
+ super + %w{ @id width height label }
13
+ end
14
+
15
+ def any_type_keys
16
+ super + %w{ }
17
+ end
18
+
19
+ def array_only_keys
20
+ super + %w{ images other_content }
21
+ end
22
+
23
+ # TODO: test and validate
24
+ def int_only_keys
25
+ super + %w{ width height }
26
+ end
27
+
28
+ def legal_viewing_hint_values
29
+ super + %w{ non-paged }
30
+ end
31
+
32
+ def initialize(hsh={})
33
+ hsh['@type'] = TYPE unless hsh.has_key? '@type'
34
+ super(hsh)
35
+ end
36
+
37
+ def validate
38
+ # all members of images must be an annotation
39
+ # all members of otherContent must be an annotation list
40
+ super
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,29 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_resource')
2
+
3
+ module IIIF
4
+ module Presentation
5
+ class Collection < AbstractResource
6
+
7
+ TYPE = 'sc:Collection'
8
+
9
+ def required_keys
10
+ super + %w{ @id label }
11
+ end
12
+
13
+ def array_only_keys
14
+ super + %w{ collections manifests }
15
+ end
16
+
17
+ def initialize(hsh={})
18
+ hsh['@type'] = TYPE unless hsh.has_key? '@type'
19
+ super(hsh)
20
+ end
21
+
22
+ def validate
23
+ # each member of collections and manifests must be a Hash
24
+ # each member of collections and manifests MUST have @id, @type, and label
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,115 @@
1
+ require File.join(File.dirname(__FILE__), 'resource')
2
+ require 'faraday'
3
+ require 'json'
4
+
5
+ module IIIF
6
+ module Presentation
7
+ class ImageResource < Resource
8
+
9
+ TYPE = 'dctypes:Image'
10
+
11
+ def int_only_keys
12
+ super + %w{ width height }
13
+ end
14
+
15
+ def initialize(hsh={})
16
+ hsh['@type'] = 'dcterms:Image' unless hsh.has_key? '@type'
17
+ super(hsh)
18
+ end
19
+
20
+ class << self
21
+ IMAGE_API_DEFAULT_PARAMS = '/full/!200,200/0/default.jpg'
22
+ IMAGE_API_CONTEXT = 'http://iiif.io/api/image/2/context.json'
23
+ DEFAULT_FORMAT = 'image/jpeg'
24
+ # Create a new ImageResource that includes a IIIF Image API Service
25
+ # See http://iiif.io/api/presentation/2.0/#image-resources
26
+ #
27
+ # Params
28
+ # * :service_id (required) - The base URI for the image on the image
29
+ # server.
30
+ # * :resource_id - The id for the resource; if supplied this should
31
+ # resolve to an actual image. Default:
32
+ # "#{:service_id}/full/!200,200/0/default.jpg"
33
+ # * :format - The format of the image that is returned when
34
+ # `:resource_id` is resolved. Default: 'image/jpeg'
35
+ # * :height (Integer)
36
+ # * :profile (String)
37
+ # * :width (Integer) - If width, height, and profile are not supplied,
38
+ # this method will try to get the info from the server (based on
39
+ # :resource_id) and raise an Exception if this is not possible for
40
+ # some reason.
41
+ # * :copy_info (bool)- Even if width and height are supplied, try to
42
+ # get the info.json from the server and copy it in. Default: false
43
+ #
44
+ # Raises:
45
+ # * KeyError if `:service_id` is not supplied
46
+ # * Expections related to HTTP problems if a call to an image server fails
47
+ #
48
+ # The result is something like this:
49
+ #
50
+ # {
51
+ # "@id":"http://www.example.org/iiif/book1/res/page1.jpg",
52
+ # "@type":"dctypes:Image",
53
+ # "format":"image/jpeg",
54
+ # "service": {
55
+ # "@context": "http://iiif.io/api/image/2/context.json",
56
+ # "@id":"http://www.example.org/images/book1-page1",
57
+ # "profile":"http://iiif.io/api/image/2/profiles/level2.json",
58
+ # },
59
+ # "height":2000,
60
+ # "width":1500
61
+ # }
62
+ #
63
+ def create_image_api_image_resource(params={})
64
+
65
+ service_id = params.fetch(:service_id)
66
+ resource_id_default = "#{service_id}#{IMAGE_API_DEFAULT_PARAMS}"
67
+ resource_id = params.fetch(:resource_id, resource_id_default)
68
+ format = params.fetch(:format, DEFAULT_FORMAT)
69
+ height = params.fetch(:height, nil)
70
+ profile = params.fetch(:profile, nil)
71
+ width = params.fetch(:width, nil)
72
+ copy_info = params.fetch(:copy_info, false)
73
+
74
+ have_whp = [width, height, profile].all? { |prop| !prop.nil? }
75
+
76
+ remote_info = get_info(service_id) if !have_whp || copy_info
77
+
78
+ resource = self.new
79
+ resource['@id'] = resource_id
80
+ resource.format = format
81
+ resource.width = width.nil? ? remote_info['width'] : width
82
+ resource.height = height.nil? ? remote_info['height'] : height
83
+ resource.service = Service.new
84
+ if copy_info
85
+ resource.service.merge!(remote_info)
86
+ else
87
+ resource.service['@context'] = IMAGE_API_CONTEXT
88
+ resource.service['@id'] = service_id
89
+ if profile.nil?
90
+ if remote_info['profile'].kind_of?(Array)
91
+ resource.service['profile'] = remote_info['profile'][0]
92
+ else
93
+ resource.service['profile'] = remote_info['profile'][0]
94
+ end
95
+ else
96
+ resource.service['profile'] = profile
97
+ end
98
+ end
99
+ return resource
100
+ end
101
+
102
+ protected
103
+ def get_info(svc_id)
104
+ conn = Faraday.new("#{svc_id}/info.json") do |c|
105
+ c.use Faraday::Response::RaiseError
106
+ c.use Faraday::Adapter::NetHttp
107
+ end
108
+ resp = conn.get # raises exceptions that indicate HTTP problems
109
+ JSON.parse(resp.body)
110
+ end
111
+ end
112
+
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,34 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_resource')
2
+
3
+ module IIIF
4
+ module Presentation
5
+ class Layer < AbstractResource
6
+
7
+ TYPE = 'sc:Layer'
8
+
9
+ def required_keys
10
+ super + %w{ @id label }
11
+ end
12
+
13
+ def array_only_keys
14
+ super + %w{ other_content }
15
+ end
16
+
17
+ def string_only_keys
18
+ super + %w{ viewing_direction } # should any of the any_type_keys be here?
19
+ end
20
+
21
+ def initialize(hsh={})
22
+ hsh['@type'] = TYPE unless hsh.has_key? '@type'
23
+ super(hsh)
24
+ end
25
+
26
+ def validate
27
+ # Must all members of otherContent and images must be a URI (string), or
28
+ # can they be inline?
29
+ super
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,39 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_resource')
2
+
3
+ module IIIF
4
+ module Presentation
5
+ class Manifest < AbstractResource
6
+
7
+ TYPE = 'sc:Manifest'
8
+
9
+ def required_keys
10
+ super + %w{ @id label }
11
+ end
12
+
13
+ def string_only_keys
14
+ super + %w{ viewing_direction }
15
+ end
16
+
17
+ def array_only_keys
18
+ super + %w{ sequences structures }
19
+ end
20
+
21
+ def legal_viewing_hint_values
22
+ %w{ individuals paged continuous }
23
+ end
24
+
25
+ def initialize(hsh={})
26
+ hsh['@type'] = TYPE unless hsh.has_key? '@type'
27
+ super(hsh)
28
+ end
29
+
30
+ def validate
31
+ # TODO: check types of sequences and structure members
32
+
33
+ super
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,32 @@
1
+ require File.join(File.dirname(__FILE__), 'sequence')
2
+
3
+ module IIIF
4
+ module Presentation
5
+ class Range < Sequence
6
+
7
+ TYPE = 'sc:Range'
8
+
9
+ def required_keys
10
+ super + %w{ @id label }
11
+ end
12
+
13
+ def array_only_keys
14
+ super + %w{ ranges }
15
+ end
16
+
17
+ def legal_viewing_hint_values
18
+ super + %w{ top }
19
+ end
20
+
21
+ def initialize(hsh={})
22
+ hsh['@type'] = TYPE unless hsh.has_key? '@type'
23
+ super(hsh)
24
+ end
25
+
26
+ def validate
27
+ # Values of the ranges array must be strings
28
+ end
29
+
30
+ end
31
+ end
32
+ end