lib/json/ld/writer.rb in json-ld-0.0.7 vs lib/json/ld/writer.rb in json-ld-0.0.8
- old
+ new
@@ -5,11 +5,11 @@
# Note that the natural interface is to write a whole graph at a time.
# Writing statements or Triples will create a graph to add them to
# and then serialize the graph.
#
# @example Obtaining a JSON-LD writer class
- # RDF::Writer.for(:jsonld) #=> RDF::N3::Writer
+ # RDF::Writer.for(:jsonld) #=> JSON::LD::Writer
# RDF::Writer.for("etc/test.json")
# RDF::Writer.for(:file_name => "etc/test.json")
# RDF::Writer.for(:file_extension => "json")
# RDF::Writer.for(:content_type => "application/turtle")
#
@@ -45,11 +45,11 @@
# graph.each_statement do |statement|
# writer << statement
# end
# end
#
- # Select the :canonicalize option to output JSON-LD in canonical form
+ # Select the :normalize option to output JSON-LD in canonical 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
@@ -67,10 +67,14 @@
# 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
@@ -97,10 +101,12 @@
# 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)
@@ -113,11 +119,11 @@
# @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
+ @iri_to_prefix = DEFAULT_CONTEXT.dup.delete_if {|k,v| k == '@coerce'}.invert
@coerce = DEFAULT_COERCE.merge(options[:coerce] || {})
if block_given?
case block.arity
when 0 then instance_eval(&block)
else block.call(self)
@@ -130,11 +136,11 @@
# Write whole graph
#
# @param [Graph] graph
# @return [void]
def write_graph(graph)
- add_debug "Add graph #{graph.inspect}"
+ add_debug {"Add graph #{graph.inspect}"}
@graph = graph
end
##
# Addes a statement to be serialized
@@ -160,22 +166,22 @@
# 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[:canonicalize]
- @vocab = @options[:vocab] unless @options[:canonicalize]
+ @base_uri = RDF::URI(@options[:base_uri]) if @options[:base_uri] && !@options[:normalize]
+ @vocab = @options[:vocab] unless @options[:normalize]
@debug = @options[:debug]
reset
- add_debug "\nserialize: graph: #{@graph.size}"
+ add_debug {"\nserialize: graph: #{@graph.size}"}
preprocess
# Don't generate context for canonical output
- json_hash = @options[:canonicalize] ? new_hash : start_document
+ json_hash = @options[:normalize] ? new_hash : start_document
elements = []
order_subjects.each do |subject|
unless is_done?(subject)
elements << subject(subject, json_hash)
@@ -185,17 +191,17 @@
return if elements.empty?
if elements.length == 1 && elements.first.is_a?(Hash)
json_hash.merge!(elements.first)
else
- json_hash[SUBJECT] = elements
+ json_hash['@subject'] = elements
end
if @output.is_a?(Hash)
@output.merge!(json_hash)
else
- json_state = if @options[:canonicalize]
+ json_state = if @options[:normalize]
JSON::State.new(
:indent => "",
:space => "",
:space_before => "",
:object_nl => "",
@@ -233,21 +239,21 @@
# 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 = '@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)}
+ {'@iri' => format_uri(value, :position => :subject)}
end
- add_debug("format_uri(#{options.inspect}, #{value.inspect}) => #{result.inspect}")
+ add_debug {"format_uri(#{options.inspect}, #{value.inspect}) => #{result.inspect}"}
result
end
##
# @param [RDF::Node] value
@@ -266,15 +272,15 @@
# @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[:canonical] || @options[:canonicalize]
+ 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?
+ 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
case literal
when RDF::Literal::Integer, RDF::Literal::Boolean
@@ -282,35 +288,35 @@
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, :canonical => true)
+ format_literal(literal, :normal => true)
end
end
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
- # @return [Array<Array<Object>>]
+ # @return [Hash{"@list" => Array<Object>}]
def format_list(object, options = {})
predicate = options[:property]
list = []
- add_debug "format_list(#{object}, #{predicate})"
+ add_debug {"format_list(#{object}, #{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}"
+ add_debug {"format_list serialize #{item.inspect}"}
list << if predicate || item.literal?
property(predicate, item)
else
subject(item)
end
@@ -318,58 +324,58 @@
object = p.fetch(RDF.rest.to_s, []).first
end
@depth -= 1
# Returns
- add_debug "format_list => #{[list].inspect}"
- [list]
+ add_debug {"format_list => #{{'@list' => list}.inspect}"}
+ {'@list' => list}
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
+ 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]}"
+ add_debug {"prefix[#{k}] => #{prefixes[k]}"}
ctx[k.to_s] = prefixes[k].to_s
end
# Coerce
- add_debug "start_doc: coerce= #{coerce.inspect}"
+ 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 ['@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)
+ 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}"
+ 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?
+ ctx['@coerce'] = c_h unless c_h.empty?
end
- add_debug "start_doc: context=#{ctx.inspect}"
+ add_debug {"start_doc: context=#{ctx.inspect}"}
# Return hash with @context, or empty
r = new_hash
- r[CONTEXT] = ctx unless ctx.empty?
+ r['@context'] = ctx unless ctx.empty?
r
end
# Perform any preprocessing of statements required
def preprocess
@@ -384,11 +390,11 @@
# 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}"
+ add_debug {"preprocess: #{statement.inspect}"}
references = ref_count(statement.object) + 1
@references[statement.object] = references
@subjects[statement.subject] = true
# Pre-fetch qnames, to fill prefixes
@@ -412,51 +418,51 @@
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}"
+ add_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[:canonicalize]
+ if subject.node? && ref_count(subject) > (options[:property] ? 1 : 0) && options[:normalize]
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)
+ defn['@subject'] = 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[SUBJECT].first if properties.empty?
+ return defn if properties.empty?
elsif subject.uri? || ref_count(subject) > 1
add_debug "subject is a uri"
# Don't need to set subject if it's a Node without references
- defn[SUBJECT] = format_uri(subject, :position => :subject)
+ defn['@subject'] = format_uri(subject, :position => :subject)
else
add_debug "subject is an unreferenced BNode"
end
prop_list = order_properties(properties)
- #add_debug "=> property order: #{prop_list.to_sentence}"
+ #add_debug {"=> property order: #{prop_list.to_sentence}"}
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}"
+ add_debug {"prop(#{p_iri}) => #{properties[prop]} => #{defn[p_iri].inspect}"}
@depth -= 1
end
- add_debug "subject: #{subject} has defn: #{defn.inspect}"
+ add_debug {"subject: #{subject} has defn: #{defn.inspect}"}
defn
end
##
# Serialize objects for a property
@@ -488,21 +494,21 @@
##
# 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})"
+ 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[:canonicalize]
+ return iri if options[:normalize]
else
return nil
end
curie = case
@@ -553,11 +559,11 @@
seen = {}
subjects = []
return @subjects.keys.sort do |a,b|
format_iri(a, :position => :subject) <=> format_iri(b, :position => :subject)
- end if @options[:canonicalize]
+ end if @options[:normalize]
# Start with base_uri
if base_uri && @subjects.keys.include?(base_uri)
subjects << base_uri
seen[base_uri] = true
@@ -581,20 +587,20 @@
##
# Does predicate have a range of IRI?
# @param [RDF::URI] predicate
# @return [Boolean]
def iri_range?(predicate)
- return false if predicate.nil? || @options[:canonicalize]
+ return false if predicate.nil? || @options[:normalize]
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
+ false : '@iri'
end
- add_debug "iri_range(#{predicate}) = #{coerce[predicate.to_s].inspect}"
- coerce[predicate.to_s] == IRI
+ add_debug {"iri_range(#{predicate}) = #{coerce[predicate.to_s].inspect}"}
+ coerce[predicate.to_s] == '@iri'
end
##
# Does predicate have a range of specific typed literal?
# @param [RDF::URI] predicate
@@ -610,11 +616,11 @@
dt = false unless dt == st.object.datatype.to_s
else
dt = false
end
end
- add_debug "range(#{predicate}) = #{dt.inspect}"
+ add_debug {"range(#{predicate}) = #{dt.inspect}"}
coerce[predicate.to_s] = dt
end
coerce[predicate.to_s]
end
@@ -628,40 +634,43 @@
@iri_to_curie = {}
end
# Add debug event to debug array, if specified
#
- # @param [String] message::
- def add_debug(message)
+ # @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}"
+ 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}"
+ #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}"
+ 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}"
+ add_debug {"is_valid_list: false, #{l.inspect}"}
return false
end
props = @graph.properties(l)
end
- add_debug "is_valid_list: valid"
+ add_debug {"is_valid_list: valid"}
true
end
def is_done?(subject)
@serialized.include?(subject)