json-ld 3.2.3 → 3.2.5

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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/lib/json/ld/api.rb +807 -764
  4. data/lib/json/ld/compact.rb +304 -304
  5. data/lib/json/ld/conneg.rb +179 -161
  6. data/lib/json/ld/context.rb +2080 -1945
  7. data/lib/json/ld/expand.rb +745 -666
  8. data/lib/json/ld/extensions.rb +14 -13
  9. data/lib/json/ld/flatten.rb +257 -247
  10. data/lib/json/ld/format.rb +202 -194
  11. data/lib/json/ld/frame.rb +525 -502
  12. data/lib/json/ld/from_rdf.rb +224 -166
  13. data/lib/json/ld/html/nokogiri.rb +123 -121
  14. data/lib/json/ld/html/rexml.rb +151 -147
  15. data/lib/json/ld/reader.rb +107 -100
  16. data/lib/json/ld/resource.rb +224 -205
  17. data/lib/json/ld/streaming_reader.rb +574 -507
  18. data/lib/json/ld/streaming_writer.rb +93 -92
  19. data/lib/json/ld/to_rdf.rb +171 -167
  20. data/lib/json/ld/utils.rb +270 -264
  21. data/lib/json/ld/version.rb +24 -14
  22. data/lib/json/ld/writer.rb +334 -311
  23. data/lib/json/ld.rb +103 -96
  24. metadata +78 -209
  25. data/spec/api_spec.rb +0 -132
  26. data/spec/compact_spec.rb +0 -3482
  27. data/spec/conneg_spec.rb +0 -373
  28. data/spec/context_spec.rb +0 -2036
  29. data/spec/expand_spec.rb +0 -4496
  30. data/spec/flatten_spec.rb +0 -1203
  31. data/spec/format_spec.rb +0 -115
  32. data/spec/frame_spec.rb +0 -2498
  33. data/spec/from_rdf_spec.rb +0 -1005
  34. data/spec/matchers.rb +0 -20
  35. data/spec/rdfstar_spec.rb +0 -25
  36. data/spec/reader_spec.rb +0 -883
  37. data/spec/resource_spec.rb +0 -76
  38. data/spec/spec_helper.rb +0 -281
  39. data/spec/streaming_reader_spec.rb +0 -237
  40. data/spec/streaming_writer_spec.rb +0 -145
  41. data/spec/suite_compact_spec.rb +0 -22
  42. data/spec/suite_expand_spec.rb +0 -36
  43. data/spec/suite_flatten_spec.rb +0 -34
  44. data/spec/suite_frame_spec.rb +0 -29
  45. data/spec/suite_from_rdf_spec.rb +0 -22
  46. data/spec/suite_helper.rb +0 -411
  47. data/spec/suite_html_spec.rb +0 -22
  48. data/spec/suite_http_spec.rb +0 -35
  49. data/spec/suite_remote_doc_spec.rb +0 -22
  50. data/spec/suite_to_rdf_spec.rb +0 -30
  51. data/spec/support/extensions.rb +0 -44
  52. data/spec/test-files/test-1-compacted.jsonld +0 -10
  53. data/spec/test-files/test-1-context.jsonld +0 -7
  54. data/spec/test-files/test-1-expanded.jsonld +0 -5
  55. data/spec/test-files/test-1-input.jsonld +0 -10
  56. data/spec/test-files/test-1-rdf.ttl +0 -8
  57. data/spec/test-files/test-2-compacted.jsonld +0 -20
  58. data/spec/test-files/test-2-context.jsonld +0 -7
  59. data/spec/test-files/test-2-expanded.jsonld +0 -16
  60. data/spec/test-files/test-2-input.jsonld +0 -20
  61. data/spec/test-files/test-2-rdf.ttl +0 -14
  62. data/spec/test-files/test-3-compacted.jsonld +0 -11
  63. data/spec/test-files/test-3-context.jsonld +0 -8
  64. data/spec/test-files/test-3-expanded.jsonld +0 -10
  65. data/spec/test-files/test-3-input.jsonld +0 -11
  66. data/spec/test-files/test-3-rdf.ttl +0 -8
  67. data/spec/test-files/test-4-compacted.jsonld +0 -10
  68. data/spec/test-files/test-4-context.jsonld +0 -7
  69. data/spec/test-files/test-4-expanded.jsonld +0 -6
  70. data/spec/test-files/test-4-input.jsonld +0 -10
  71. data/spec/test-files/test-4-rdf.ttl +0 -5
  72. data/spec/test-files/test-5-compacted.jsonld +0 -13
  73. data/spec/test-files/test-5-context.jsonld +0 -7
  74. data/spec/test-files/test-5-expanded.jsonld +0 -9
  75. data/spec/test-files/test-5-input.jsonld +0 -13
  76. data/spec/test-files/test-5-rdf.ttl +0 -7
  77. data/spec/test-files/test-6-compacted.jsonld +0 -10
  78. data/spec/test-files/test-6-context.jsonld +0 -7
  79. data/spec/test-files/test-6-expanded.jsonld +0 -10
  80. data/spec/test-files/test-6-input.jsonld +0 -10
  81. data/spec/test-files/test-6-rdf.ttl +0 -6
  82. data/spec/test-files/test-7-compacted.jsonld +0 -23
  83. data/spec/test-files/test-7-context.jsonld +0 -4
  84. data/spec/test-files/test-7-expanded.jsonld +0 -20
  85. data/spec/test-files/test-7-input.jsonld +0 -23
  86. data/spec/test-files/test-7-rdf.ttl +0 -14
  87. data/spec/test-files/test-8-compacted.jsonld +0 -34
  88. data/spec/test-files/test-8-context.jsonld +0 -11
  89. data/spec/test-files/test-8-expanded.jsonld +0 -24
  90. data/spec/test-files/test-8-frame.jsonld +0 -18
  91. data/spec/test-files/test-8-framed.jsonld +0 -25
  92. data/spec/test-files/test-8-input.jsonld +0 -30
  93. data/spec/test-files/test-8-rdf.ttl +0 -15
  94. data/spec/test-files/test-9-compacted.jsonld +0 -20
  95. data/spec/test-files/test-9-context.jsonld +0 -13
  96. data/spec/test-files/test-9-expanded.jsonld +0 -14
  97. data/spec/test-files/test-9-input.jsonld +0 -12
  98. data/spec/to_rdf_spec.rb +0 -1551
  99. data/spec/writer_spec.rb +0 -427
