Skip to content

Commit

Permalink
Expand strings and native types to @value form. (json-ld/json-ld.org#115
Browse files Browse the repository at this point in the history
 resolution).
  • Loading branch information
gkellogg committed Jun 13, 2012
1 parent 04bbd60 commit 9e6fb7e
Show file tree
Hide file tree
Showing 21 changed files with 376 additions and 222 deletions.
9 changes: 2 additions & 7 deletions lib/json/ld/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,10 @@ def self.compact(input, context, callback = nil, options = {})

# 1) Perform the Expansion Algorithm on the JSON-LD input.
# This removes any existing context to allow the given context to be cleanly applied.
API.new(input, nil, options) do
expanded = expand(value, nil, self.context)
debug(".compact") {"expanded input: #{value.to_json(JSON_STATE)}"}

# x) If no context provided, use context from input document
context ||= value.fetch('@context', nil)
end
expanded = API.expand(input, nil, nil, options)

API.new(expanded, context, options) do
debug(".compact") {"expanded input: #{expanded.to_json(JSON_STATE)}"}
result = compact(value, nil)

# xxx) Add the given context to the output
Expand Down
99 changes: 52 additions & 47 deletions lib/json/ld/evaluation_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ def expand_value(property, value, options = {})
else
if options[:native]
# Unless there's coercion, to not modify representation
value.is_a?(RDF::Literal::Boolean) ? value.object : value
{"@value" => (value.is_a?(RDF::Literal::Boolean) ? value.object : value)}
else
{"@value" => value.to_s, "@type" => RDF::XSD.boolean.to_s}
end
Expand All @@ -648,7 +648,7 @@ def expand_value(property, value, options = {})
when RDF::XSD.integer.to_s, nil
# Unless there's coercion, to not modify representation
if options[:native]
value.is_a?(RDF::Literal::Integer) ? value.object : value
{"@value" => value.is_a?(RDF::Literal::Integer) ? value.object : value}
else
{"@value" => value.to_s, "@type" => RDF::XSD.integer.to_s}
end
Expand All @@ -667,7 +667,7 @@ def expand_value(property, value, options = {})
when nil
if options[:native]
# Unless there's coercion, to not modify representation
value.is_a?(RDF::Literal::Double) ? value.object : value
{"@value" => value.is_a?(RDF::Literal::Double) ? value.object : value}
else
{"@value" => RDF::Literal::Double.new(value, :canonicalize => true).to_s, "@type" => RDF::XSD.double.to_s}
end
Expand Down Expand Up @@ -696,7 +696,7 @@ def expand_value(property, value, options = {})
{'@id' => expand_iri(value, :position => :object).to_s}
when nil
debug("expand value") {"lang(prop): #{language(property).inspect}, def: #{default_language.inspect}"}
language(property) ? {"@value" => value.to_s, "@language" => language(property)} : value.to_s
language(property) ? {"@value" => value.to_s, "@language" => language(property)} : {"@value" => value.to_s}
else
res = Hash.ordered
res['@value'] = value.to_s
Expand All @@ -722,18 +722,19 @@ def expand_value(property, value, options = {})
# @return [Hash] Object representation of value
# @raise [ProcessingError] if the iri cannot be expanded
# @see http://json-ld.org/spec/latest/json-ld-api/#value-compaction
# FIXME: revisit the specification version of this.
def compact_value(property, value, options = {})
raise ProcessingError::Lossy, "attempt to compact a non-object value: #{value.inspect}" unless value.is_a?(Hash)

depth(options) do
debug("compact_value") {"property: #{property.inspect}, value: #{value.inspect}, coerce: #{coerce(property).inspect}"}

