Skip to content

Commit 18707be

Browse files
committed
Add support for @container to be an array including @set in addition to other appropriate keywords.
Addresses json-ld/json-ld.org#269 and json-ld/json-ld.org#407.
1 parent 0f59730 commit 18707be

File tree

4 files changed

+74
-40
lines changed

4 files changed

+74
-40
lines changed

lib/json/ld/compact.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def compact(element, property: nil)
3030
# If element has a single member and the active property has no
3131
# @container mapping to @list or @set, the compacted value is that
3232
# member; otherwise the compacted value is element
33-
if result.length == 1 && context.container(property).nil? && @options[:compactArrays]
33+
if result.length == 1 && !context.as_array?(property) && @options[:compactArrays]
3434
#log_debug("=> extract single element: #{result.first.inspect}")
3535
result.first
3636
else
@@ -85,7 +85,7 @@ def compact(element, property: nil)
8585
compacted_value.each do |prop, value|
8686
if context.reverse?(prop)
8787
value = [value] if !value.is_a?(Array) &&
88-
(context.container(prop) == '@set' || !@options[:compactArrays])
88+
(context.as_array?(prop) || !@options[:compactArrays])
8989
#log_debug("") {"merge #{prop} => #{value.inspect}"}
9090

9191
merge_compacted_value(result, prop, value)
@@ -161,6 +161,7 @@ def compact(element, property: nil)
161161
end
162162

163163
container = context.container(item_active_property)
164+
as_array = context.as_array?(item_active_property)
164165
value = list?(expanded_item) ? expanded_item['@list'] : expanded_item
165166
compacted_item = compact(value, property: item_active_property)
166167
#log_debug("") {" => compacted key: #{item_active_property.inspect} for #{compacted_item.inspect}"}
@@ -206,14 +207,11 @@ def compact(element, property: nil)
206207
end
207208
compacted_item
208209
end
210+
compacted_item = [compacted_item] if as_array && !compacted_item.is_a?(Array)
209211
merge_compacted_value(map_object, map_key, compacted_item)
210212
else
211213
compacted_item = [compacted_item] if
212-
!compacted_item.is_a?(Array) && (
213-
!@options[:compactArrays] ||
214-
%w(@set @list).include?(container) ||
215-
%w(@list @graph).include?(expanded_property)
216-
)
214+
!compacted_item.is_a?(Array) && (!@options[:compactArrays] || as_array)
217215
merge_compacted_value(nest_result, item_active_property, compacted_item)
218216
end
219217
end

lib/json/ld/context.rb

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,13 @@ class TermDefinition
3636
# @return [String] Type mapping
3737
attr_accessor :type_mapping
3838

39-
# @return ['@index', '@language', '@index', '@set', '@type', '@id'] Container mapping
40-
attr_accessor :container_mapping
39+
# Base container mapping, without @set
40+
# @return ['@index', '@language', '@index', '@type', '@id'] Container mapping
41+
attr_reader :container_mapping
42+
43+
# If container mapping was defined along with @set
44+
# @return [Boolean]
45+
attr_reader :as_set
4146

4247
# @return [String] Term used for nest properties
4348
attr_accessor :nest
@@ -81,15 +86,24 @@ def initialize(term,
8186
nest: nil,
8287
simple: false,
8388
context: nil)
84-
@term = term
85-
@id = id.to_s if id
86-
@type_mapping = type_mapping.to_s if type_mapping
87-
@container_mapping = container_mapping if container_mapping
88-
@language_mapping = language_mapping if language_mapping
89-
@reverse_property = reverse_property if reverse_property
90-
@nest = nest if nest
91-
@simple = simple if simple
92-
@context = context if context
89+
@term = term
90+
@id = id.to_s if id
91+
@type_mapping = type_mapping.to_s if type_mapping
92+
self.container_mapping = container_mapping if container_mapping
93+
@language_mapping = language_mapping if language_mapping
94+
@reverse_property = reverse_property if reverse_property
95+
@nest = nest if nest
96+
@simple = simple if simple
97+
@context = context if context
98+
end
99+
100+
# Set container mapping, from an array which may include @set
101+
def container_mapping=(mapping)
102+
mapping = Array(mapping)
103+
if @as_set = mapping.include?('@set')
104+
mapping -= %w(@set)
105+
end
106+
@container_mapping = mapping.first
93107
end
94108