@@ -1,188 +1,206 @@
1
- # -*- encoding: utf-8 -*-
2
1
  # frozen_string_literal: true
2
+
3
+ require 'English'
4
+
3
5
  require 'rack'
4
6
  require 'link_header'
5
7
 
6
- module JSON::LD
7
- ##
8
- # Rack middleware for JSON-LD content negotiation.
9
- #
10
- # Uses HTTP Content Negotiation to serialize `Array` and `Hash` results as JSON-LD using 'profile' accept-params to invoke appropriate JSON-LD API methods.
11
- #
12
- # Allows black-listing and white-listing of two-part profiles where the second part denotes a URL of a _context_ or _frame_. (See {JSON::LD::Writer.accept?})
13
- #
14
- # Works along with `rack-linkeddata` for serializing data which is not in the form of an `RDF::Repository`.
15
- #
16
- #
17
- # @example
18
- # use JSON::LD::Rack
19
- #
20
- # @see https://www.w3.org/TR/json-ld11/#iana-considerations
21
- # @see https://www.rubydoc.info/github/rack/rack/master/file/SPEC
22
- class ContentNegotiation
23
- VARY = {'Vary' => 'Accept'}.freeze
24
-
25
- # @return [#call]
26
- attr_reader :app
27
-
8
+ module JSON
9
+ module LD
28
10
  ##
29
- # * Registers JSON::LD::Rack, suitable for Sinatra application
30
- # * adds helpers
11
+ # Rack middleware for JSON-LD content negotiation.
31
12
  #
32
- # @param [Sinatra::Base] app
33
- # @return [void]
34
- def self.registered(app)
35
- options = {}
36
- app.use(JSON::LD::Rack, **options)
37
- end
13
+ # Uses HTTP Content Negotiation to serialize `Array` and `Hash` results as JSON-LD using 'profile' accept-params to invoke appropriate JSON-LD API methods.
14
+ #
15
+ # Allows black-listing and white-listing of two-part profiles where the second part denotes a URL of a _context_ or _frame_. (See {JSON::LD::Writer.accept?})
16
+ #
17
+ # Works along with `rack-linkeddata` for serializing data which is not in the form of an `RDF::Repository`.
18
+ #
19
+ #
20
+ # @example
21
+ # use JSON::LD::Rack
22
+ #
23
+ # @see https://www.w3.org/TR/json-ld11/#iana-considerations
24
+ # @see https://www.rubydoc.info/github/rack/rack/master/file/SPEC
25
+ class ContentNegotiation
26
+ VARY = { 'Vary' => 'Accept' }.freeze
27
+
28
+ # @return [#call]
29
+ attr_reader :app
30
+
31
+ ##
32
+ # * Registers JSON::LD::Rack, suitable for Sinatra application
33
+ # * adds helpers
34
+ #
35
+ # @param [Sinatra::Base] app
36
+ # @return [void]
37
+ def self.registered(app)
38
+ options = {}
39
+ app.use(JSON::LD::Rack, **options)
40
+ end
38
41
 
