json-ld 3.2.4 → 3.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/json/ld/api.rb +807 -771
- data/lib/json/ld/compact.rb +304 -304
- data/lib/json/ld/conneg.rb +179 -161
- data/lib/json/ld/context.rb +2080 -1913
- 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 +223 -204
- 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 -169
- 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 +55 -198
- 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 -2056
- 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 -2541
- data/spec/from_rdf_spec.rb +0 -1072
- 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 -1684
- 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
|