95109
##
@@ -109,6 +123,7 @@ def to_context_definition(context)
109123

110124
if language_mapping.nil? &&
111125
container_mapping.nil? &&
126+
!as_set &&
112127
type_mapping.nil? &&
113128
reverse_property.nil? &&
114129
self.context.nil? &&
@@ -125,7 +140,10 @@ def to_context_definition(context)
125140
context.compact_iri(type_mapping, vocab: true)
126141
end
127142
end
128-
defn['@container'] = container_mapping if container_mapping
143+
144+
cm = [container_mapping, ('@set' if as_set)].compact
145+
cm = cm.first if cm.length == 1
146+
defn['@container'] = cm unless cm.empty?
129147
# Language set as false to be output as null
130148
defn['@language'] = (language_mapping ? language_mapping : nil) unless language_mapping.nil?
131149
defn['@context'] = self.context unless self.context.nil?
@@ -143,6 +161,9 @@ def to_rb
143161
%w(id type_mapping container_mapping language_mapping reverse_property nest simple context).each do |acc|
144162
v = instance_variable_get("@#{acc}".to_sym)
145163
v = v.to_s if v.is_a?(RDF::Term)
164+
if acc == 'container_mapping' && as_set
165+
v = v ? [v, '@set'] : '@set'
166+
end
146167
defn << "#{acc}: #{v.inspect}" if v
147168
end
148169
defn.join(', ') + ")"
@@ -154,6 +175,7 @@ def inspect
154175
v << "term=#{@term}"
155176
v << "rev" if reverse_property
156177
v << "container=#{container_mapping}" if container_mapping
178+
v << "as_set=#{as_set.inspect}"
157179
v << "lang=#{language_mapping.inspect}" unless language_mapping.nil?
158180
v << "type=#{type_mapping}" unless type_mapping.nil?
159181
v << "nest=#{nest.inspect}" unless nest.nil?
@@ -607,7 +629,7 @@ def create_term_definition(local_context, term, defined)
607629
container = value['@container']
608630
raise JsonLdError::InvalidReverseProperty,
609631
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" unless
610-
['@set', '@index'].include?(container)
632+
container.is_a?(String) && ['@set', '@index'].include?(container)
611633
definition.container_mapping = check_container(container, local_context, defined, term)
612634
end
613635
definition.reverse_property = true
@@ -848,6 +870,17 @@ def container(term)
848870
term && term.container_mapping
849871
end
850872

873+
##
874+
# Should values be represented as a set?
875+
#
876+
# @param [Term, #to_s] term in unexpanded form
877+
# @return [Boolean]
878+
def as_array?(term)
879+
return true if %w(@graph @list).include?(term)
880+
term = find_definition(term)
881+
term && (term.as_set || term.container_mapping == '@list')
882+
end
883+
851884
##
852885
# Retrieve content of a term
853886
#
@@ -1447,7 +1480,7 @@ def inverse_context
14471480
a.length == b.length ? (a <=> b) : (a.length <=> b.length)
14481481
end.each do |term|
14491482
next unless td = term_definitions[term]
1450-
container = td.container_mapping || '@none'
1483+
container = td.container_mapping || (td.as_set ? '@set' : '@none')
14511484
container_map = result[td.id.to_s] ||= {}
14521485
tl_map = container_map[container] ||= {'@language' => {}, '@type' => {}}
14531486
type_map = tl_map['@type']
@@ -1564,8 +1597,23 @@ def languages
15641597
# Ensure @container mapping is appropriate
15651598
# The result is the original container definition. For IRI containers, this is necessary to be able to determine the @type mapping for string values
15661599
def check_container(container, local_context, defined, term)
1567-
case container
1568-
when '@set', '@list', '@language', '@index', nil
1600+
if container.is_a?(Array) && processingMode < 'json-ld-1.1'
1601+
raise JsonLdError::InvalidContainerMapping,
1602+
"'@container' on term #{term.inspect} must be a string: #{container.inspect}"
1603+
end
1604+
1605+
val = Array(container)
1606+
val -= %w(@set) if has_set = val.include?('@set')
1607+
1608+
raise JsonLdError::InvalidContainerMapping,
1609+
"'@container' has more than one value other than @set" if val.length > 1
1610+
1611+
case val.first
1612+
when '@list'
1613+
raise JsonLdError::InvalidContainerMapping,
1614+
"'@container' on term #{term.inspect} cannot be both @list and @set" if has_set
1615+
# Okay
1616+
when '@language', '@index', nil
15691617
# Okay
15701618
when '@type', '@id', nil
15711619
raise JsonLdError::InvalidContainerMapping,
@@ -1575,7 +1623,7 @@ def check_container(container, local_context, defined, term)
15751623
raise JsonLdError::InvalidContainerMapping,
15761624
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}"
15771625
end
1578-
container
1626+
Array(container)
15791627
end
15801628
end
15811629
end