39
- def initialize(app)
40
- @app = app
41
- end
42
+ def initialize(app)
43
+ @app = app
44
+ end
42
45
 
43
- ##
44
- # Handles a Rack protocol request.
45
- # Parses Accept header to find appropriate mime-type and sets content_type accordingly.
46
- #
47
- # @param [Hash{String => String}] env
48
- # @return [Array(Integer, Hash, #each)] Status, Headers and Body
49
- # @see https://rubydoc.info/github/rack/rack/file/SPEC
50
- def call(env)
51
- response = app.call(env)
52
- body = response[2].respond_to?(:body) ? response[2].body : response[2]
53
- case body
46
+ ##
47
+ # Handles a Rack protocol request.
48
+ # Parses Accept header to find appropriate mime-type and sets content_type accordingly.
49
+ #
50
+ # @param [Hash{String => String}] env
51
+ # @return [Array(Integer, Hash, #each)] Status, Headers and Body
52
+ # @see https://rubydoc.info/github/rack/rack/file/SPEC
53
+ def call(env)
54
+ response = app.call(env)
55
+ body = response[2].respond_to?(:body) ? response[2].body : response[2]
56
+ case body
54
57
  when Array, Hash
55
- response[2] = body # Put it back in the response, it might have been a proxy
58
+ response[2] = body # Put it back in the response, it might have been a proxy
56
59
  serialize(env, *response)
57
60
  else response
58
- end
59
- end
60
-
61
- ##
62
- # Serializes objects as JSON-LD. Defaults to expanded form, other forms
63
- # determined by presense of `profile` in accept-parms.
64
- #
65
- # @param [Hash{String => String}] env
66
- # @param [Integer] status
67
- # @param [Hash{String => Object}] headers
68
- # @param [RDF::Enumerable] body
69
- # @return [Array(Integer, Hash, #each)] Status, Headers and Body
70
- def serialize(env, status, headers, body)
71
- # This will only return json-ld content types, possibly with parameters
72
- content_types = parse_accept_header(env['HTTP_ACCEPT'] || 'application/ld+json')
73
- content_types = content_types.select do |content_type|
74
- _, *params = content_type.split(';').map(&:strip)
75
- accept_params = params.inject({}) do |memo, pv|
76
- p, v = pv.split('=').map(&:strip)
77
- memo.merge(p.downcase.to_sym => v.sub(/^["']?([^"']*)["']?$/, '\1'))
78
61
  end
79
- JSON::LD::Writer.accept?(accept_params)
80
62
  end
81
- if content_types.empty?
82
- not_acceptable("No appropriate combinaion of media-type and parameters found")
83
- else
84
- ct, *params = content_types.first.split(';').map(&:strip)
85
- accept_params = params.inject({}) do |memo, pv|
86
- p, v = pv.split('=').map(&:strip)
87
- memo.merge(p.downcase.to_sym => v.sub(/^["']?([^"']*)["']?$/, '\1'))
88
- end
89
-
90
- # Determine API method from profile
91
- profile = accept_params[:profile].to_s.split(' ')
92
-
93
- # Get context from Link header
94
- links = LinkHeader.parse(env['HTTP_LINK'])
95
- context = links.find_link(['rel', JSON_LD_NS+"context"]).href rescue nil
96
- frame = links.find_link(['rel', JSON_LD_NS+"frame"]).href rescue nil
97
63
 
98
- if profile.include?(JSON_LD_NS+"framed") && frame.nil?
99
- return not_acceptable("framed profile without a frame")
64
+ ##
65
+ # Serializes objects as JSON-LD. Defaults to expanded form, other forms
66
+ # determined by presense of `profile` in accept-parms.
67
+ #
68
+ # @param [Hash{String => String}] env
69
+ # @param [Integer] status
70
+ # @param [Hash{String => Object}] headers
71
+ # @param [RDF::Enumerable] body
72
+ # @return [Array(Integer, Hash, #each)] Status, Headers and Body
73
+ def serialize(env, status, headers, body)
74
+ # This will only return json-ld content types, possibly with parameters
75
+ content_types = parse_accept_header(env['HTTP_ACCEPT'] || 'application/ld+json')
76
+ content_types = content_types.select do |content_type|
77
+ _, *params = content_type.split(';').map(&:strip)
78
+ accept_params = params.inject({}) do |memo, pv|
79
+ p, v = pv.split('=').map(&:strip)
80
+ memo.merge(p.downcase.to_sym => v.sub(/^["']?([^"']*)["']?$/, '\1'))
81
+ end
82
+ JSON::LD::Writer.accept?(accept_params)
100
83
  end
