lib/json/ld/writer.rb in json-ld-0.0.8 vs lib/json/ld/writer.rb in json-ld-0.1.0
- old
+ new
@@ -32,64 +32,41 @@
# end
# end
#
# The writer will add prefix definitions, and use them for creating @context definitions, and minting CURIEs
#
- # @example Creating @base, @vocab and @context prefix definitions in output
+ # @example Creating @@context prefix definitions in output
# JSON::LD::Writer.buffer(
- # :base_uri => "http://example.com/",
- # :vocab => "http://example.net/"
# :prefixes => {
# nil => "http://example.com/ns#",
# :foaf => "http://xmlns.com/foaf/0.1/"}
# ) do |writer|
# graph.each_statement do |statement|
# writer << statement
# end
# end
#
- # Select the :normalize option to output JSON-LD in canonical form
+ # Select the :expand option to output JSON-LD in expanded form
#
# @see http://json-ld.org/spec/ED/20110507/
# @see http://json-ld.org/spec/ED/20110507/#the-normalization-algorithm
# @author [Gregg Kellogg](http://greggkellogg.net/)
class Writer < RDF::Writer
format Format
- # @attr [Graph] Graph of statements serialized
+ # @attr [RDF::Graph] Graph of statements serialized
attr :graph
- # @attr [URI] Base IRI used for relativizing IRIs
- attr :base_uri
- # @attr [String] Vocabulary prefix used for relativizing IRIs
- attr :vocab
+
+ # @attr [EvaluationContext] context used to load and administer contexts
+ attr :context
- # Type coersion to use for serialization. Defaults to DEFAULT_COERCION
- #
- # Maintained as a reverse mapping of `property` => `type`.
- #
- # @attr [Hash{String => String}]
- attr :coerce, true
-
##
- # Local implementation of ruby Hash class to allow for ordering in 1.8.x implementations.
- #
- # @return [Hash, InsertOrderPreservingHash]
- def self.new_hash
- if RUBY_VERSION < "1.9"
- InsertOrderPreservingHash.new
- else
- Hash.new
- end
- end
- def new_hash; self.class.new_hash; end
-
- ##
# Return the pre-serialized Hash before turning into JSON
#
# @return [Hash]
def self.hash(*args, &block)
- hash = new_hash
+ hash = Hash.new
self.new(hash, *args, &block)
hash
end
##
@@ -101,30 +78,38 @@
# any additional options
# @option options [Encoding] :encoding (Encoding::UTF_8)
# the encoding to use on the output stream (Ruby 1.9+)
# @option options [Boolean] :canonicalize (false)
# whether to canonicalize literals when serializing
- # @option options [Boolean] :normalize (false)
- # Output document in [normalized form](http://json-ld.org/spec/latest/#normalization-1)
# @option options [Hash] :prefixes (Hash.new)
# the prefix mappings to use (not supported by all writers)
- # @option options [#to_s] :base_uri (nil)
- # Base IRI used for relativizing IRIs
- # @option options [#to_s] :vocab (nil)
- # Vocabulary prefix used for relativizing IRIs
# @option options [Boolean] :standard_prefixes (false)
# Add standard prefixes to @prefixes, if necessary.
+ # @option options [IO, Array, Hash, String, EvaluationContext] :context (Hash.new)
+ # context to use when serializing. Constructed context for native serialization.
+ # @option options [Boolean] :automatic (true)
+ # Automatically create context coercions and generate compacted form
+ # @option options [Boolean] :expand (false)
+ # Output document in [expanded form](http://json-ld.org/spec/latest/json-ld-api/#expansion)
+ # @option options [Boolean] :compact (false)
+ # Output document in [compacted form](http://json-ld.org/spec/latest/json-ld-api/#compaction).
+ # Requires a referenced evaluation context
+ # @option options [Boolean] :normalize (false)
+ # Output document in [normalized form](http://json-ld.org/spec/latest/json-ld-api/#normalization)
+ # @option options [IO, Array, Hash, String] :frame
+ # Output document in [framed form](http://json-ld.org/spec/latest/json-ld-api/#framing)
+ # using the referenced document as a frame.
# @yield [writer] `self`
# @yieldparam [RDF::Writer] writer
# @yieldreturn [void]
# @yield [writer]
# @yieldparam [RDF::Writer] writer
def initialize(output = $stdout, options = {}, &block)
super do
@graph = RDF::Graph.new
- @iri_to_prefix = DEFAULT_CONTEXT.dup.delete_if {|k,v| k == '@coerce'}.invert
- @coerce = DEFAULT_COERCE.merge(options[:coerce] || {})
+ @options[:automatic] = true unless [:automatic, :expand, :compact, :frame, :normalize].any? {|k| options.has_key?(k)}
+
if block_given?
case block.arity
when 0 then instance_eval(&block)
else block.call(self)
end
@@ -136,11 +121,11 @@
# Write whole graph
#
# @param [Graph] graph
# @return [void]
def write_graph(graph)
- add_debug {"Add graph #{graph.inspect}"}
+ debug {"Add graph #{graph.inspect}"}
@graph = graph
end
##
# Addes a statement to be serialized
@@ -166,42 +151,58 @@
# Outputs the Serialized JSON-LD representation of all stored triples.
#
# @return [void]
# @see #write_triple
def write_epilogue
- @base_uri = RDF::URI(@options[:base_uri]) if @options[:base_uri] && !@options[:normalize]
- @vocab = @options[:vocab] unless @options[:normalize]
@debug = @options[:debug]
reset
+
+ raise RDF::WriterError, "Compaction requres a context" if @options[:compact] && !@options[:context]
- add_debug {"\nserialize: graph: #{@graph.size}"}
+ @context = EvaluationContext.new(@options)
+ @context = @context.parse(@options[:context]) if @options[:context]
+ @context.language = @options[:language] if @options[:language]
+ @context.lists.each {|p| @list_range[p] = true}
+ debug {"\nserialize: graph: #{@graph.size}"}
+ debug {"=> options: #{@options.reject {|k,v| k == :debug}.inspect}"}
+ debug {"=> context: #{@context.inspect}"}
+
preprocess
-
- # Don't generate context for canonical output
- json_hash = @options[:normalize] ? new_hash : start_document
+ # Update prefix mappings to those defined in context
+ @options[:prefixes] = {}
+ @context.iri_to_term.each_pair do |iri, term|
+ debug {"add prefix #{term.inspect} for #{iri}"}
+ prefix(term, iri) # Define for output
+ end
+
+ # Don't generate context for expanded or normalized output
+ json_hash = (@options[:expand] || @options[:normalize]) ? Hash.new : context.serialize(:depth => @depth)
+
elements = []
order_subjects.each do |subject|
unless is_done?(subject)
elements << subject(subject, json_hash)
end
end
return if elements.empty?
+ # If there are more than one top-level subjects, place in an array form
if elements.length == 1 && elements.first.is_a?(Hash)
json_hash.merge!(elements.first)
else
- json_hash['@subject'] = elements
+ json_hash['@id'] = elements
end
if @output.is_a?(Hash)
@output.merge!(json_hash)
else
json_state = if @options[:normalize]
+ # Normalization uses a compressed form
JSON::State.new(
:indent => "",
:space => "",
:space_before => "",
:object_nl => "",
@@ -221,50 +222,39 @@
end
##
# Returns the representation of a IRI reference.
#
- # Spec confusion: should a subject URI be normalized?
+ # Spec confusion: should a subject IRI be normalized?
#
# @param [RDF::URI] value
# @param [Hash{Symbol => Object}] options
# @option options [:subject, :predicate, :object] position
# Useful when determining how to serialize.
# @option options [RDF::URI] property
- # Property for object reference, which can be used to return
- # bare strings, rather than {"iri":}
+ # Property for object reference, which can be used to return bare strings
# @return [Object]
- def format_uri(value, options = {})
- result = case options[:position]
- when :subject
- # attempt base_uri replacement
- short = value.to_s.sub(base_uri.to_s, "")
- short == value.to_s ? (get_curie(value) || value.to_s) : short
- when :predicate
- # attempt vocab replacement
- short = '@type' if value == RDF.type
- short ||= value.to_s.sub(@vocab.to_s, "")
- short == value.to_s ? (get_curie(value) || value.to_s) : short
- else
- # Encode like a subject
- iri_range?(options[:property]) ?
- format_uri(value, :position => :subject) :
- {'@iri' => format_uri(value, :position => :subject)}
+ def format_iri(value, options = {})
+ debug {"format_iri(#{options.inspect}, #{value.inspect})"}
+
+ result = context.compact_iri(value, {:depth => @depth}.merge(options))
+ unless options[:position] != :object || iri_range?(options[:property])
+ result = {"@id" => result}
end
- add_debug {"format_uri(#{options.inspect}, #{value.inspect}) => #{result.inspect}"}
+ debug {"=> #{result.inspect}"}
result
end
##
# @param [RDF::Node] value
# @param [Hash{Symbol => Object}] options
# @return [String]
# @raise [NotImplementedError] unless implemented in subclass
- # @abstract
+ # @see {#format\_iri}
def format_node(value, options = {})
- format_uri(value, options)
+ format_iri(value, options)
end
##
# Returns the representation of a literal.
#
@@ -272,197 +262,148 @@
# @param [Hash{Symbol => Object}] options
# @option options [RDF::URI] property
# Property referencing literal for type coercion
# @return [Object]
def format_literal(literal, options = {})
- if options[:normal] || @options[:normalize]
- ret = new_hash
- ret['@literal'] = literal.value
- ret['@datatype'] = format_uri(literal.datatype, :position => :subject) if literal.has_datatype?
- ret['@language'] = literal.language.to_s if literal.has_language?
- return ret.delete_if {|k,v| v.nil?}
- end
+ debug {"format_literal(#{options.inspect}, #{literal.inspect})"}
- case literal
- when RDF::Literal::Integer, RDF::Literal::Boolean
+ value = Hash.new
+ value['@literal'] = literal.value
+ value['@type'] = literal.datatype.to_s if literal.has_datatype?
+ value['@language'] = literal.language.to_s if literal.has_language?
+
+ result = case literal
+ when RDF::Literal::Boolean, RDF::Literal::Integer, RDF::Literal::Double
literal.object
- when RDF::Literal
- if datatype_range?(options[:property]) || !(literal.has_datatype? || literal.has_language?)
- # Datatype coercion where literal has the same datatype
- literal.value
- else
- format_literal(literal, :normal => true)
- end
+ else
+ context.compact_value(options[:property], value, {:depth => @depth}.merge(options))
end
+
+ debug {"=> #{result.inspect}"}
+ result
end
##
# Serialize an RDF list
+ #
# @param [RDF::URI] object
# @param [Hash{Symbol => Object}] options
# @option options [RDF::URI] property
- # Property referencing literal for type coercion
+ # Property referencing literal for type and list coercion
# @return [Hash{"@list" => Array<Object>}]
def format_list(object, options = {})
predicate = options[:property]
- list = []
+ list = RDF::List.new(object, @graph)
+ ary = []
- add_debug {"format_list(#{object}, #{predicate})"}
+ debug {"format_list(#{list.inspect}, #{predicate})"}
- @depth += 1
- while object do
- subject_done(object)
- p = @graph.properties(object)
- item = p.fetch(RDF.first.to_s, []).first
- if item
- add_debug {"format_list serialize #{item.inspect}"}
- list << if predicate || item.literal?
- property(predicate, item)
+ depth do
+ list.each_statement do |st|
+ next unless st.predicate == RDF.first
+ debug {" format_list this: #{st.subject} first: #{st.object}"}
+ ary << if predicate || st.object.literal?
+ property(predicate, st.object)
else
- subject(item)
+ subject(st.object)
end
+ subject_done(st.subject)
end
- object = p.fetch(RDF.rest.to_s, []).first
end
- @depth -= 1
- # Returns
- add_debug {"format_list => #{{'@list' => list}.inspect}"}
- {'@list' => list}
+ # Returns
+ ary = {'@list' => ary} unless predicate && list_range?(predicate)
+ debug {"format_list => #{ary.inspect}"}
+ ary
end
private
- ##
- # Generate @context
- # @return [Hash]
- def start_document
- ctx = new_hash
- ctx['@base'] = base_uri.to_s if base_uri
- ctx['@vocab'] = vocab.to_s if vocab
-
- # Prefixes
- prefixes.keys.sort {|a,b| a.to_s <=> b.to_s}.each do |k|
- next if DEFAULT_CONTEXT.has_key?(k.to_s)
- add_debug {"prefix[#{k}] => #{prefixes[k]}"}
- ctx[k.to_s] = prefixes[k].to_s
- end
-
- # Coerce
- add_debug {"start_doc: coerce= #{coerce.inspect}"}
- unless coerce == DEFAULT_COERCE
- c_h = new_hash
- coerce.keys.sort.each do |k|
- next if ['@type', RDF.type.to_s].include?(k.to_s)
- next if [DEFAULT_COERCE[k], false, RDF::XSD.integer.to_s, RDF::XSD.boolean.to_s].include?(coerce[k])
- k_iri = k == '@iri' ? '@iri' : format_uri(k, :position => :predicate)
- d_iri = format_uri(coerce[k], :position => :subject)
- add_debug {"coerce[#{k_iri}] => #{d_iri}, k=#{k.inspect}"}
- case c_h[d_iri]
- when nil
- c_h[d_iri] = k_iri
- when Array
- c_h[d_iri] << k_iri
- else
- c_h[d_iri] = [c_h[d_iri], k_iri]
- end
- end
-
- ctx['@coerce'] = c_h unless c_h.empty?
- end
-
- add_debug {"start_doc: context=#{ctx.inspect}"}
-
- # Return hash with @context, or empty
- r = new_hash
- r['@context'] = ctx unless ctx.empty?
- r
- end
-
# Perform any preprocessing of statements required
def preprocess
- # Load defined prefixes
- (@options[:prefixes] || {}).each_pair do |k, v|
- @iri_to_prefix[v.to_s] = k
- end
- @options[:prefixes] = new_hash # Will define actual used when matched
-
@graph.each {|statement| preprocess_statement(statement)}
end
# Perform any statement preprocessing required. This is used to perform reference counts and determine required
# prefixes.
+ #
# @param [Statement] statement
def preprocess_statement(statement)
- add_debug {"preprocess: #{statement.inspect}"}
+ debug {"preprocess: #{statement.inspect}"}
references = ref_count(statement.object) + 1
@references[statement.object] = references
@subjects[statement.subject] = true
- # Pre-fetch qnames, to fill prefixes
- get_curie(statement.subject)
- get_curie(statement.predicate)
- if statement.object.literal?
- datatype_range?(statement.predicate) # To figure out coercion requirements
- else
- iri_range?(statement.predicate)
- get_curie(statement.object)
+ depth do
+ # Pre-fetch qnames, to fill prefixes
+ format_iri(statement.subject, :position => :subject)
+ format_iri(statement.predicate, :position => :predicate)
+
+ # To figure out coercion requirements
+ if statement.object.literal?
+ format_literal(statement.object, :property => statement.predicate)
+ datatype_range?(statement.predicate)
+ else
+ format_iri(statement.object, :position => :object)
+ iri_range?(statement.predicate)
+ end
+ list_range?(statement.predicate)
end
@references[statement.predicate] = ref_count(statement.predicate) + 1
end
# Serialize a subject
# Option contains referencing property, if this is recursive
# @return [Hash]
def subject(subject, options = {})
- defn = new_hash
+ defn = Hash.new
raise RDF::WriterError, "Illegal use of subject #{subject.inspect}, not supported" unless subject.resource?
subject_done(subject)
properties = @graph.properties(subject)
- add_debug {"subject: #{subject.inspect}, props: #{properties.inspect}"}
+ debug {"subject: #{subject.inspect}, props: #{properties.inspect}"}
@graph.query(:subject => subject).each do |st|
raise RDF::WriterError, "Illegal use of predicate #{st.predicate.inspect}, not supported in RDF/XML" unless st.predicate.uri?
end
- if subject.node? && ref_count(subject) > (options[:property] ? 1 : 0) && options[:normalize]
+ if subject.node? && ref_count(subject) > (options[:property] ? 1 : 0) && options[:expand]
raise RDF::WriterError, "Can't serialize named node when normalizing"
end
# Subject may be a list
if is_valid_list?(subject)
- add_debug "subject is a list"
- defn['@subject'] = format_list(subject)
+ debug "subject is a list"
+ defn['@id'] = format_list(subject)
properties.delete(RDF.first.to_s)
properties.delete(RDF.rest.to_s)
# Special case, if there are no properties, then we can just serialize the list itself
return defn if properties.empty?
elsif subject.uri? || ref_count(subject) > 1
- add_debug "subject is a uri"
+ debug "subject is an iri or it's a node referenced multiple times"
# Don't need to set subject if it's a Node without references
- defn['@subject'] = format_uri(subject, :position => :subject)
+ defn['@id'] = format_iri(subject, :position => :subject)
else
- add_debug "subject is an unreferenced BNode"
+ debug "subject is an unreferenced BNode"
end
prop_list = order_properties(properties)
- #add_debug {"=> property order: #{prop_list.to_sentence}"}
+ debug {"=> property order: #{prop_list.inspect}"}
prop_list.each do |prop|
predicate = RDF::URI.intern(prop)
- p_iri = format_uri(predicate, :position => :predicate)
- @depth += 1
- defn[p_iri] = property(predicate, properties[prop])
- add_debug {"prop(#{p_iri}) => #{properties[prop]} => #{defn[p_iri].inspect}"}
- @depth -= 1
+ p_iri = format_iri(predicate, :position => :predicate)
+ depth do
+ defn[p_iri] = property(predicate, properties[prop])
+ debug {"prop(#{p_iri}) => #{properties[prop]} => #{defn[p_iri].inspect}"}
+ end
end
- add_debug {"subject: #{subject} has defn: #{defn.inspect}"}
+ debug {"subject: #{subject} has defn: #{defn.inspect}"}
defn
end
##
# Serialize objects for a property
@@ -482,95 +423,46 @@
format_literal(objects, options.merge(:property => predicate))
else
if is_valid_list?(objects)
format_list(objects, :property => predicate)
elsif is_done?(objects) || !@subjects.include?(objects)
- format_uri(objects, :position => :object, :property => predicate)
+ format_iri(objects, :position => :object, :property => predicate)
else
subject(objects, :property => predicate)
end
end
end
##
- # Return a CURIE for the IRI, or nil. Adds namespace of CURIE to defined prefixes
- # @param [RDF::Resource] resource
- # @return [String, nil] value to use to identify IRI
- def get_curie(resource)
- add_debug {"get_curie(#{resource.inspect})"}
- case resource
- when RDF::Node
- return resource.to_s
- when String
- iri = resource
- resource = RDF::URI(resource)
- return nil unless resource.absolute?
- when RDF::URI
- iri = resource.to_s
- return iri if options[:normalize]
- else
- return nil
- end
-
- curie = case
- when @iri_to_curie.has_key?(iri)
- return @iri_to_curie[iri]
- when u = @iri_to_prefix.keys.detect {|u| iri.index(u.to_s) == 0}
- # Use a defined prefix
- prefix = @iri_to_prefix[u]
- prefix(prefix, u) # Define for output
- iri.sub(u.to_s, "#{prefix}:")
- when @options[:standard_prefixes] && vocab = RDF::Vocabulary.detect {|v| iri.index(v.to_uri.to_s) == 0}
- prefix = vocab.__name__.to_s.split('::').last.downcase
- @iri_to_prefix[vocab.to_uri.to_s] = prefix
- prefix(prefix, vocab.to_uri) # Define for output
- iri.sub(vocab.to_uri.to_s, "#{prefix}:")
- else
- nil
- end
-
- @iri_to_curie[iri] = curie
- rescue Addressable::URI::InvalidURIError => e
- raise RDF::WriterError, "Invalid IRI #{resource.inspect}: #{e.message}"
- end
-
- ##
# Take a hash from predicate IRIs to lists of values.
# Sort the lists of values. Return a sorted list of properties.
# @param [Hash{String => Array<Resource>}] properties A hash of Property to Resource mappings
# @return [Array<String>}] Ordered list of properties.
def order_properties(properties)
# Make sorted list of properties
prop_list = []
- properties.keys.sort do |a,b|
- format_uri(a, :position => :predicate) <=> format_uri(b, :position => :predicate)
+ properties.keys.sort do |a, b|
+ format_iri(a, :position => :predicate) <=> format_iri(b, :position => :predicate)
end.each do |prop|
prop_list << prop.to_s
end
prop_list
end
# Order subjects for output. Override this to output subjects in another order.
#
- # Uses #base_uri.
# @return [Array<Resource>] Ordered list of subjects
def order_subjects
seen = {}
subjects = []
return @subjects.keys.sort do |a,b|
format_iri(a, :position => :subject) <=> format_iri(b, :position => :subject)
- end if @options[:normalize]
+ end unless @options[:automatic]
- # Start with base_uri
- if base_uri && @subjects.keys.include?(base_uri)
- subjects << base_uri
- seen[base_uri] = true
- end
-
# Sort subjects by resources over bnodes, ref_counts and the subject URI itself
recursable = @subjects.keys.
select {|s| !seen.include?(s)}.
map {|r| [r.is_a?(RDF::Node) ? 1 : 0, ref_count(r), r]}.
sort
@@ -587,100 +479,134 @@
##
# Does predicate have a range of IRI?
# @param [RDF::URI] predicate
# @return [Boolean]
def iri_range?(predicate)
- return false if predicate.nil? || @options[:normalize]
+ return false if predicate.nil? || [RDF.first, RDF.rest].include?(predicate) || @options[:expand]
+ return true if predicate == RDF.type
- unless coerce.has_key?(predicate.to_s)
- # objects of all statements with the predicate may not be literal
- coerce[predicate.to_s] = @graph.query(:predicate => predicate).to_a.any? {|st| st.object.literal?} ?
- false : '@iri'
+ unless context.coerce(predicate)
+ not_iri = !@options[:automatic]
+ #debug {" (automatic) = #{(!not_iri).inspect}"}
+
+ # Any literal object makes it not so
+ not_iri ||= @graph.query(:predicate => predicate).to_a.any? do |st|
+ l = RDF::List.new(st.object, @graph)
+ #debug {" o.literal? #{st.object.literal?.inspect}"}
+ #debug {" l.valid? #{l.valid?.inspect}"}
+ #debug {" l.any.valid? #{l.to_a.any?(&:literal?).inspect}"}
+ st.object.literal? || (l.valid? && l.to_a.any?(&:literal?))
+ end
+ #debug {" (literal) = #{(!not_iri).inspect}"}
+
+ # FIXME: detect when values are all represented through chaining
+
+ context.coerce(predicate, not_iri ? false : '@id')
end
- add_debug {"iri_range(#{predicate}) = #{coerce[predicate.to_s].inspect}"}
- coerce[predicate.to_s] == '@iri'
+ debug {"iri_range(#{predicate}) = #{context.coerce(predicate).inspect}"}
+ context.coerce(predicate) == '@id'
end
##
# Does predicate have a range of specific typed literal?
# @param [RDF::URI] predicate
# @return [Boolean]
def datatype_range?(predicate)
- unless coerce.has_key?(predicate.to_s)
+ unless context.coerce(predicate)
# objects of all statements with the predicate must be literal
# and have the same non-nil datatype
dt = nil
- @graph.query(:predicate => predicate) do |st|
- if st.object.literal? && st.object.has_datatype?
- dt = st.object.datatype.to_s if dt.nil?
- dt = false unless dt == st.object.datatype.to_s
- else
- dt = false
+ if @options[:automatic]
+ @graph.query(:predicate => predicate) do |st|
+ debug {"datatype_range? literal? #{st.object.literal?.inspect} dt? #{(st.object.literal? && st.object.has_datatype?).inspect}"}
+ if st.object.literal? && st.object.has_datatype?
+ dt = st.object.datatype.to_s if dt.nil?
+ debug {"=> dt: #{st.object.datatype}"}
+ dt = false unless dt == st.object.datatype.to_s
+ else
+ dt = false
+ end
end
+ # Cause necessary prefixes to be output
+ format_iri(dt, :position => :datatype) if dt && !NATIVE_DATATYPES.include?(dt.to_s)
+ debug {"range(#{predicate}) = #{dt.inspect}"}
+ else
+ dt = false
end
- add_debug {"range(#{predicate}) = #{dt.inspect}"}
- coerce[predicate.to_s] = dt
+ context.coerce(predicate, dt)
end
- coerce[predicate.to_s]
+ context.coerce(predicate)
end
+ ##
+ # Is every use of the predicate an RDF Collection?
+ #
+ # @param [RDF::URI] predicate
+ # @return [Boolean]
+ def list_range?(predicate)
+ return false if [RDF.first, RDF.rest].include?(predicate)
+
+ unless @list_range.include?(predicate.to_s)
+ # objects of all statements with the predicate must be a list
+ @list_range[predicate.to_s] = if @options[:automatic]
+ @graph.query(:predicate => predicate).to_a.all? do |st|
+ is_valid_list?(st.object)
+ end
+ else
+ false
+ end
+ context.list(predicate, true) if @list_range[predicate.to_s]
+
+ debug {"list(#{predicate}) = #{@list_range[predicate.to_s].inspect}"}
+ end
+
+ @list_range[predicate.to_s]
+ end
+
# Reset internal helper instance variables
def reset
@depth = 0
@references = {}
@serialized = {}
@subjects = {}
- @iri_to_curie = {}
+ @list_range = {}
end
- # Add debug event to debug array, if specified
- #
- # @param [String] message
- # @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
- def add_debug(message = "")
- return unless ::JSON::LD.debug? || @options[:debug]
- message = message + yield if block_given?
- msg = "#{" " * @depth * 2}#{message}"
- STDERR.puts msg if ::JSON::LD::debug?
- @debug << msg if @debug.is_a?(Array)
- end
-
# Checks if l is a valid RDF list, i.e. no nodes have other properties.
def is_valid_list?(l)
- props = @graph.properties(l)
- unless l.node? && props.has_key?(RDF.first.to_s) || l == RDF.nil
- add_debug {"is_valid_list: false, #{l.inspect}: #{props.inspect}"}
- return false
- end
-
- while l && l != RDF.nil do
- #add_debug {"is_valid_list(length): #{props.length}"}
- return false unless props.has_key?(RDF.first.to_s) && props.has_key?(RDF.rest.to_s)
- n = props[RDF.rest.to_s]
- unless n.is_a?(Array) && n.length == 1
- add_debug {"is_valid_list: false, #{n.inspect}"}
- return false
- end
- l = n.first
- unless l.node? || l == RDF.nil
- add_debug {"is_valid_list: false, #{l.inspect}"}
- return false
- end
- props = @graph.properties(l)
- end
- add_debug {"is_valid_list: valid"}
- true
+ #debug {"is_valid_list: #{l.inspect}"}
+ return RDF::List.new(l, @graph).valid?
end
def is_done?(subject)
@serialized.include?(subject)
end
# Mark a subject as done.
def subject_done(subject)
@serialized[subject] = true
+ end
+
+ # Add debug event to debug array, if specified
+ #
+ # @param [String] message
+ # @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
+ def debug(*args)
+ return unless ::JSON::LD.debug? || @options[:debug]
+ message = " " * @depth * 2 + (args.empty? ? "" : args.join(": "))
+ message += yield if block_given?
+ puts message if JSON::LD::debug?
+ @options[:debug] << message if @options[:debug].is_a?(Array)
+ end
+
+ # Increase depth around a method invocation
+ def depth
+ @depth += 1
+ ret = yield
+ @depth -= 1
+ ret
end
end
end