lib/json/ld/frame.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def cleanup_preserve(input, bnodes_to_clear)
218218
v = input.map {|o| cleanup_preserve(o, bnodes_to_clear)}.compact
219219

220220
# If the array contains a single member, which is itself an array, use that value as the result
221-
v.length == 1 && v.first.is_a?(Array) ? v.first : v
221+
(v.length == 1 && v.first.is_a?(Array)) ? v.first : v
222222
when Hash
223223
output = Hash.new
224224
input.each do |key, value|
@@ -232,8 +232,7 @@ def cleanup_preserve(input, bnodes_to_clear)
232232
v = cleanup_preserve(value, bnodes_to_clear)
233233

234234
# Because we may have added a null value to an array, we need to clean that up, if we possible
235-
v = v.first if v.is_a?(Array) && v.length == 1 &&
236-
context.expand_iri(key) != "@graph" && context.container(key).nil?
235+
v = v.first if v.is_a?(Array) && v.length == 1 && !context.as_array?(key)
237236
output[key] = v
238237
end
239238
end

spec/context_spec.rb

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -200,14 +200,6 @@ def containers
200200
}, logger)
201201
end
202202

203-
it "associates @set container mapping with term" do
204-
expect(subject.parse({
205-
"foo" => {"@id" => "http://example.com/", "@container" => "@set"}
206-
}).containers).to produce({
207-
"foo" => '@set'
208-
}, logger)
209-
end
210-
211203
it "associates @type container mapping with term" do
212204
expect(subject.parse({
213205
"foo" => {"@id" => "http://example.com/", "@container" => "@type"}
@@ -934,7 +926,6 @@ def containers
934926
"listdouble" => {"@id" => "http://example.com/double", "@type" => "xsd:double", "@container" => "@list"},
935927
"listdate" => {"@id" => "http://example.com/date", "@type" => "xsd:date", "@container" => "@list"},
936928
"listid" => {"@id" => "http://example.com/id", "@type" => "@id", "@container" => "@list"},
937-
"setplain" => {"@id" => "http://example.com/plain", "@container" => "@set"},
938929
"setlang" => {"@id" => "http://example.com/lang", "@language" => "en", "@container" => "@set"},
939930
"setbool" => {"@id" => "http://example.com/bool", "@type" => "xsd:boolean", "@container" => "@set"},
940931
"setinteger" => {"@id" => "http://example.com/integer", "@type" => "xsd:integer", "@container" => "@set"},
@@ -950,7 +941,7 @@ def containers
950941
{
951942
"langmap" => [{"@value" => "en", "@language" => "en"}],
952943
#"plain" => [{"@value" => "foo"}],
953-
"setplain" => [{"@value" => "foo", "@language" => "pl"}]
944+
#"setplain" => [{"@value" => "foo", "@language" => "pl"}]
954945
}.each do |prop, values|
955946
context "uses #{prop}" do
956947
values.each do |value|
@@ -1370,7 +1361,6 @@ def containers
13701361
{
13711362
"ex" => nil,
13721363
"list" => "@list",
1373-
"set" => "@set",
13741364
"language" => "@language",
13751365
"ndx" => "@index",
13761366
"id" => "@id",
@@ -1384,7 +1374,6 @@ def containers
13841374
{
13851375
"ex" => nil,
13861376
"list" => "@list",
1387-
"set" => "@set",
13881377
"language" => "@language",
13891378
"ndx" => "@index",
13901379
"id" => "@id",
@@ -1519,7 +1508,7 @@ def containers
15191508

15201509
context "with container_mapping" do
15211510
subject {described_class.new("term", container_mapping: "@set")}
1522-
its(:container_mapping) {is_expected.to eq "@set"}
1511+
its(:container_mapping) {is_expected.to be_nil}
15231512
its(:to_rb) {is_expected.to eq %(TermDefinition.new("term", container_mapping: "@set"))}
15241513
end
15251514

0 commit comments

Comments
 (0)