Skip to content

Commit

Permalink
Partial work on context and expansion of containers containing @id,…
Browse files Browse the repository at this point in the history
… `@type`, and arbitrary IRIs.

For json-ld.org issue #246.
  • Loading branch information
gkellogg committed Jan 5, 2017
1 parent eb97c88 commit 3db66f1
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 65 deletions.
67 changes: 55 additions & 12 deletions lib/json/ld/context.rb
Expand Up @@ -36,7 +36,7 @@ class TermDefinition
# @return [String] Type mapping
attr_accessor :type_mapping

# @return ['@set', '@list'] Container mapping
# @return [String] Container mapping
attr_accessor :container_mapping

# Language mapping of term, `false` is used if there is explicitly no language mapping for this term.
Expand All @@ -62,7 +62,7 @@ def simple?; simple; end
# @param [String] term
# @param [String] id
# @param [String] type_mapping Type mapping
# @param ['@set', '@list'] container_mapping
# @param [String] container_mapping
# @param [String] language_mapping
# Language mapping of term, `false` is used if there is explicitly no language mapping for this term
# @param [Boolean] reverse_property
Expand Down Expand Up @@ -569,12 +569,14 @@ def create_term_definition(local_context, term, defined)
raise JsonLdError::InvalidIRIMapping, "non-absolute @reverse IRI: #{definition.id} on term #{term.inspect}" unless
definition.id.is_a?(RDF::URI) && definition.id.absolute?

# If value contains an @container member, set the container mapping of definition to its value; if its value is neither @set, nor @index, nor null, an invalid reverse property error has been detected (reverse properties only support set- and index-containers) and processing is aborted.
if (container = value.fetch('@container', false))
# If value contains an @container member, set the container mapping of definition to its value; if its value is neither @set, @index, @type, @id, an absolute IRI nor null, an invalid reverse property error has been detected (reverse properties only support set- and index-containers) and processing is aborted.
if value.has_key?('@container')
container = value['@container']
# FIXME: Are URIS, @id, and @type reasonable for reverse mappings?
raise JsonLdError::InvalidReverseProperty,
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" unless
['@set', '@index', nil].include?(container)
definition.container_mapping = container
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" if
%w(@language @list).include?(container)
definition.container_mapping = check_container(container, local_context, defined, term)
end
definition.reverse_property = true
elsif value.has_key?('@id') && value['@id'] != term
Expand Down Expand Up @@ -610,10 +612,8 @@ def create_term_definition(local_context, term, defined)
@iri_to_term[definition.id] = term if simple_term && definition.id

if value.has_key?('@container')
container = value['@container']
raise JsonLdError::InvalidContainerMapping, "unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" unless %w(@list @set @language @index).include?(container)
#log_debug("") {"container_mapping: #{container.inspect}"}
definition.container_mapping = container
#log_debug("") {"container_mapping: #{value['@container'].inspect}"}
definition.container_mapping = check_container(value['@container'], local_context, defined, term)
end

if value.has_key?('@context')
Expand Down Expand Up @@ -956,7 +956,18 @@ def compact_iri(iri, value: nil, vocab: nil, reverse: false, quiet: false, **opt
default_language = self.default_language || @none
containers = []
tl, tl_value = "@language", "@null"
containers << '@index' if index?(value)

# If the value is a JSON Object, then for the keywords @index, @id, and @type along with the compacted version of all non-keyword properties of the object in order, if the value contains that property, append it to containers.
if value.is_a?(Hash)
%w(@index @id @type).each do |kw|
containers << kw if value.has_key?(kw)
end
containers.concat value.keys.
reject {|k| k.start_with?('@')}.
sort.
map {|k| compact_iri(k, vocab: true, quite: true)}
end

if reverse
tl, tl_value = "@type", "@reverse"
containers << '@set'
Expand Down Expand Up @@ -1485,5 +1496,37 @@ def languages
memo
end
end

# 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', '@type', '@id', nil
# Okay
when /^@/
raise JsonLdError::InvalidContainerMapping,
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}"
when String
expanded = expand_iri(container,
vocab: true,
documentRelative: false,
local_context: local_context,
defined: defined)
case expanded
when RDF::URI
raise JsonLdError::InvalidContainerMapping,
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" unless
expanded.absolute?
else
raise JsonLdError::InvalidContainerMapping,
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}" unless
container.absolute?
end
else
raise JsonLdError::InvalidContainerMapping,
"unknown mapping for '@container' to #{container.inspect} on term #{term.inspect}"
end
container
end
end
end
27 changes: 22 additions & 5 deletions lib/json/ld/expand.rb
Expand Up @@ -249,7 +249,8 @@ def expand(input, active_property, context, ordered: true)
# Use a term-specific context, if defined
term_context = context.term_definitions[key].context if context.term_definitions[key]
active_context = term_context ? context.parse(term_context) : context
expanded_value = if active_context.container(key) == '@language' && value.is_a?(Hash)
container = active_context.container(key)
expanded_value = if container == '@language' && value.is_a?(Hash)
# Otherwise, if key's container mapping in active context is @language and value is a JSON object then value is expanded from a language map as follows:

