Skip to content

Commit

Permalink
Add support for @container to be an array including @set in addit…
Browse files Browse the repository at this point in the history
…ion to other appropriate keywords.

Addresses json-ld/json-ld.org#269 and json-ld/json-ld.org#407.
  • Loading branch information
gkellogg committed May 28, 2017
1 parent 0f59730 commit 18707be
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 40 deletions.
12 changes: 5 additions & 7 deletions lib/json/ld/compact.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def compact(element, property: nil)
# If element has a single member and the active property has no
# @container mapping to @list or @set, the compacted value is that
# member; otherwise the compacted value is element
if result.length == 1 && context.container(property).nil? && @options[:compactArrays]
if result.length == 1 && !context.as_array?(property) && @options[:compactArrays]
#log_debug("=> extract single element: #{result.first.inspect}")
result.first
else
Expand Down Expand Up @@ -85,7 +85,7 @@ def compact(element, property: nil)
compacted_value.each do |prop, value|
if context.reverse?(prop)
value = [value] if !value.is_a?(Array) &&
(context.container(prop) == '@set' || !@options[:compactArrays])
(context.as_array?(prop) || !@options[:compactArrays])
#log_debug("") {"merge #{prop} => #{value.inspect}"}

merge_compacted_value(result, prop, value)
Expand Down Expand Up @@ -161,6 +161,7 @@ def compact(element, property: nil)
end

container = context.container(item_active_property)
as_array = context.as_array?(item_active_property)
value = list?(expanded_item) ? expanded_item['@list'] : expanded_item
compacted_item = compact(value, property: item_active_property)
#log_debug("") {" => compacted key: #{item_active_property.inspect} for #{compacted_item.inspect}"}
Expand Down Expand Up @@ -206,14 +207,11 @@ def compact(element, property: nil)
end
compacted_item
end
compacted_item = [compacted_item] if as_array && !compacted_item.is_a?(Array)
merge_compacted_value(map_object, map_key, compacted_item)
else
compacted_item = [compacted_item] if
!compacted_item.is_a?(Array) && (
!@options[:compactArrays] ||
%w(@set @list).include?(container) ||
%w(@list @graph).include?(expanded_property)
)
!compacted_item.is_a?(Array) && (!@options[:compactArrays] || as_array)
merge_compacted_value(nest_result, item_active_property, compacted_item)
end
end
Expand Down
82 changes: 65 additions & 17 deletions lib/json/ld/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,13 @@ class TermDefinition
# @return [String] Type mapping
attr_accessor :type_mapping

# @return ['@index', '@language', '@index', '@set', '@type', '@id'] Container mapping
attr_accessor :container_mapping
# Base container mapping, without @set
# @return ['@index', '@language', '@index', '@type', '@id'] Container mapping
attr_reader :container_mapping

# If container mapping was defined along with @set
# @return [Boolean]
attr_reader :as_set

# @return [String] Term used for nest properties
attr_accessor :nest
Expand Down Expand Up @@ -81,15 +86,24 @@ def initialize(term,
nest: nil,
simple: false,
context: nil)
@term = term
@id = id.to_s if id
@type_mapping = type_mapping.to_s if type_mapping
@container_mapping = container_mapping if container_mapping
@language_mapping = language_mapping if language_mapping
@reverse_property = reverse_property if reverse_property
@nest = nest if nest
@simple = simple if simple
@context = context if context
@term = term
@id = id.to_s if id
@type_mapping = type_mapping.to_s if type_mapping
self.container_mapping = container_mapping if container_mapping
@language_mapping = language_mapping if language_mapping
@reverse_property = reverse_property if reverse_property
@nest = nest if nest
@simple = simple if simple
@context = context if context
end

# Set container mapping, from an array which may include @set
def container_mapping=(mapping)
mapping = Array(mapping)
if @as_set = mapping.include?('@set')
mapping -= %w(@set)
end
@container_mapping = mapping.first
end

##
Expand All @@ -109,6 +123,7 @@ def to_context_definition(context)

if language_mapping.nil? &&
container_mapping.nil? &&
!as_set &&
type_mapping.nil? &&
reverse_property.nil? &&
self.context.nil? &&
Expand All @@ -125,7 +140,10 @@ def to_context_definition(context)
context.compact_iri(type_mapping, vocab: true)
end
end
defn['@container'] = container_mapping if container_mapping

cm = [container_mapping, ('@set' if as_set)].compact
cm = cm.first if cm.length == 1
defn['@container'] = cm unless cm.empty?
# Language set as false to be output as null
defn['@language'] = (language_mapping ? language_mapping : nil) unless language_mapping.nil?
defn['@context'] = self.context unless self.context.nil?
Expand All @@ -143,6 +161,9 @@ def to_rb
%w(id type_mapping container_mapping language_mapping reverse_property nest simple context).each do |acc|
v = instance_variable_get("@#{acc}".to_sym)
v = v.to_s if v.is_a?(RDF::Term)
if acc == 'container_mapping' && as_set
v = v ? [v, '@set'] : '@set'
end
defn << "#{acc}: #{v.inspect}" if v
end
defn.join(', ') + ")"
Expand All @@ -154,6 +175,7 @@ def inspect
v << "term=#{@term}"
v << "rev" if reverse_property
v << "container=#{container_mapping}" if container_mapping
v << "as_set=#{as_set.inspect}"
v << "lang=#{language_mapping.inspect}" unless language_mapping.nil?
v << "type=#{type_mapping}" unless type_mapping.nil?
v << "nest=#{nest.inspect}" unless nest.nil?
Expand Down Expand Up @@ -607,7 +629,7 @@ def create_term_definition(local_context, term, defined)
container = value['@container']
raise JsonLdError::InvalidReverseProperty,
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" unless
['@set', '@index'].include?(container)
container.is_a?(String) && ['@set', '@index'].include?(container)
definition.container_mapping = check_container(container, local_context, defined, term)
end
definition.reverse_property = true
Expand Down Expand Up @@ -848,6 +870,17 @@ def container(term)
term && term.container_mapping
end