result = case
when %w(boolean integer double).any? {|t| expand_iri(value['@type'], :position => :datatype) == RDF::XSD[t]}
# Compact native type
debug {" (native)"}
l = RDF::Literal(value['@value'], :datatype => expand_iri(value['@type'], :position => :datatype))
l.canonicalize.object
#when %w(boolean integer double).any? {|t| expand_iri(value['@type'], :position => :datatype) == RDF::XSD[t]}
# # Compact native type
# debug {" (native)"}
# l = RDF::Literal(value['@value'], :datatype => expand_iri(value['@type'], :position => :datatype))
# l.canonicalize.object
when coerce(property) == '@id' && value.has_key?('@id')
# Compact an @id coercion
debug {" (@id & coerce)"}
Expand All @@ -751,10 +752,18 @@ def compact_value(property, value, options = {})
# Compact language
debug {" (@language) == #{language(property).inspect}"}
value['@value']
when value['@value'] && !value['@value'].is_a?(String)
# Compact simple literal to string
debug {" (@value not string)"}
value['@value']
when value['@value'] && !value['@language'] && !value['@type'] && !coerce(property) && !default_language
# Compact simple literal to string
debug {" (@value && !@language && !@type && !coerce && !language)"}
value['@value']
when value['@value'] && !value['@language'] && !value['@type'] && !coerce(property) && !language(property)
# Compact simple literal to string
debug {" (@value && !@language && !@type && !coerce && language(property).false)"}
value['@value']
when value['@type']
# Compact datatype
debug {" (@type)"}
Expand Down Expand Up @@ -834,51 +843,47 @@ def bnode(value = nil)
# @param [Object] value
# @return [Integer]
def term_rank(term, value)
debug("term rank") { "term: #{term.inspect}, value: #{value.inspect}"}
debug("term rank") { "coerce: #{coerce(term).inspect}, lang: #{languages.fetch(term, nil).inspect}"}

# A term without @language or @type can be used with rank 1 for any value
default_term = !coerce(term) && !languages.has_key?(term)
debug("term rank") { "default_term: #{default_term.inspect}"}

rank = case value
when TrueClass, FalseClass
coerce(term) == RDF::XSD.boolean.to_s ? 3 : (default_term ? 2 : 1)
when Integer
coerce(term) == RDF::XSD.integer.to_s ? 3 : (default_term ? 2 : 1)
when Float
coerce(term) == RDF::XSD.double.to_s ? 3 : (default_term ? 2 : 1)
when nil
# A value of null probably means it's an @id
debug("term rank") {
"term: #{term.inspect}, " +
"value: #{value.inspect}, " +
"coerce: #{coerce(term).inspect}, " +
"lang: #{languages.fetch(term, nil).inspect}/#{language(term).inspect} " +
"default_term: #{default_term.inspect}"
}

# value is null
rank = if value.nil?
debug("term rank") { "null value: 3"}
3
when String
# When compacting a string, the string has no language, so the term can be used if the term has @language null or it is a default term and there is no default language
debug("term rank") {"string: lang: #{languages.fetch(term, false).inspect}, def: #{default_language.inspect}"}
!languages.fetch(term, true) || (default_term && !default_language) ? 3 : 0
when Hash
if list?(value)
if value['@list'].empty?
# If the @list property is an empty array, if term has @container set to @list, term rank is 1, otherwise 0.
container(term) == '@list' ? 1 : 0
else
# Otherwise, return the sum of the term ranks for every entry in the list.
depth {value['@list'].inject(0) {|memo, v| memo + term_rank(term, v)}}
end
elsif subject?(value) || subject_reference?(value)
coerce(term) == '@id' ? 3 : (default_term ? 1 : 0)
elsif val_type = value.fetch('@type', nil)
elsif list?(value)
if value['@list'].empty?
# If the @list property is an empty array, if term has @container set to @list, term rank is 1, otherwise 0.
container(term) == '@list' ? 1 : 0
else
# Otherwise, return the sum of the term ranks for every entry in the list.
depth {value['@list'].inject(0) {|memo, v| memo + term_rank(term, v)}}
end
elsif value?(value)
val_type = value.fetch('@type', nil)
val_lang = value.fetch('@language', nil)
debug("term rank") {"@val_type: #{val_type.inspect}, val_lang: #{val_lang.inspect}"}
if val_type
coerce(term) == val_type ? 3 : (default_term ? 1 : 0)
elsif val_lang = value.fetch('@language', nil)
val_lang == language(term) ? 3 : (default_term ? 1 : 0)
elsif !value['@value'].is_a?(String)
default_term ? 2 : 1
elsif val_lang.nil?
debug("val_lang.nil") {"#{language(term).inspect} && #{coerce(term).inspect}"}
!language(term) && !coerce(term) ? 3 : 0
else
default_term ? 3 : 0
val_lang == language(term) ? 3 : (default_term ? 1 : 0)
end
else
raise ProcessingError, "Unexpected value for term_rank: #{value.inspect}"
else # subject definition/reference
coerce(term) == '@id' ? 3 : (default_term ? 1 : 0)
end

# If term has @container @set, and rank is not 0, increase rank by 1.
rank > 0 && container(term) == '@set' ? rank + 1 : rank
debug(" =>") {rank.inspect}
rank
end
end
end
6 changes: 2 additions & 4 deletions lib/json/ld/expand.rb
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,8 @@ def expand(input, active_property, context, options = {})
raise ProcessingError, "element must not have more than one other property, which can either be @language or @type with a string value." unless value.is_a?(String)
end

