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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/json/ld/api.rb +807 -764
- data/lib/json/ld/compact.rb +304 -304
- data/lib/json/ld/conneg.rb +179 -161
- data/lib/json/ld/context.rb +2080 -1945
- data/lib/json/ld/expand.rb +745 -666
- data/lib/json/ld/extensions.rb +14 -13
- data/lib/json/ld/flatten.rb +257 -247
- data/lib/json/ld/format.rb +202 -194
- data/lib/json/ld/frame.rb +525 -502
- data/lib/json/ld/from_rdf.rb +224 -166
- data/lib/json/ld/html/nokogiri.rb +123 -121
- data/lib/json/ld/html/rexml.rb +151 -147
- data/lib/json/ld/reader.rb +107 -100
- data/lib/json/ld/resource.rb +224 -205
- data/lib/json/ld/streaming_reader.rb +574 -507
- data/lib/json/ld/streaming_writer.rb +93 -92
- data/lib/json/ld/to_rdf.rb +171 -167
- data/lib/json/ld/utils.rb +270 -264
- data/lib/json/ld/version.rb +24 -14
- data/lib/json/ld/writer.rb +334 -311
- data/lib/json/ld.rb +103 -96
- metadata +78 -209
- data/spec/api_spec.rb +0 -132
- data/spec/compact_spec.rb +0 -3482
- data/spec/conneg_spec.rb +0 -373
- data/spec/context_spec.rb +0 -2036
- data/spec/expand_spec.rb +0 -4496
- data/spec/flatten_spec.rb +0 -1203
- data/spec/format_spec.rb +0 -115
- data/spec/frame_spec.rb +0 -2498
- data/spec/from_rdf_spec.rb +0 -1005
- data/spec/matchers.rb +0 -20
- data/spec/rdfstar_spec.rb +0 -25
- data/spec/reader_spec.rb +0 -883
- data/spec/resource_spec.rb +0 -76
- data/spec/spec_helper.rb +0 -281
- data/spec/streaming_reader_spec.rb +0 -237
- data/spec/streaming_writer_spec.rb +0 -145
- data/spec/suite_compact_spec.rb +0 -22
- data/spec/suite_expand_spec.rb +0 -36
- data/spec/suite_flatten_spec.rb +0 -34
- data/spec/suite_frame_spec.rb +0 -29
- data/spec/suite_from_rdf_spec.rb +0 -22
- data/spec/suite_helper.rb +0 -411
- data/spec/suite_html_spec.rb +0 -22
- data/spec/suite_http_spec.rb +0 -35
- data/spec/suite_remote_doc_spec.rb +0 -22
- data/spec/suite_to_rdf_spec.rb +0 -30
- data/spec/support/extensions.rb +0 -44
- data/spec/test-files/test-1-compacted.jsonld +0 -10
- data/spec/test-files/test-1-context.jsonld +0 -7
- data/spec/test-files/test-1-expanded.jsonld +0 -5
- data/spec/test-files/test-1-input.jsonld +0 -10
- data/spec/test-files/test-1-rdf.ttl +0 -8
- data/spec/test-files/test-2-compacted.jsonld +0 -20
- data/spec/test-files/test-2-context.jsonld +0 -7
- data/spec/test-files/test-2-expanded.jsonld +0 -16
- data/spec/test-files/test-2-input.jsonld +0 -20
- data/spec/test-files/test-2-rdf.ttl +0 -14
- data/spec/test-files/test-3-compacted.jsonld +0 -11
- data/spec/test-files/test-3-context.jsonld +0 -8
- data/spec/test-files/test-3-expanded.jsonld +0 -10
- data/spec/test-files/test-3-input.jsonld +0 -11
- data/spec/test-files/test-3-rdf.ttl +0 -8
- data/spec/test-files/test-4-compacted.jsonld +0 -10
- data/spec/test-files/test-4-context.jsonld +0 -7
- data/spec/test-files/test-4-expanded.jsonld +0 -6
- data/spec/test-files/test-4-input.jsonld +0 -10
- data/spec/test-files/test-4-rdf.ttl +0 -5
- data/spec/test-files/test-5-compacted.jsonld +0 -13
- data/spec/test-files/test-5-context.jsonld +0 -7
- data/spec/test-files/test-5-expanded.jsonld +0 -9
- data/spec/test-files/test-5-input.jsonld +0 -13
- data/spec/test-files/test-5-rdf.ttl +0 -7
- data/spec/test-files/test-6-compacted.jsonld +0 -10
- data/spec/test-files/test-6-context.jsonld +0 -7
- data/spec/test-files/test-6-expanded.jsonld +0 -10
- data/spec/test-files/test-6-input.jsonld +0 -10
- data/spec/test-files/test-6-rdf.ttl +0 -6
- data/spec/test-files/test-7-compacted.jsonld +0 -23
- data/spec/test-files/test-7-context.jsonld +0 -4
- data/spec/test-files/test-7-expanded.jsonld +0 -20
- data/spec/test-files/test-7-input.jsonld +0 -23
- data/spec/test-files/test-7-rdf.ttl +0 -14
- data/spec/test-files/test-8-compacted.jsonld +0 -34
- data/spec/test-files/test-8-context.jsonld +0 -11
- data/spec/test-files/test-8-expanded.jsonld +0 -24
- data/spec/test-files/test-8-frame.jsonld +0 -18
- data/spec/test-files/test-8-framed.jsonld +0 -25
- data/spec/test-files/test-8-input.jsonld +0 -30
- data/spec/test-files/test-8-rdf.ttl +0 -15
- data/spec/test-files/test-9-compacted.jsonld +0 -20
- data/spec/test-files/test-9-context.jsonld +0 -13
- data/spec/test-files/test-9-expanded.jsonld +0 -14
- data/spec/test-files/test-9-input.jsonld +0 -12
- data/spec/to_rdf_spec.rb +0 -1551
- data/spec/writer_spec.rb +0 -427
data/lib/json/ld/frame.rb
CHANGED
@@ -1,224 +1,234 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
# frozen_string_literal: true
|
2
|
+
|
3
3
|
require 'set'
|
4
4
|
|
5
|
-
module JSON
|
6
|
-
module
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
# Get link for current graph
|
38
|
-
link = state[:link][state[:graph]] ||= {}
|
39
|
-
|
40
|
-
# Create a set of matched subjects by filtering subjects by checking the map of flattened subjects against frame
|
41
|
-
# This gives us a hash of objects indexed by @id
|
42
|
-
matches = filter_subjects(state, subjects, frame, flags)
|
43
|
-
|
44
|
-
# For each id and node from the set of matched subjects ordered by id
|
45
|
-
matches.keys.opt_sort(ordered: ordered).each do |id|
|
46
|
-
subject = matches[id]
|
47
|
-
|
48
|
-
# Note: In order to treat each top-level match as a compartmentalized result, clear the unique embedded subjects map when the property is nil, which only occurs at the top-level.
|
49
|
-
if property.nil?
|
50
|
-
state[:uniqueEmbeds] = {state[:graph] => {}}
|
51
|
-
else
|
52
|
-
state[:uniqueEmbeds][state[:graph]] ||= {}
|
53
|
-
end
|
5
|
+
module JSON
|
6
|
+
module LD
|
7
|
+
module Frame
|
8
|
+
include Utils
|
9
|
+
|
10
|
+
##
|
11
|
+
# Frame input. Input is expected in expanded form, but frame is in compacted form.
|
12
|
+
#
|
13
|
+
# @param [Hash{Symbol => Object}] state
|
14
|
+
# Current framing state
|
15
|
+
# @param [Array<String>] subjects
|
16
|
+
# The subjects to filter
|
17
|
+
# @param [Hash{String => Object}] frame
|
18
|
+
# @param [String] property (nil)
|
19
|
+
# The parent property.
|
20
|
+
# @param [Hash{String => Object}] parent (nil)
|
21
|
+
# Parent subject or top-level array
|
22
|
+
# @param [Boolean] ordered (true)
|
23
|
+
# Ensure output objects have keys ordered properly
|
24
|
+
# @param [Hash{Symbol => Object}] options ({})
|
25
|
+
# @raise [JSON::LD::InvalidFrame]
|
26
|
+
def frame(state, subjects, frame, parent: nil, property: nil, ordered: false, **options)
|
27
|
+
# Validate the frame
|
28
|
+
validate_frame(frame)
|
29
|
+
frame = frame.first if frame.is_a?(Array)
|
30
|
+
|
31
|
+
# Get values for embedOn and explicitOn
|
32
|
+
flags = {
|
33
|
+
embed: get_frame_flag(frame, options, :embed),
|
34
|
+
explicit: get_frame_flag(frame, options, :explicit),
|
35
|
+
requireAll: get_frame_flag(frame, options, :requireAll)
|
36
|
+
}
|
54
37
|
|
55
|
-
|
56
|
-
|
57
|
-
add_frame_output(parent, property, link[id])
|
58
|
-
next
|
59
|
-
end
|
38
|
+
# Get link for current graph
|
39
|
+
link = state[:link][state[:graph]] ||= {}
|
60
40
|
|
61
|
-
|
62
|
-
|
41
|
+
# Create a set of matched subjects by filtering subjects by checking the map of flattened subjects against frame
|
42
|
+
# This gives us a hash of objects indexed by @id
|
43
|
+
matches = filter_subjects(state, subjects, frame, flags)
|
63
44
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
45
|
+
# For each id and node from the set of matched subjects ordered by id
|
46
|
+
matches.keys.opt_sort(ordered: ordered).each do |id|
|
47
|
+
subject = matches[id]
|
68
48
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
add_frame_output(parent, property, output)
|
76
|
-
next
|
77
|
-
elsif state[:embedded] &&
|
78
|
-
%w(@first @once).include?(flags[:embed]) &&
|
79
|
-
state[:uniqueEmbeds][state[:graph]].key?(id)
|
49
|
+
# NOTE: In order to treat each top-level match as a compartmentalized result, clear the unique embedded subjects map when the property is nil, which only occurs at the top-level.
|
50
|
+
if property.nil?
|
51
|
+
state[:uniqueEmbeds] = { state[:graph] => {} }
|
52
|
+
else
|
53
|
+
state[:uniqueEmbeds][state[:graph]] ||= {}
|
54
|
+
end
|
80
55
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
# if only the last match should be embedded
|
87
|
-
# remove any existing embed
|
88
|
-
remove_embed(state, id) if state[:uniqueEmbeds][state[:graph]].include?(id)
|
89
|
-
end
|
56
|
+
if flags[:embed] == '@link' && link.key?(id)
|
57
|
+
# add existing linked subject
|
58
|
+
add_frame_output(parent, property, link[id])
|
59
|
+
next
|
60
|
+
end
|
90
61
|
|
91
|
-
|
92
|
-
|
93
|
-
property: property
|
94
|
-
}
|
62
|
+
output = { '@id' => id }
|
63
|
+
link[id] = output
|
95
64
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
# 2. if it doesn't exist and state.graph !== "@merged", recurse
|
104
|
-
# 3. if "@merged" then don't recurse
|
105
|
-
# 4. if "@default" then don't recurse
|
106
|
-
# 5. recurse
|
107
|
-
recurse, subframe = false, nil
|
108
|
-
if !frame.key?('@graph')
|
109
|
-
recurse, subframe = (state[:graph] != '@merged'), {}
|
110
|
-
else
|
111
|
-
subframe = frame['@graph'].first
|
112
|
-
recurse = !(id == '@merged' || id == '@default')
|
113
|
-
subframe = {} unless subframe.is_a?(Hash)
|
65
|
+
if %w[@first @last].include?(flags[:embed]) && context.processingMode('json-ld-1.1')
|
66
|
+
if @options[:validate]
|
67
|
+
raise JSON::LD::JsonLdError::InvalidEmbedValue,
|
68
|
+
"#{flags[:embed]} is not a valid value of @embed in 1.1 mode"
|
69
|
+
end
|
70
|
+
|
71
|
+
warn "[DEPRECATION] #{flags[:embed]} is not a valid value of @embed in 1.1 mode.\n"
|
114
72
|
end
|
115
73
|
|
116
|
-
if
|
117
|
-
|
74
|
+
if !state[:embedded] && state[:uniqueEmbeds][state[:graph]].key?(id)
|
75
|
+
# Skip adding this node object to the top-level, as it was included in another node object
|
76
|
+
next
|
77
|
+
elsif state[:embedded] &&
|
78
|
+
(flags[:embed] == '@never' || creates_circular_reference(subject, state[:graph], state[:subjectStack]))
|
79
|
+
# if embed is @never or if a circular reference would be created by an embed, the subject cannot be embedded, just add the reference; note that a circular reference won't occur when the embed flag is `@link` as the above check will short-circuit before reaching this point
|
80
|
+
add_frame_output(parent, property, output)
|
81
|
+
next
|
82
|
+
elsif state[:embedded] &&
|
83
|
+
%w[@first @once].include?(flags[:embed]) &&
|
84
|
+
state[:uniqueEmbeds][state[:graph]].key?(id)
|
85
|
+
|
86
|
+
# if only the first match should be embedded
|
87
|
+
# Embed unless already embedded
|
88
|
+
add_frame_output(parent, property, output)
|
89
|
+
next
|
90
|
+
elsif flags[:embed] == '@last'
|
91
|
+
# if only the last match should be embedded
|
92
|
+
# remove any existing embed
|
93
|
+
remove_embed(state, id) if state[:uniqueEmbeds][state[:graph]].include?(id)
|
118
94
|
end
|
119
|
-
end
|
120
95
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
96
|
+
state[:uniqueEmbeds][state[:graph]][id] = {
|
97
|
+
parent: parent,
|
98
|
+
property: property
|
99
|
+
}
|
100
|
+
|
101
|
+
# push matching subject onto stack to enable circular embed checks
|
102
|
+
state[:subjectStack] << { subject: subject, graph: state[:graph] }
|
103
|
+
|
104
|
+
# Subject is also the name of a graph
|
105
|
+
if state[:graphMap].key?(id)
|
106
|
+
# check frame's "@graph" to see what to do next
|
107
|
+
# 1. if it doesn't exist and state.graph === "@merged", don't recurse
|
108
|
+
# 2. if it doesn't exist and state.graph !== "@merged", recurse
|
109
|
+
# 3. if "@merged" then don't recurse
|
110
|
+
# 4. if "@default" then don't recurse
|
111
|
+
# 5. recurse
|
112
|
+
recurse = false
|
113
|
+
subframe = nil
|
114
|
+
if frame.key?('@graph')
|
115
|
+
subframe = frame['@graph'].first
|
116
|
+
recurse = !['@merged', '@default'].include?(id)
|
117
|
+
subframe = {} unless subframe.is_a?(Hash)
|
118
|
+
else
|
119
|
+
recurse = (state[:graph] != '@merged')
|
120
|
+
subframe = {}
|
121
|
+
end
|
125
122
|
|
126
|
-
|
127
|
-
|
128
|
-
|
123
|
+
if recurse
|
124
|
+
frame(state.merge(graph: id, embedded: false), state[:graphMap][id].keys, [subframe], parent: output,
|
125
|
+
property: '@graph', **options)
|
126
|
+
end
|
127
|
+
end
|
129
128
|
|
130
|
-
#
|
131
|
-
if
|
132
|
-
|
133
|
-
|
129
|
+
# If frame has `@included`, recurse over its sub-frame
|
130
|
+
if frame['@included']
|
131
|
+
frame(state.merge(embedded: false), subjects, frame['@included'], parent: output, property: '@included',
|
132
|
+
**options)
|
134
133
|
end
|
135
134
|
|
136
|
-
#
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
135
|
+
# iterate over subject properties in order
|
136
|
+
subject.keys.opt_sort(ordered: ordered).each do |prop|
|
137
|
+
objects = subject[prop]
|
138
|
+
|
139
|
+
# copy keywords to output
|
140
|
+
if prop.start_with?('@')
|
141
|
+
output[prop] = objects.dup
|
142
|
+
next
|
143
|
+
end
|
144
|
+
|
145
|
+
# explicit is on and property isn't in frame, skip processing
|
146
|
+
next if flags[:explicit] && !frame.key?(prop)
|
147
|
+
|
148
|
+
# add objects
|
149
|
+
objects.each do |o|
|
150
|
+
subframe = Array(frame[prop]).first || create_implicit_frame(flags)
|
151
|
+
|
152
|
+
if list?(o)
|
153
|
+
subframe = frame[prop].first['@list'] if Array(frame[prop]).first.is_a?(Hash)
|
154
|
+
subframe ||= create_implicit_frame(flags)
|
155
|
+
# add empty list
|
156
|
+
list = { '@list' => [] }
|
157
|
+
add_frame_output(output, prop, list)
|
158
|
+
|
159
|
+
src = o['@list']
|
160
|
+
src.each do |oo|
|
161
|
+
if node_reference?(oo)
|
162
|
+
frame(state.merge(embedded: true), [oo['@id']], subframe, parent: list, property: '@list',
|
163
|
+
**options)
|
164
|
+
else
|
165
|
+
add_frame_output(list, '@list', oo.dup)
|
166
|
+
end
|
157
167
|
end
|
168
|
+
elsif node_reference?(o)
|
169
|
+
# recurse into subject reference
|
170
|
+
frame(state.merge(embedded: true), [o['@id']], subframe, parent: output, property: prop, **options)
|
171
|
+
elsif value_match?(subframe, o)
|
172
|
+
# Include values if they match
|
173
|
+
add_frame_output(output, prop, o.dup)
|
158
174
|
end
|
159
|
-
when node_reference?(o)
|
160
|
-
# recurse into subject reference
|
161
|
-
frame(state.merge(embedded: true), [o['@id']], subframe, parent: output, property: prop, **options)
|
162
|
-
when value_match?(subframe, o)
|
163
|
-
# Include values if they match
|
164
|
-
add_frame_output(output, prop, o.dup)
|
165
175
|
end
|
166
176
|
end
|
167
|
-
end
|
168
177
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
178
|
+
# handle defaults in order
|
179
|
+
frame.keys.opt_sort(ordered: ordered).each do |prop|
|
180
|
+
if prop == '@type' && frame[prop].first.is_a?(Hash) && frame[prop].first.keys == %w[@default]
|
181
|
+
# Treat this as a default
|
182
|
+
elsif prop.start_with?('@')
|
183
|
+
next
|
184
|
+
end
|
176
185
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
186
|
+
# if omit default is off, then include default values for properties that appear in the next frame but are not in the matching subject
|
187
|
+
n = frame[prop].first || {}
|
188
|
+
omit_default_on = get_frame_flag(n, options, :omitDefault)
|
189
|
+
if !omit_default_on && !output[prop]
|
190
|
+
preserve = as_array(n.fetch('@default', '@null').dup)
|
191
|
+
output[prop] = [{ '@preserve' => preserve }]
|
192
|
+
end
|
183
193
|
end
|
184
|
-
end
|
185
194
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
195
|
+
# If frame has @reverse, embed identified nodes having this subject as a value of the associated property.
|
196
|
+
frame.fetch('@reverse', {}).each do |reverse_prop, subframe|
|
197
|
+
state[:subjects].each do |r_id, node|
|
198
|
+
next unless Array(node[reverse_prop]).any? { |v| v['@id'] == id }
|
199
|
+
|
190
200
|
# Node has property referencing this subject
|
191
201
|
# recurse into reference
|
192
202
|
(output['@reverse'] ||= {})[reverse_prop] ||= []
|
193
|
-
frame(state.merge(embedded: true), [r_id], subframe, parent: output['@reverse'][reverse_prop],
|
203
|
+
frame(state.merge(embedded: true), [r_id], subframe, parent: output['@reverse'][reverse_prop],
|
204
|
+
property: property, **options)
|
194
205
|
end
|
195
206
|
end
|
196
|
-
end
|
197
207
|
|
198
|
-
|
199
|
-
|
208
|
+
# add output to parent
|
209
|
+
add_frame_output(parent, property, output)
|
200
210
|
|
201
|
-
|
202
|
-
|
211
|
+
# pop matching subject from circular ref-checking stack
|
212
|
+
state[:subjectStack].pop
|
213
|
+
end
|
214
|
+
# end
|
203
215
|
end
|
204
|
-
#end
|
205
|
-
end
|
206
216
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
217
|
+
##
|
218
|
+
# Recursively find and count blankNode identifiers.
|
219
|
+
# @return [Hash{String => Integer}]
|
220
|
+
def count_blank_node_identifiers(input)
|
221
|
+
{}.tap do |results|
|
222
|
+
count_blank_node_identifiers_internal(input, results)
|
223
|
+
end
|
213
224
|
end
|
214
|
-
end
|
215
225
|
|
216
|
-
|
217
|
-
|
226
|
+
def count_blank_node_identifiers_internal(input, results)
|
227
|
+
case input
|
218
228
|
when Array
|
219
|
-
input.each {|o| count_blank_node_identifiers_internal(o, results)}
|
229
|
+
input.each { |o| count_blank_node_identifiers_internal(o, results) }
|
220
230
|
when Hash
|
221
|
-
input.each do |
|
231
|
+
input.each do |_k, v|
|
222
232
|
count_blank_node_identifiers_internal(v, results)
|
223
233
|
end
|
224
234
|
when String
|
@@ -226,377 +236,390 @@ module JSON::LD
|
|
226
236
|
results[input] ||= 0
|
227
237
|
results[input] += 1
|
228
238
|
end
|
239
|
+
end
|
229
240
|
end
|
230
|
-
end
|
231
241
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
242
|
+
##
|
243
|
+
# Prune BNode identifiers recursively
|
244
|
+
#
|
245
|
+
# @param [Array, Hash] input
|
246
|
+
# @param [Array<String>] bnodes_to_clear
|
247
|
+
# @return [Array, Hash]
|
248
|
+
def prune_bnodes(input, bnodes_to_clear)
|
249
|
+
case input
|
250
|
+
when Array
|
251
|
+
# If, after replacement, an array contains only the value null remove the value, leaving an empty array.
|
252
|
+
input.map { |o| prune_bnodes(o, bnodes_to_clear) }.compact
|
253
|
+
when Hash
|
254
|
+
output = {}
|
255
|
+
input.each do |key, value|
|
256
|
+
if context.expand_iri(key) == '@id' && bnodes_to_clear.include?(value)
|
257
|
+
# Don't add this to output, as it is pruned as being superfluous
|
258
|
+
else
|
259
|
+
output[key] = prune_bnodes(value, bnodes_to_clear)
|
260
|
+
end
|
250
261
|
end
|
262
|
+
output
|
263
|
+
else
|
264
|
+
input
|
251
265
|
end
|
252
|
-
output
|
253
|
-
else
|
254
|
-
input
|
255
266
|
end
|
256
|
-
result
|
257
|
-
end
|
258
267
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
268
|
+
##
|
269
|
+
# Replace @preserve keys with the values, also replace @null with null.
|
270
|
+
#
|
271
|
+
# @param [Array, Hash] input
|
272
|
+
# @return [Array, Hash]
|
273
|
+
def cleanup_preserve(input)
|
274
|
+
case input
|
275
|
+
when Array
|
276
|
+
input.map! { |o| cleanup_preserve(o) }
|
277
|
+
when Hash
|
278
|
+
if input.key?('@preserve')
|
279
|
+
# Replace with the content of `@preserve`
|
280
|
+
cleanup_preserve(input['@preserve'].first)
|
281
|
+
else
|
282
|
+
input.transform_values do |v|
|
283
|
+
cleanup_preserve(v)
|
284
|
+
end
|
276
285
|
end
|
286
|
+
else
|
287
|
+
input
|
277
288
|
end
|
278
|
-
else
|
279
|
-
input
|
280
289
|
end
|
281
|
-
end
|
282
290
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
291
|
+
##
|
292
|
+
# Replace `@null` with `null`, removing it from arrays.
|
293
|
+
#
|
294
|
+
# @param [Array, Hash] input
|
295
|
+
# @return [Array, Hash]
|
296
|
+
def cleanup_null(input)
|
297
|
+
case input
|
298
|
+
when Array
|
299
|
+
# If, after replacement, an array contains only the value null remove the value, leaving an empty array.
|
300
|
+
input.map! { |o| cleanup_null(o) }.compact
|
301
|
+
when Hash
|
302
|
+
input.transform_values do |v|
|
303
|
+
cleanup_null(v)
|
304
|
+
end
|
305
|
+
when '@null'
|
306
|
+
# If the value from the key-pair is @null, replace the value with null
|
307
|
+
nil
|
308
|
+
else
|
309
|
+
input
|
296
310
|
end
|
297
|
-
when '@null'
|
298
|
-
# If the value from the key-pair is @null, replace the value with null
|
299
|
-
nil
|
300
|
-
else
|
301
|
-
input
|
302
311
|
end
|
303
|
-
result
|
304
|
-
end
|
305
312
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
313
|
+
private
|
314
|
+
|
315
|
+
##
|
316
|
+
# Returns a map of all of the subjects that match a parsed frame.
|
317
|
+
#
|
318
|
+
# @param [Hash{Symbol => Object}] state
|
319
|
+
# Current framing state
|
320
|
+
# @param [Array<String>] subjects
|
321
|
+
# The subjects to filter
|
322
|
+
# @param [Hash{String => Object}] frame
|
323
|
+
# @param [Hash{Symbol => String}] flags the frame flags.
|
324
|
+
#
|
325
|
+
# @return all of the matched subjects.
|
326
|
+
def filter_subjects(state, subjects, frame, flags)
|
327
|
+
subjects.each_with_object({}) do |id, memo|
|
328
|
+
subject = state[:graphMap][state[:graph]][id]
|
329
|
+
memo[id] = subject if filter_subject(subject, frame, state, flags)
|
330
|
+
end
|
323
331
|
end
|
324
|
-
end
|
325
332
|
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
else
|
356
|
-
# Match on specific @id
|
357
|
-
ids.include?(subject['@id'])
|
358
|
-
end
|
359
|
-
return match_this if !flags[:requireAll]
|
360
|
-
when '@type'
|
361
|
-
# No longer a wildcard pattern
|
362
|
-
wildcard = false
|
363
|
-
|
364
|
-
match_this = case v
|
365
|
-
when []
|
366
|
-
# Don't match with any @type
|
367
|
-
return false if !node_values.empty?
|
368
|
-
true
|
369
|
-
when [{}]
|
370
|
-
# Match with any @type
|
371
|
-
!node_values.empty?
|
372
|
-
else
|
373
|
-
# Treat a map with @default like an empty map
|
374
|
-
if v.first.is_a?(Hash) && v.first.keys == %w(@default)
|
333
|
+
##
|
334
|
+
# Returns true if the given node matches the given frame.
|
335
|
+
#
|
336
|
+
# Matches either based on explicit type inclusion where the node has any type listed in the frame. If the frame has empty types defined matches nodes not having a @type. If the frame has a type of {} defined matches nodes having any type defined.
|
337
|
+
#
|
338
|
+
# Otherwise, does duck typing, where the node must have any or all of the properties defined in the frame, depending on the `requireAll` flag.
|
339
|
+
#
|
340
|
+
# @param [Hash{String => Object}] subject the subject to check.
|
341
|
+
# @param [Hash{String => Object}] frame the frame to check.
|
342
|
+
# @param [Hash{Symbol => Object}] state Current framing state
|
343
|
+
# @param [Hash{Symbol => Object}] flags the frame flags.
|
344
|
+
#
|
345
|
+
# @return [Boolean] true if the node matches, false if not.
|
346
|
+
def filter_subject(subject, frame, state, flags)
|
347
|
+
# Duck typing, for nodes not having a type, but having @id
|
348
|
+
wildcard = true
|
349
|
+
matches_some = false
|
350
|
+
|
351
|
+
frame.each do |k, v|
|
352
|
+
node_values = subject.fetch(k, [])
|
353
|
+
|
354
|
+
case k
|
355
|
+
when '@id'
|
356
|
+
ids = v || []
|
357
|
+
|
358
|
+
# Match on specific @id.
|
359
|
+
match_this = case ids
|
360
|
+
when [], [{}]
|
361
|
+
# Match on no @id or any @id
|
375
362
|
true
|
376
|
-
elsif (v & node_values).empty?
|
377
|
-
# Match on specific @type
|
378
|
-
false
|
379
363
|
else
|
380
|
-
|
364
|
+
# Match on specific @id
|
365
|
+
ids.include?(subject['@id'])
|
381
366
|
end
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
next
|
387
|
-
else
|
388
|
-
is_empty = v.empty?
|
389
|
-
if v = v.first
|
390
|
-
validate_frame(v)
|
391
|
-
has_default = v.key?('@default')
|
392
|
-
end
|
367
|
+
return match_this unless flags[:requireAll]
|
368
|
+
when '@type'
|
369
|
+
# No longer a wildcard pattern
|
370
|
+
wildcard = false
|
393
371
|
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
!node_values.empty?
|
411
|
-
when value?(v)
|
412
|
-
# Match on any matching value
|
413
|
-
node_values.any? {|nv| value_match?(v, nv)}
|
414
|
-
when node?(v) || node_reference?(v)
|
415
|
-
node_values.any? do |nv|
|
416
|
-
node_match?(v, nv, state, flags)
|
372
|
+
match_this = case v
|
373
|
+
when []
|
374
|
+
# Don't match with any @type
|
375
|
+
return false unless node_values.empty?
|
376
|
+
|
377
|
+
true
|
378
|
+
when [{}]
|
379
|
+
# Match with any @type
|
380
|
+
!node_values.empty?
|
381
|
+
else
|
382
|
+
# Treat a map with @default like an empty map
|
383
|
+
if v.first.is_a?(Hash) && v.first.keys == %w[@default]
|
384
|
+
true
|
385
|
+
else
|
386
|
+
!(v & node_values).empty?
|
387
|
+
end
|
417
388
|
end
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
389
|
+
return match_this unless flags[:requireAll]
|
390
|
+
when /@/
|
391
|
+
# Skip other keywords
|
392
|
+
next
|
393
|
+
else
|
394
|
+
is_empty = v.empty?
|
395
|
+
if (v = v.first)
|
396
|
+
validate_frame(v)
|
397
|
+
has_default = v.key?('@default')
|
398
|
+
end
|
399
|
+
|
400
|
+
# No longer a wildcard pattern if frame has any non-keyword properties
|
401
|
+
wildcard = false
|
402
|
+
|
403
|
+
# Skip, but allow match if node has no value for property, and frame has a default value
|
404
|
+
next if node_values.empty? && has_default
|
405
|
+
|
406
|
+
# If frame value is empty, don't match if subject has any value
|
407
|
+
return false if !node_values.empty? && is_empty
|
408
|
+
|
409
|
+
match_this = case
|
410
|
+
when v.nil?
|
411
|
+
# node does not match if values is not empty and the value of property in frame is match none.
|
412
|
+
return false unless node_values.empty?
|
413
|
+
|
414
|
+
true
|
415
|
+
when v.is_a?(Hash) && (v.keys - FRAMING_KEYWORDS).empty?
|
416
|
+
# node matches if values is not empty and the value of property in frame is wildcard (frame with properties other than framing keywords)
|
417
|
+
!node_values.empty?
|
418
|
+
when value?(v)
|
426
419
|
# Match on any matching value
|
427
|
-
node_values.any? {|nv| value_match?(
|
428
|
-
|
420
|
+
node_values.any? { |nv| value_match?(v, nv) }
|
421
|
+
when node?(v) || node_reference?(v)
|
429
422
|
node_values.any? do |nv|
|
430
|
-
node_match?(
|
423
|
+
node_match?(v, nv, state, flags)
|
424
|
+
end
|
425
|
+
when list?(v)
|
426
|
+
vv = v['@list'].first
|
427
|
+
node_values = if list?(node_values.first)
|
428
|
+
node_values.first['@list']
|
429
|
+
else
|
430
|
+
false
|
431
|
+
end
|
432
|
+
if !node_values
|
433
|
+
false # Lists match Lists
|
434
|
+
elsif value?(vv)
|
435
|
+
# Match on any matching value
|
436
|
+
node_values.any? { |nv| value_match?(vv, nv) }
|
437
|
+
elsif node?(vv) || node_reference?(vv)
|
438
|
+
node_values.any? do |nv|
|
439
|
+
node_match?(vv, nv, state, flags)
|
440
|
+
end
|
441
|
+
else
|
442
|
+
false
|
431
443
|
end
|
432
444
|
else
|
433
|
-
false
|
445
|
+
false # No matching on non-value or node values
|
434
446
|
end
|
435
|
-
else
|
436
|
-
false # No matching on non-value or node values
|
437
447
|
end
|
438
|
-
end
|
439
448
|
|
440
|
-
|
441
|
-
|
449
|
+
# All non-defaulted values must match if @requireAll is set
|
450
|
+
return false if !match_this && flags[:requireAll]
|
451
|
+
|
452
|
+
matches_some ||= match_this
|
453
|
+
end
|
442
454
|
|
443
|
-
|
455
|
+
# return true if wildcard or subject matches some properties
|
456
|
+
wildcard || matches_some
|
444
457
|
end
|
445
458
|
|
446
|
-
|
447
|
-
|
448
|
-
|
459
|
+
def validate_frame(frame)
|
460
|
+
unless frame.is_a?(Hash) || (frame.is_a?(Array) && frame.first.is_a?(Hash) && frame.length == 1)
|
461
|
+
raise JsonLdError::InvalidFrame,
|
462
|
+
"Invalid JSON-LD frame syntax; a JSON-LD frame must be an object: #{frame.inspect}"
|
463
|
+
end
|
464
|
+
frame = frame.first if frame.is_a?(Array)
|
449
465
|
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
Array(frame['@type']).all?{|v| v.is_a?(Hash) && (v.keys - %w(@default)).empty? || RDF::URI(v).valid?}
|
463
|
-
end
|
466
|
+
# Check values of @id and @type
|
467
|
+
unless Array(frame['@id']) == [{}] || Array(frame['@id']).all? { |v| RDF::URI(v).valid? }
|
468
|
+
raise JsonLdError::InvalidFrame,
|
469
|
+
"Invalid JSON-LD frame syntax; invalid value of @id: #{frame['@id']}"
|
470
|
+
end
|
471
|
+
unless Array(frame['@type']).all? do |v|
|
472
|
+
(v.is_a?(Hash) && (v.keys - %w[@default]).empty?) || RDF::URI(v).valid?
|
473
|
+
end
|
474
|
+
raise JsonLdError::InvalidFrame,
|
475
|
+
"Invalid JSON-LD frame syntax; invalid value of @type: #{frame['@type']}"
|
476
|
+
end
|
477
|
+
end
|
464
478
|
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
479
|
+
# Checks the current subject stack to see if embedding the given subject would cause a circular reference.
|
480
|
+
#
|
481
|
+
# @param subject_to_embed the subject to embed.
|
482
|
+
# @param graph the graph the subject to embed is in.
|
483
|
+
# @param subject_stack the current stack of subjects.
|
484
|
+
#
|
485
|
+
# @return true if a circular reference would be created, false if not.
|
486
|
+
def creates_circular_reference(subject_to_embed, graph, subject_stack)
|
487
|
+
subject_stack[0..-2].any? do |subject|
|
488
|
+
subject[:graph] == graph && subject[:subject]['@id'] == subject_to_embed['@id']
|
489
|
+
end
|
475
490
|
end
|
476
|
-
end
|
477
491
|
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
492
|
+
##
|
493
|
+
# Gets the frame flag value for the given flag name.
|
494
|
+
#
|
495
|
+
# @param frame the frame.
|
496
|
+
# @param options the framing options.
|
497
|
+
# @param name the flag name.
|
498
|
+
#
|
499
|
+
# @return the flag value.
|
500
|
+
def get_frame_flag(frame, options, name)
|
501
|
+
rval = frame.fetch("@#{name}", [options[name]]).first
|
502
|
+
rval = rval.values.first if value?(rval)
|
503
|
+
if name == :embed
|
504
|
+
rval = case rval
|
505
|
+
when true then '@once'
|
506
|
+
when false then '@never'
|
507
|
+
when '@always', '@first', '@last', '@link', '@once', '@never' then rval
|
508
|
+
else
|
509
|
+
raise JsonLdError::InvalidEmbedValue,
|
510
|
+
"Invalid JSON-LD frame syntax; invalid value of @embed: #{rval}"
|
511
|
+
end
|
497
512
|
end
|
513
|
+
rval
|
498
514
|
end
|
499
|
-
rval
|
500
|
-
end
|
501
515
|
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
516
|
+
##
|
517
|
+
# Removes an existing embed.
|
518
|
+
#
|
519
|
+
# @param state the current framing state.
|
520
|
+
# @param id the @id of the embed to remove.
|
521
|
+
def remove_embed(state, id)
|
522
|
+
# get existing embed
|
523
|
+
embeds = state[:uniqueEmbeds][state[:graph]]
|
524
|
+
embed = embeds[id]
|
525
|
+
property = embed[:property]
|
526
|
+
|
527
|
+
# create reference to replace embed
|
528
|
+
subject = { '@id' => id }
|
529
|
+
|
530
|
+
if embed[:parent].is_a?(Array)
|
531
|
+
# replace subject with reference
|
532
|
+
embed[:parent].map! do |parent|
|
533
|
+
compare_values(parent, subject) ? subject : parent
|
534
|
+
end
|
535
|
+
else
|
536
|
+
parent = embed[:parent]
|
537
|
+
# replace node with reference
|
538
|
+
if parent[property].is_a?(Array)
|
539
|
+
parent[property].reject! { |v| compare_values(v, subject) }
|
540
|
+
parent[property] << subject
|
541
|
+
elsif compare_values(parent[property], subject)
|
542
|
+
parent[property] = subject
|
543
|
+
end
|
520
544
|
end
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
545
|
+
|
546
|
+
# recursively remove dependent dangling embeds
|
547
|
+
def remove_dependents(id, embeds)
|
548
|
+
# get embed keys as a separate array to enable deleting keys in map
|
549
|
+
embeds.each do |id_dep, e|
|
550
|
+
p = e.fetch(:parent, {}) if e.is_a?(Hash)
|
551
|
+
next unless p.is_a?(Hash)
|
552
|
+
|
553
|
+
pid = p.fetch('@id', nil)
|
554
|
+
if pid == id
|
555
|
+
embeds.delete(id_dep)
|
556
|
+
remove_dependents(id_dep, embeds)
|
557
|
+
end
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
remove_dependents(id, embeds)
|
562
|
+
end
|
563
|
+
|
564
|
+
##
|
565
|
+
# Adds framing output to the given parent.
|
566
|
+
#
|
567
|
+
# @param parent the parent to add to.
|
568
|
+
# @param property the parent property, null for an array parent.
|
569
|
+
# @param output the output to add.
|
570
|
+
def add_frame_output(parent, property, output)
|
571
|
+
if parent.is_a?(Hash)
|
572
|
+
parent[property] ||= []
|
573
|
+
parent[property] << output
|
574
|
+
else
|
575
|
+
parent << output
|
529
576
|
end
|
530
577
|
end
|
531
578
|
|
532
|
-
#
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
embeds.delete(id_dep)
|
541
|
-
remove_dependents(id_dep, embeds)
|
579
|
+
# Creates an implicit frame when recursing through subject matches. If a frame doesn't have an explicit frame for a particular property, then a wildcard child frame will be created that uses the same flags that the parent frame used.
|
580
|
+
#
|
581
|
+
# @param [Hash] flags the current framing flags.
|
582
|
+
# @return [Array<Hash>] the implicit frame.
|
583
|
+
def create_implicit_frame(flags)
|
584
|
+
{}.tap do |memo|
|
585
|
+
flags.each_pair do |key, val|
|
586
|
+
memo["@#{key}"] = [val]
|
542
587
|
end
|
543
588
|
end
|
544
589
|
end
|
545
590
|
|
546
|
-
|
547
|
-
|
591
|
+
# Node matches if it is a node, and matches the pattern as a frame
|
592
|
+
def node_match?(pattern, value, state, flags)
|
593
|
+
return false unless value['@id']
|
548
594
|
|
549
|
-
|
550
|
-
|
551
|
-
#
|
552
|
-
# @param parent the parent to add to.
|
553
|
-
# @param property the parent property, null for an array parent.
|
554
|
-
# @param output the output to add.
|
555
|
-
def add_frame_output(parent, property, output)
|
556
|
-
if parent.is_a?(Hash)
|
557
|
-
parent[property] ||= []
|
558
|
-
parent[property] << output
|
559
|
-
else
|
560
|
-
parent << output
|
595
|
+
node_object = state[:subjects][value['@id']]
|
596
|
+
node_object && filter_subject(node_object, pattern, state, flags)
|
561
597
|
end
|
562
|
-
end
|
563
598
|
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
{}
|
570
|
-
|
571
|
-
|
599
|
+
# Value matches if it is a value, and matches the value pattern.
|
600
|
+
#
|
601
|
+
# * `pattern` is empty
|
602
|
+
# * @values are the same, or `pattern[@value]` is a wildcard, and
|
603
|
+
# * @types are the same or `value[@type]` is not null and `pattern[@type]` is `{}`, or `value[@type]` is null and `pattern[@type]` is null or `[]`, and
|
604
|
+
# * @languages are the same or `value[@language]` is not null and `pattern[@language]` is `{}`, or `value[@language]` is null and `pattern[@language]` is null or `[]`.
|
605
|
+
def value_match?(pattern, value)
|
606
|
+
v1 = value['@value']
|
607
|
+
t1 = value['@type']
|
608
|
+
l1 = value['@language']
|
609
|
+
v2 = Array(pattern['@value'])
|
610
|
+
t2 = Array(pattern['@type'])
|
611
|
+
l2 = Array(pattern['@language']).map do |v|
|
612
|
+
v.is_a?(String) ? v.downcase : v
|
572
613
|
end
|
573
|
-
|
574
|
-
|
614
|
+
return true if (v2 + t2 + l2).empty?
|
615
|
+
return false unless v2.include?(v1) || v2 == [{}]
|
616
|
+
return false unless t2.include?(t1) || (t1 && t2 == [{}]) || (t1.nil? && (t2 || []).empty?)
|
617
|
+
return false unless l2.include?(l1.to_s.downcase) || (l1 && l2 == [{}]) || (l1.nil? && (l2 || []).empty?)
|
575
618
|
|
576
|
-
|
577
|
-
|
578
|
-
def node_match?(pattern, value, state, flags)
|
579
|
-
return false unless value['@id']
|
580
|
-
node_object = state[:subjects][value['@id']]
|
581
|
-
node_object && filter_subject(node_object, pattern, state, flags)
|
582
|
-
end
|
619
|
+
true
|
620
|
+
end
|
583
621
|
|
584
|
-
|
585
|
-
#
|
586
|
-
# * `pattern` is empty
|
587
|
-
# * @values are the same, or `pattern[@value]` is a wildcard, and
|
588
|
-
# * @types are the same or `value[@type]` is not null and `pattern[@type]` is `{}`, or `value[@type]` is null and `pattern[@type]` is null or `[]`, and
|
589
|
-
# * @languages are the same or `value[@language]` is not null and `pattern[@language]` is `{}`, or `value[@language]` is null and `pattern[@language]` is null or `[]`.
|
590
|
-
def value_match?(pattern, value)
|
591
|
-
v1, t1, l1 = value['@value'], value['@type'], value['@language']
|
592
|
-
v2, t2, l2 = Array(pattern['@value']), Array(pattern['@type']), Array(pattern['@language']).map {|v| v.is_a?(String) ? v.downcase : v}
|
593
|
-
return true if (v2 + t2 + l2).empty?
|
594
|
-
return false unless v2.include?(v1) || v2 == [{}]
|
595
|
-
return false unless t2.include?(t1) || t1 && t2 == [{}] || t1.nil? && (t2 || []).empty?
|
596
|
-
return false unless l2.include?(l1.to_s.downcase) || l1 && l2 == [{}] || l1.nil? && (l2 || []).empty?
|
597
|
-
true
|
622
|
+
FRAMING_KEYWORDS = %w[@default @embed @explicit @omitDefault @requireAll].freeze
|
598
623
|
end
|
599
|
-
|
600
|
-
FRAMING_KEYWORDS = %w(@default @embed @explicit @omitDefault @requireAll).freeze
|
601
624
|
end
|
602
625
|
end
|