101
-
102
- # accept? already determined that there are appropriate contexts
103
- # If profile also includes a URI which is not a namespace, use it for compaction.
104
- context ||= Writer.default_context if profile.include?(JSON_LD_NS+"compacted")
105
-
106
- result = if profile.include?(JSON_LD_NS+"flattened")
107
- API.flatten(body, context)
108
- elsif profile.include?(JSON_LD_NS+"framed")
109
- API.frame(body, frame)
110
- elsif context
111
- API.compact(body, context)
112
- elsif profile.include?(JSON_LD_NS+"expanded")
113
- API.expand(body)
84
+ if content_types.empty?
85
+ not_acceptable("No appropriate combinaion of media-type and parameters found")
114
86
  else
115
- body
87
+ ct, *params = content_types.first.split(';').map(&:strip)
88
+ accept_params = params.inject({}) do |memo, pv|
89
+ p, v = pv.split('=').map(&:strip)
90
+ memo.merge(p.downcase.to_sym => v.sub(/^["']?([^"']*)["']?$/, '\1'))
91
+ end
92
+
93
+ # Determine API method from profile
94
+ profile = accept_params[:profile].to_s.split
95
+
96
+ # Get context from Link header
97
+ links = LinkHeader.parse(env['HTTP_LINK'])
98
+ context = begin
99
+ links.find_link(['rel', JSON_LD_NS + "context"]).href
100
+ rescue StandardError
101
+ nil
102
+ end
103
+ frame = begin
104
+ links.find_link(['rel', JSON_LD_NS + "frame"]).href
105
+ rescue StandardError
106
+ nil
107
+ end
108
+
109
+ if profile.include?(JSON_LD_NS + "framed") && frame.nil?
110
+ return not_acceptable("framed profile without a frame")
111
+ end
112
+
113
+ # accept? already determined that there are appropriate contexts
114
+ # If profile also includes a URI which is not a namespace, use it for compaction.
115
+ context ||= Writer.default_context if profile.include?(JSON_LD_NS + "compacted")
116
+
117
+ result = if profile.include?(JSON_LD_NS + "flattened")
118
+ API.flatten(body, context)
119
+ elsif profile.include?(JSON_LD_NS + "framed")
120
+ API.frame(body, frame)
121
+ elsif context
122
+ API.compact(body, context)
123
+ elsif profile.include?(JSON_LD_NS + "expanded")
124
+ API.expand(body)
125
+ else
126
+ body
127
+ end
128
+
129
+ headers = headers.merge(VARY).merge('Content-Type' => ct)
130
+ [status, headers, [result.to_json]]
116
131
  end
117
-
118
- headers = headers.merge(VARY).merge('Content-Type' => ct)
119
- [status, headers, [result.to_json]]
132
+ rescue StandardError
133
+ http_error(500, $ERROR_INFO.message)
120
134
  end
121
- rescue
122
- http_error(500, $!.message)
123
- end
124
135
 
125
- protected
136
+ protected
137
+
138
+ ##
139
+ # Parses an HTTP `Accept` header, returning an array of MIME content
140
+ # types ordered by the precedence rules defined in HTTP/1.1 §14.1.
141
+ #
142
+ # @param [String, #to_s] header
143
+ # @return [Array<String>]
144
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
145
+ def parse_accept_header(header)
146
+ entries = header.to_s.split(',')
147
+ entries = entries
148
+ .map { |e| accept_entry(e) }
149
+ .sort_by(&:last)
150
+ .map(&:first)
151
+ entries.map { |e| find_content_type_for_media_range(e) }.compact
152
+ end
126
153
 