# Set multilingual array to an empty array.
Expand All @@ -272,19 +273,35 @@ def expand(input, active_property, context, ordered: true)
end

ary
elsif active_context.container(key) == '@index' && value.is_a?(Hash)
# Otherwise, if key's container mapping in active context is @index and value is a JSON object then value is expanded from an index map as follows:
elsif container && !%w(@set @list @language).include?(container) && value.is_a?(Hash)
# Otherwise, if key's container mapping in active context is @index, @id, @type, an IRI or Blank Node and value is a JSON object then value is expanded from an index map as follows:

# Set ary to an empty array.
ary = []
container, ary = container.to_s, []

# For each key-value in the object:
keys = ordered ? value.keys.sort : value.keys
keys.each do |k|
# Initialize index value to the result of using this algorithm recursively, passing active context, key as active property, and index value as element.
index_value = expand([value[k]].flatten, key, active_context, ordered: ordered)
index_value.each do |item|
item['@index'] ||= k
case container
when '@id', '@index' then item[container] ||= k
# If container is @type add the key-value pair (@type-[index]) to item, appending any existing values in item
when '@type' then item[container] = [k].concat(Array(item[container]))
else
#require 'byebug'; byebug
# Otherwise container is an IRI or Blank Node
# Expand index using the Value Expansion algorithm
prop = active_context.expand_iri(container, vocab: true).to_s
item_value = active_context.expand_value(container, k, log_depth: @options[:log_depth])
# add the key-value pair (container-[{"@id": index}]) to item, appending any existing values in item
values = item[prop]
values = [values].compact unless values.is_a?(Array)
item[prop] = [item_value].concat(values)
end

# Append item to expanded value.
ary << item
end
end
Expand Down
60 changes: 48 additions & 12 deletions spec/context_spec.rb
Expand Up @@ -208,7 +208,23 @@ def containers
}, logger)
end

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

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

it "associates @id type mapping with term" do
expect(subject.parse({
"foo" => {"@id" => "http://example.com/", "@type" => "@id"}
}).coercions).to produce({
Expand Down Expand Up @@ -1298,26 +1314,46 @@ def containers
describe "#container" do
subject {
ctx = context.parse({
"ex" => "http://example.org/",
"list" => {"@id" => "ex:list", "@container" => "@list"},
"set" => {"@id" => "ex:set", "@container" => "@set"},
"ndx" => {"@id" => "ex:ndx", "@container" => "@index"},
"ex" => "http://example.org/",
"list" => {"@id" => "ex:list", "@container" => "@list"},
"set" => {"@id" => "ex:set", "@container" => "@set"},
"language" => {"@id" => "ex:language", "@container" => "@language"},
"ndx" => {"@id" => "ex:ndx", "@container" => "@index"},
"id" => {"@id" => "ex:id", "@container" => "@id"},
"type" => {"@id" => "ex:type", "@container" => "@type"},
'uri' => {"@id" => "ex:uri", "@container" => "ex:uri"}
})
logger.clear
ctx
}
it "uses TermDefinition" do
expect(subject.container(subject.term_definitions['ex'])).to be_nil
expect(subject.container(subject.term_definitions['list'])).to eq '@list'
expect(subject.container(subject.term_definitions['set'])).to eq '@set'
expect(subject.container(subject.term_definitions['ndx'])).to eq '@index'
{
"ex" => nil,
"list" => "@list",
"set" => "@set",
"language" => "@language",
"ndx" => "@index",
"id" => "@id",
"type" => "@type",
'uri' => "ex:uri",
}.each do |defn, container|
expect(subject.container(subject.term_definitions[defn])).to eq container
end
end

it "uses string" do
expect(subject.container('ex')).to be_nil
expect(subject.container('list')).to eq '@list'
expect(subject.container('set')).to eq '@set'
expect(subject.container('ndx')).to eq '@index'
{
"ex" => nil,
"list" => "@list",
"set" => "@set",
"language" => "@language",
"ndx" => "@index",
"id" => "@id",
"type" => "@type",
'uri' => "ex:uri",
}.each do |defn, container|
expect(subject.container(defn)).to eq container
end
end
end

Expand Down

0 comments on commit 3db66f1

Please sign in to comment.