lib/json/ld/evaluation_context.rb in json-ld-0.1.2 vs lib/json/ld/evaluation_context.rb in json-ld-0.1.3
- old
+ new
@@ -101,12 +101,11 @@
yield(self) if block_given?
end
# Create an Evaluation Context using an existing context as a start by parsing the input.
#
- # @param [String, #read, Array, Hash, EvaluatoinContext] input
- # @return [EvaluationContext] context
+ # @param [String, #read, Array, Hash, EvaluatoinContext] context
# @raise [InvalidContext]
# on a remote context load error, syntax error, or a reference to a term which is not defined.
def parse(context)
case context
when nil
@@ -117,12 +116,11 @@
when IO, StringIO
debug("parse") {"io: #{context}"}
# Load context document, if it is a string
begin
ctx = JSON.load(context)
- raise JSON::LD::InvalidContext::Syntax, "missing @context" unless ctx.is_a?(Hash) && ctx["@context"]
- parse(ctx["@context"])
+ parse(ctx["@context"] || {})
rescue JSON::ParserError => e
debug("parse") {"Failed to parse @context from remote document at #{context}: #{e.message}"}
raise JSON::LD::InvalidContext::Syntax, "Failed to parse remote context at #{context}: #{e.message}" if @options[:validate]
self.dup
end
@@ -159,11 +157,11 @@
# Map terms to IRIs/keywords first
context.each do |key, value|
# Expand a string value, unless it matches a keyword
debug("parse") {"Hash[#{key}] = #{value.inspect}"}
- if key == '@language'
+ if key == '@language' && (value.nil? || value.is_a?(String))
new_ec.default_language = value
elsif term_valid?(key)
# Remove all coercion information for the property
new_ec.set_coerce(key, nil)
new_ec.set_container(key, nil)
@@ -172,11 +170,13 @@
# Extract IRI mapping. This is complicated, as @id may have been aliased
value = value.fetch('@id', nil) if value.is_a?(Hash)
raise InvalidContext::Syntax, "unknown mapping for #{key.inspect} to #{value.class}" unless value.is_a?(String) || value.nil?
iri = new_ec.expand_iri(value, :position => :predicate) if value.is_a?(String)
- if iri && new_ec.mappings.fetch(key, nil) != iri
+ if iri && KEYWORDS.include?(key)
+ raise InvalidContext::Syntax, "key #{key.inspect} must not be a keyword"
+ elsif iri && new_ec.mappings.fetch(key, nil) != iri
# Record term definition
new_ec.set_mapping(key, iri)
num_updates += 1
elsif value.nil?
new_ec.set_mapping(key, nil)
@@ -323,27 +323,24 @@
end
##
# Set term mapping
#
- # @param [String] term
+ # @param [#to_s] term
# @param [RDF::URI, String] value
#
# @return [RDF::URI, String]
def set_mapping(term, value)
+ term = term.to_s
+ term_sym = term.empty? ? "" : term.to_sym
# raise InvalidContext::Syntax, "mapping term #{term.inspect} must be a string" unless term.is_a?(String)
# raise InvalidContext::Syntax, "mapping value #{value.inspect} must be an RDF::URI" unless value.nil? || value.to_s[0,1] == '@' || value.is_a?(RDF::URI)
- debug {"map #{term.inspect} to #{value}"} unless @mappings[term] == value
+ debug {"map #{term.inspect} to #{value.inspect}"}
iri_to_term.delete(@mappings[term].to_s) if @mappings[term]
- if value
- @mappings[term] = value
- @options[:prefixes][term] = value if @options.has_key?(:prefixes)
- iri_to_term[value.to_s] = term
- else
- @mappings.delete(term)
- nil
- end
+ @mappings[term] = value
+ @options[:prefixes][term_sym] = value if @options.has_key?(:prefixes)
+ iri_to_term[value.to_s] = term
end
##
# Reverse term mapping, typically used for finding aliases for keys.
#
@@ -364,11 +361,12 @@
# @param [String] property in unexpanded form
#
# @return [RDF::URI, '@id']
def coerce(property)
# Map property, if it's not an RDF::Value
- return '@id' if [RDF.type, '@type'].include?(property) # '@type' always is an IRI
+ # @type and @graph always is an IRI
+ return '@id' if [RDF.type, '@type', '@graph'].include?(property)
@coercions.fetch(property, nil)
end
##
# Set term coercion
@@ -457,19 +455,18 @@
return mapping(iri) if mapping(iri) # If it's an exact match
debug("expand_iri") {"prefix: #{prefix.inspect}, suffix: #{suffix.inspect}"} unless options[:quiet]
base = self.base unless [:predicate, :datatype].include?(options[:position])
prefix = prefix.to_s
case
- when prefix == '_' && suffix then debug("=> bnode"); bnode(suffix)
- when iri.to_s[0,1] == "@" then debug("=> keyword"); iri
- when suffix.to_s[0,2] == '//' then debug("=> iri"); uri(iri)
- when mappings.has_key?(prefix) then debug("=> curie"); uri(mappings[prefix] + suffix.to_s)
- when base then debug("=> base"); base.join(iri)
+ when prefix == '_' && suffix then bnode(suffix)
+ when iri.to_s[0,1] == "@" then iri
+ when suffix.to_s[0,2] == '//' then uri(iri)
+ when mappings.has_key?(prefix) then uri(mappings[prefix] + suffix.to_s)
+ when base then base.join(iri)
else
# Otherwise, it must be an absolute IRI
u = uri(iri)
- debug("=> absolute") {"#{u.inspect} abs? #{u.absolute?.inspect}"}
u if u.absolute? || [:subject, :object].include?(options[:position])
end
end
##
@@ -485,14 +482,10 @@
# Don't return a term, but only a CURIE or IRI.
#
# @return [String] compacted form of IRI
# @see http://json-ld.org/spec/latest/json-ld-api/#iri-compaction
def compact_iri(iri, options = {})
- # Don't cause these to be compacted
- return iri.to_s if [RDF.first, RDF.rest, RDF.nil].include?(iri)
- return self.alias('@type') if options[:position] == :predicate && iri == RDF.type
-
depth(options) do
debug {"compact_iri(#{iri.inspect}, #{options.inspect})"}
value = options.fetch(:value, nil)
@@ -545,13 +538,19 @@
# If the list of found terms is empty, append a compact IRI for
# each term which is a prefix of iri which does not have
# @type coercion, @container coercion or @language coercion rules
# along with the iri itself.
if terms.empty?
- curies = mappings.keys.map {|k| iri.to_s.sub(mapping(k).to_s, "#{k}:") if
- iri.to_s.index(mapping(k).to_s) == 0 &&
- iri.to_s != mapping(k).to_s}.compact
+ debug("curies") {"mappings: #{mappings.inspect}"}
+ curies = mappings.keys.map do |k|
+ debug("curies[#{k}]") {"#{mapping(k).inspect}"}
+ #debug("curies[#{k}]") {"#{(mapping(k).to_s.length > 0).inspect}, #{iri.to_s.index(mapping(k).to_s)}"}
+ iri.to_s.sub(mapping(k).to_s, "#{k}:") if
+ mapping(k).to_s.length > 0 &&
+ iri.to_s.index(mapping(k).to_s) == 0 &&
+ iri.to_s != mapping(k).to_s
+ end.compact
debug("curies") do
curies.map do |c|
"#{c}: " +
"container: #{container(c).inspect}, " +
@@ -579,21 +578,28 @@
iri.sub(v.to_uri.to_s, "#{prefix}:").sub(/:$/, '')
end
debug("curies") {"using standard prefies: #{terms.inspect}"}
end
- terms << iri.to_s
+ # If there is a mapping from the complete IRI to null, return null,
+ # otherwise, return the complete IRI.
+ if mappings.has_key?(iri.to_s) && !mapping(iri)
+ debug("iri") {"use nil IRI mapping"}
+ terms << nil
+ else
+ terms << iri.to_s
+ end
end
# Get the first term based on distance and lexecographical order
# Prefer terms that don't have @container @set over other terms, unless as set is true
terms = terms.sort do |a, b|
debug("term sort") {"c(a): #{container(a).inspect}, c(b): #{container(b)}"}
- if a.length == b.length
- a <=> b
+ if a.to_s.length == b.to_s.length
+ a.to_s <=> b.to_s
else
- a.length <=> b.length
+ a.to_s.length <=> b.to_s.length
end
end
debug("sorted terms") {terms.inspect}
result = terms.first
@@ -611,33 +617,43 @@
# @param [String] property
# Associated property used to find coercion rules
# @param [Hash, String] value
# Value (literal or IRI) to be expanded
# @param [Hash{Symbol => Object}] options
+ # @option options [Boolean] :native (true) use native representations
#
# @return [Hash] Object representation of value
# @raise [RDF::ReaderError] if the iri cannot be expanded
# @see http://json-ld.org/spec/latest/json-ld-api/#value-expansion
def expand_value(property, value, options = {})
+ options = {:native => true}.merge(options)
depth(options) do
debug("expand_value") {"property: #{property.inspect}, value: #{value.inspect}, coerce: #{coerce(property).inspect}"}
result = case value
when TrueClass, FalseClass, RDF::Literal::Boolean
case coerce(property)
when RDF::XSD.double.to_s
{"@value" => value.to_s, "@type" => RDF::XSD.double.to_s}
else
- # Unless there's coercion, to not modify representation
- value.is_a?(RDF::Literal::Boolean) ? value.object : value
+ if options[:native]
+ # Unless there's coercion, to not modify representation
+ {"@value" => (value.is_a?(RDF::Literal::Boolean) ? value.object : value)}
+ else
+ {"@value" => value.to_s, "@type" => RDF::XSD.boolean.to_s}
+ end
end
when Integer, RDF::Literal::Integer
case coerce(property)
when RDF::XSD.double.to_s
{"@value" => RDF::Literal::Double.new(value, :canonicalize => true).to_s, "@type" => RDF::XSD.double.to_s}
when RDF::XSD.integer.to_s, nil
# Unless there's coercion, to not modify representation
- value.is_a?(RDF::Literal::Integer) ? value.object : value
+ if options[:native]
+ {"@value" => value.is_a?(RDF::Literal::Integer) ? value.object : value}
+ else
+ {"@value" => value.to_s, "@type" => RDF::XSD.integer.to_s}
+ end
else
res = Hash.ordered
res['@value'] = value.to_s
res['@type'] = coerce(property)
res
@@ -647,12 +663,16 @@
when RDF::XSD.integer.to_s
{"@value" => value.to_int.to_s, "@type" => RDF::XSD.integer.to_s}
when RDF::XSD.double.to_s
{"@value" => RDF::Literal::Double.new(value, :canonicalize => true).to_s, "@type" => RDF::XSD.double.to_s}
when nil
- # Unless there's coercion, to not modify representation
- value.is_a?(RDF::Literal::Double) ? value.object : value
+ if options[:native]
+ # Unless there's coercion, to not modify representation
+ {"@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
else
res = Hash.ordered
res['@value'] = value.to_s
res['@type'] = coerce(property)
res
@@ -674,11 +694,11 @@
case coerce(property)
when '@id'
{'@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
res['@type'] = coerce(property).to_s
res
@@ -700,22 +720,23 @@
# @param [Hash{Symbol => Object}] 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)"}
compact_iri(value['@id'], :position => :object)
when value['@type'] && expand_iri(value['@type'], :position => :datatype) == coerce(property)
@@ -729,14 +750,22 @@
value
when value['@language'] && value['@language'] == language(property)
# 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)"}
value[self.alias('@type')] = compact_iri(value['@type'], :position => :datatype)
value
@@ -812,53 +841,49 @@
#
# @param [String] term
# @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}"}
+ 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}"
+ }
- 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
+ # 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
\ No newline at end of file
+end