127
- ##
128
- # Parses an HTTP `Accept` header, returning an array of MIME content
129
- # types ordered by the precedence rules defined in HTTP/1.1 §14.1.
130
- #
131
- # @param [String, #to_s] header
132
- # @return [Array<String>]
133
- # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
134
- def parse_accept_header(header)
135
- entries = header.to_s.split(',')
136
- entries = entries.
137
- map { |e| accept_entry(e) }.
138
- sort_by(&:last).
139
- map(&:first)
140
- entries.map { |e| find_content_type_for_media_range(e) }.compact
141
- end
154
+ # Returns an array of quality, number of '*' in content-type, and number of non-'q' parameters
155
+ def accept_entry(entry)
156
+ type, *options = entry.split(';').map(&:strip)
157
+ quality = 0 # we sort smallest first
158
+ options.delete_if { |e| quality = 1 - e[2..].to_f if e.start_with? 'q=' }
159
+ [options.unshift(type).join(';'), [quality, type.count('*'), 1 - options.size]]
160
+ end
142
161
 
143
- # Returns an array of quality, number of '*' in content-type, and number of non-'q' parameters
144
- def accept_entry(entry)
145
- type, *options = entry.split(';').map(&:strip)
146
- quality = 0 # we sort smallest first
147
- options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' }
148
- [options.unshift(type).join(';'), [quality, type.count('*'), 1 - options.size]]
149
- end
162
+ ##
163
+ # Returns a content type appropriate for the given `media_range`,
164
+ # returns `nil` if `media_range` contains a wildcard subtype
165
+ # that is not mapped.
166
+ #
167
+ # @param [String, #to_s] media_range
168
+ # @return [String, nil]
169
+ def find_content_type_for_media_range(media_range)
170
+ media_range = media_range.sub('*/*', 'application/ld+json') if media_range.to_s.start_with?('*/*')
171
+ if media_range.to_s.start_with?('application/*')
172
+ media_range = media_range.sub('application/*',
173
+ 'application/ld+json')
174
+ end
175
+ if media_range.to_s.start_with?('application/json')
176
+ media_range = media_range.sub('application/json',
177
+ 'application/ld+json')
178
+ end
150
179
 
151
- ##
152
- # Returns a content type appropriate for the given `media_range`,
153
- # returns `nil` if `media_range` contains a wildcard subtype
154
- # that is not mapped.
155
- #
156
- # @param [String, #to_s] media_range
157
- # @return [String, nil]
158
- def find_content_type_for_media_range(media_range)
159
- media_range = media_range.sub('*/*', 'application/ld+json') if media_range.to_s.start_with?('*/*')
160
- media_range = media_range.sub('application/*', 'application/ld+json') if media_range.to_s.start_with?('application/*')
161
- media_range = media_range.sub('application/json', 'application/ld+json') if media_range.to_s.start_with?('application/json')
162
-
163
- media_range.start_with?('application/ld+json') ? media_range : nil
164
- end
180
+ media_range.start_with?('application/ld+json') ? media_range : nil
181
+ end
165
182
 
166
- ##
167
- # Outputs an HTTP `406 Not Acceptable` response.
168
- #
169
- # @param [String, #to_s] message
170
- # @return [Array(Integer, Hash, #each)]
171
- def not_acceptable(message = nil)
172
- http_error(406, message, VARY)
173
- end
183
+ ##
184
+ # Outputs an HTTP `406 Not Acceptable` response.
185
+ #
186
+ # @param [String, #to_s] message
187
+ # @return [Array(Integer, Hash, #each)]
188
+ def not_acceptable(message = nil)
189
+ http_error(406, message, VARY)
190
+ end
174
191
 
175
- ##
176
- # Outputs an HTTP `4xx` or `5xx` response.
177
- #
178
- # @param [Integer, #to_i] code
179
- # @param [String, #to_s] message
180
- # @param [Hash{String => String}] headers
181
- # @return [Array(Integer, Hash, #each)]
182
- def http_error(code, message = nil, headers = {})
183
- message = [code, Rack::Utils::HTTP_STATUS_CODES[code]].join(' ') +
184
- (message.nil? ? "\n" : " (#{message})\n")
185
- [code, {'Content-Type' => "text/plain"}.merge(headers), [message]]
192
+ ##
193
+ # Outputs an HTTP `4xx` or `5xx` response.
194
+ #
195
+ # @param [Integer, #to_i] code
196
+ # @param [String, #to_s] message
197
+ # @param [Hash{String => String}] headers
198
+ # @return [Array(Integer, Hash, #each)]
199
+ def http_error(code, message = nil, headers = {})
200
+ message = [code, Rack::Utils::HTTP_STATUS_CODES[code]].join(' ') +
201
+ (message.nil? ? "\n" : " (#{message})\n")
202
+ [code, { 'Content-Type' => "text/plain" }.merge(headers), [message]]
203
+ end
186
204
  end
187
205
  end
188
206
  end