# if @value is the only property or the value of @value equals null, replace element with the value of @value.
if output_object['@value'].nil? || output_object.keys.length == 1
return output_object['@value']
end
# if the value of @value equals null, replace element with the value of null.
return nil if output_object['@value'].nil?
elsif !output_object.fetch('@type', []).is_a?(Array)
# Otherwise, if element has an @type property and it's value is not in the form of an array,
# convert it to an array.
Expand Down
9 changes: 9 additions & 0 deletions lib/json/ld/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ def list?(value)
value.is_a?(Hash) && value.keys == %w(@list)
end

##
# Is value literal?
#
# @param [Object] value
# @return [Boolean]
def value?(value)
value.is_a?(Hash) && value.has_key?('@value')
end

private

# Add debug event to debug array, if specified
Expand Down
5 changes: 5 additions & 0 deletions spec/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,23 @@

context "callbacks" do
describe ".compact" do
it "needs to be implemented"
end

describe ".expand" do
it "needs to be implemented"
end

describe ".frame" do
it "needs to be implemented"
end

describe ".fromRDF" do
it "needs to be implemented"
end

describe ".toRDF" do
it "needs to be implemented"
end
end

Expand Down
93 changes: 77 additions & 16 deletions spec/compact_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,30 @@
"b" => {"@id" => "http://example.com/c"}
}
},
"integer value" => {
:input => {
"@id" => "http://example.com/a",
"http://example.com/b" => {"@value" => 1}
},
:context => {"b" => "http://example.com/b"},
:output => {
"@context" => {"b" => "http://example.com/b"},
"@id" => "http://example.com/a",
"b" => 1
}
},
"boolean value" => {
:input => {
"@id" => "http://example.com/a",
"http://example.com/b" => {"@value" => true}
},
:context => {"b" => "http://example.com/b"},
:output => {
"@context" => {"b" => "http://example.com/b"},
"@id" => "http://example.com/a",
"b" => true
}
},
"@id" => {
:input => {"@id" => "http://example.org/test#example"},
:context => {},
Expand Down Expand Up @@ -74,6 +98,24 @@
"b" => ["c", "d"]
}
},
"@list coercion (integer)" => {
:input => {
"http://example.com/term" => [
{"@list" => [1]},
]
},
:context => {
"term4" => {"@id" => "http://example.com/term", "@container" => "@list"},
"@language" => "de"
},
:output => {
"@context" => {
"term4" => {"@id" => "http://example.com/term", "@container" => "@list"},
"@language" => "de"
},
"term4" => [1],
}
},
"@set coercion" => {
:input => {
"http://example.com/b" => {"@set" => ["c"]}
Expand Down Expand Up @@ -128,6 +170,25 @@
"@type" => "#{RDF::RDFS.Resource}"
},
},
"default language" => {
:input => {
"http://example.com/term" => [
"v5",
{"@value" => "plain literal"}
]
},
:context => {
"term5" => {"@id" => "http://example.com/term", "@language" => nil},
"@language" => "de"
},
:output => {
"@context" => {
"term5" => {"@id" => "http://example.com/term", "@language" => nil},
"@language" => "de"
},
"term5" => [ "v5", "plain literal" ]
}
},
}.each_pair do |title, params|
it title do
jld = JSON::LD::API.compact(params[:input], params[:context], nil, :debug => @debug)
Expand Down Expand Up @@ -201,22 +262,22 @@

context "term selection" do
{
"Uses term with nil language when two terms conflict on language" => {
:input => [{
"http://example.com/term" => {"@value" => "v1", "@language" => nil}
}],
:context => {
"term5" => {"@id" => "http://example.com/term","@language" => nil},
"@language" => "de"
},
:output => {
"@context" => {
"term5" => {"@id" => "http://example.com/term","@language" => nil},
"@language" => "de"
},
"term5" => "v1",
}
},
#"Uses term with nil language when two terms conflict on language" => {
# :input => [{
# "http://example.com/term" => {"@value" => "v1", "@language" => nil}
# }],
# :context => {
# "term5" => {"@id" => "http://example.com/term","@language" => nil},
# "@language" => "de"
# },
# :output => {
# "@context" => {
# "term5" => {"@id" => "http://example.com/term","@language" => nil},
# "@language" => "de"
# },
# "term5" => "v1",
# }
#},
"Uses subject alias" => {
:input => [{
"@id" => "http://example.com/id1"
Expand Down
Loading

0 comments on commit 9e6fb7e

Please sign in to comment.