##
# Should values be represented as a set?
#
# @param [Term, #to_s] term in unexpanded form
# @return [Boolean]
def as_array?(term)
return true if %w(@graph @list).include?(term)
term = find_definition(term)
term && (term.as_set || term.container_mapping == '@list')
end

##
# Retrieve content of a term
#
Expand Down Expand Up @@ -1447,7 +1480,7 @@ def inverse_context
a.length == b.length ? (a <=> b) : (a.length <=> b.length)
end.each do |term|
next unless td = term_definitions[term]
container = td.container_mapping || '@none'
container = td.container_mapping || (td.as_set ? '@set' : '@none')
container_map = result[td.id.to_s] ||= {}
tl_map = container_map[container] ||= {'@language' => {}, '@type' => {}}
type_map = tl_map['@type']
Expand Down Expand Up @@ -1564,8 +1597,23 @@ def languages
# Ensure @container mapping is appropriate
# The result is the original container definition. For IRI containers, this is necessary to be able to determine the @type mapping for string values
def check_container(container, local_context, defined, term)
case container
when '@set', '@list', '@language', '@index', nil
if container.is_a?(Array) && processingMode < 'json-ld-1.1'
raise JsonLdError::InvalidContainerMapping,
"'@container' on term #{term.inspect} must be a string: #{container.inspect}"
end

val = Array(container)
val -= %w(@set) if has_set = val.include?('@set')

raise JsonLdError::InvalidContainerMapping,
"'@container' has more than one value other than @set" if val.length > 1

case val.first
when '@list'
raise JsonLdError::InvalidContainerMapping,
"'@container' on term #{term.inspect} cannot be both @list and @set" if has_set
# Okay
when '@language', '@index', nil
# Okay
when '@type', '@id', nil
raise JsonLdError::InvalidContainerMapping,
Expand All @@ -1575,7 +1623,7 @@ def check_container(container, local_context, defined, term)
raise JsonLdError::InvalidContainerMapping,
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}"
end
container
Array(container)
end
end
end
5 changes: 2 additions & 3 deletions lib/json/ld/frame.rb
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ def cleanup_preserve(input, bnodes_to_clear)
v = input.map {|o| cleanup_preserve(o, bnodes_to_clear)}.compact

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

# Because we may have added a null value to an array, we need to clean that up, if we possible
v = v.first if v.is_a?(Array) && v.length == 1 &&
context.expand_iri(key) != "@graph" && context.container(key).nil?
v = v.first if v.is_a?(Array) && v.length == 1 && !context.as_array?(key)
output[key] = v
end
end
Expand Down
15 changes: 2 additions & 13 deletions spec/context_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -200,14 +200,6 @@ def containers
}, logger)
end

it "associates @set container mapping with term" do
expect(subject.parse({
"foo" => {"@id" => "http://example.com/", "@container" => "@set"}
}).containers).to produce({
"foo" => '@set'
}, logger)
end

it "associates @type container mapping with term" do
expect(subject.parse({
"foo" => {"@id" => "http://example.com/", "@container" => "@type"}
Expand Down Expand Up @@ -934,7 +926,6 @@ def containers
"listdouble" => {"@id" => "http://example.com/double", "@type" => "xsd:double", "@container" => "@list"},
"listdate" => {"@id" => "http://example.com/date", "@type" => "xsd:date", "@container" => "@list"},
"listid" => {"@id" => "http://example.com/id", "@type" => "@id", "@container" => "@list"},
"setplain" => {"@id" => "http://example.com/plain", "@container" => "@set"},
"setlang" => {"@id" => "http://example.com/lang", "@language" => "en", "@container" => "@set"},
"setbool" => {"@id" => "http://example.com/bool", "@type" => "xsd:boolean", "@container" => "@set"},
"setinteger" => {"@id" => "http://example.com/integer", "@type" => "xsd:integer", "@container" => "@set"},
Expand All @@ -950,7 +941,7 @@ def containers
{
"langmap" => [{"@value" => "en", "@language" => "en"}],
#"plain" => [{"@value" => "foo"}],
"setplain" => [{"@value" => "foo", "@language" => "pl"}]
#"setplain" => [{"@value" => "foo", "@language" => "pl"}]
}.each do |prop, values|
context "uses #{prop}" do
values.each do |value|
Expand Down Expand Up @@ -1370,7 +1361,6 @@ def containers
{
"ex" => nil,
"list" => "@list",
"set" => "@set",
"language" => "@language",
"ndx" => "@index",
"id" => "@id",
Expand All @@ -1384,7 +1374,6 @@ def containers
{
"ex" => nil,
"list" => "@list",
"set" => "@set",
"language" => "@language",
"ndx" => "@index",
"id" => "@id",
Expand Down Expand Up @@ -1519,7 +1508,7 @@ def containers

context "with container_mapping" do
subject {described_class.new("term", container_mapping: "@set")}
its(:container_mapping) {is_expected.to eq "@set"}
its(:container_mapping) {is_expected.to be_nil}
its(:to_rb) {is_expected.to eq %(TermDefinition.new("term", container_mapping: "@set"))}
end

Expand Down

0 comments on commit 18707be

Please sign in to comment.