lib/rdf/rdfa/writer.rb in rdf-rdfa-1.0.3 vs lib/rdf/rdfa/writer.rb in rdf-rdfa-1.1.0
- old
+ new
@@ -82,10 +82,12 @@
# whether to canonicalize literals when serializing
# @option options [Hash] :prefixes (Hash.new)
# the prefix mappings to use
# @option options [#to_s] :base_uri (nil)
# the base URI to use when constructing relative URIs, set as html>head>base.href
+ # @option options [Boolean] :validate (false)
+ # whether to validate terms when serializing
# @option options [#to_s] :lang (nil)
# Output as root @lang attribute, and avoid generation _@lang_ where possible
# @option options [Boolean] :standard_prefixes (false)
# Add standard prefixes to _prefixes_, if necessary.
# @option options [Array<RDF::URI>] :top_classes ([RDF::RDFS.Class])
@@ -133,11 +135,13 @@
##
# Addes a statement to be serialized
# @param [RDF::Statement] statement
# @return [void]
+ # @raise [RDF::WriterError] if validating and attempting to write an invalid {RDF::Term}.
def write_statement(statement)
+ raise RDF::WriterError, "Statement #{statement.inspect} is invalid" if validate? && statement.invalid?
@graph.insert(statement)
end
##
# Addes a triple to be serialized
@@ -145,12 +149,13 @@
# @param [RDF::URI] predicate
# @param [RDF::Value] object
# @return [void]
# @raise [NotImplementedError] unless implemented in subclass
# @abstract
+ # @raise [RDF::WriterError] if validating and attempting to write an invalid {RDF::Term}.
def write_triple(subject, predicate, object)
- @graph.insert(Statement.new(subject, predicate, object))
+ write_statement Statement.new(subject, predicate, object)
end
##
# Outputs the XHTML+RDFa representation of all stored triples.
#
@@ -288,48 +293,56 @@
# @param [Array<RDF::Resource>] predicate
# Predicate to render.
# @param [Array<RDF::Resource>] objects
# List of objects to render. If the list contains only a single element, the :property_value template will be used. Otherwise, the :property_values template is used.
# @param [Hash{Symbol => Object}] options Rendering options passed to Haml render.
- # @option options [String] haml (haml_template[:property_value], haml_template[:property_values])
+ # @option options [String] :haml (haml_template[:property_value], haml_template[:property_values])
# Haml template to render. Otherwise, uses `haml_template[:property_value] or haml_template[:property_values]`
# depending on the cardinality of objects.
- # @yield [object]
- # Yields object.
+ # @yield object, inlist
+ # Yields object and if it is contained in a list.
# @yieldparam [RDF::Resource] object
+ # @yieldparam [Boolean] inlist
# @yieldreturn [String, nil]
# The block should only return a string for recursive object definitions.
# @return String
# The rendered document is returned as a string
def render_property(predicate, objects, options = {}, &block)
- add_debug {"render_property(#{predicate}): #{objects.inspect}"}
+ add_debug {"render_property(#{predicate}): #{objects.inspect}, #{options.inspect}"}
# If there are multiple objects, and no :property_values is defined, call recursively with
# each object
template = options[:haml]
template ||= objects.length > 1 ? haml_template[:property_values] : haml_template[:property_value]
# Separate out the objects which are lists and render separately
- list_objects = objects.select {|o| o != RDF.nil && RDF::List.new(o, @graph).valid?}
+ list_objects = objects.reject do |o|
+ o == RDF.nil ||
+ (l = RDF::List.new(o, @graph)).invalid?
+ end
unless list_objects.empty?
# Render non-list objects
- add_debug {"properties with lists: non-lists: #{objects - list_objects} lists: #{list_objects}"}
- nl = render_property(predicate, objects - list_objects, options, &block) unless objects == list_objects
+ add_debug {"properties with lists: #{list_objects} non-lists: #{objects - list_objects}"}
+ nl = depth {render_property(predicate, objects - list_objects, options, &block)} unless objects == list_objects
return nl.to_s + list_objects.map do |object|
# Render each list as multiple properties and set :inlist to true
list = RDF::List.new(object, @graph)
list.each_statement {|st| subject_done(st.subject)}
add_debug {"list: #{list.inspect} #{list.to_a}"}
- render_property(predicate, list.to_a, options.merge(:inlist => "true"), &block)
+ depth do
+ render_property(predicate, list.to_a, options.merge(:inlist => "true")) do |object|
+ yield(object, true) if block_given?
+ end
+ end
end.join(" ")
end
if objects.length > 1 && template.nil?
- # Uf there is no property_values template, render each property using property_value template
+ # If there is no property_values template, render each property using property_value template
objects.map do |object|
- render_property(predicate, [object], options, &block)
+ depth {render_property(predicate, [object], options, &block)}
end.join(" ")
else
raise RDF::WriterError, "Missing property template" if template.nil?
template = options[:haml] || (
@@ -343,13 +356,11 @@
:predicate => predicate,
:property => get_curie(predicate),
:rel => get_curie(predicate),
:inlist => nil,
}.merge(options)
- hamlify(template, options) do |object|
- yield(object) if block_given?
- end
+ hamlify(template, options, &block)
end
end
# Perform any preprocessing of statements required
# @return [ignored]
@@ -358,11 +369,11 @@
# Add terms and prefixes to local store for converting URIs
# Keep track of vocabulary from left-most context
[XML_RDFA_CONTEXT, HTML_RDFA_CONTEXT].each do |uri|
ctx = Context.find(uri)
ctx.prefixes.each_pair do |k, v|
- @uri_to_prefix[v] = k
+ @uri_to_prefix[v] = k unless k.to_s == "dcterms"
end
ctx.terms.each_pair do |k, v|
@uri_to_term_or_curie[v] = k
end
@@ -442,12 +453,12 @@
# Perform any statement preprocessing required. This is used to perform reference counts and determine required prefixes.
# @param [RDF::Statement] statement
# @return [ignored]
def preprocess_statement(statement)
#add_debug {"preprocess: #{statement.inspect}"}
- references = ref_count(statement.object) + 1
- @references[statement.object] = references
+ bump_reference(statement.subject)
+ bump_reference(statement.object)
@subjects[statement.subject] = true
get_curie(statement.subject)
get_curie(statement.predicate)
get_curie(statement.object)
get_curie(statement.object.datatype) if statement.object.literal? && statement.object.has_datatype?
@@ -480,54 +491,80 @@
# @param [Hash{Symbol => Object}] options
# @option options [:li, nil] :element(:div)
# Serialize using <li> rather than template default element
# @option options [RDF::Resource] :rel (nil)
# Optional @rel property
- # @return [Nokogiri::XML::Element, {Namespace}]
+ # @return [String]
def subject(subject, options = {})
return if is_done?(subject)
subject_done(subject)
+ properties = properties_for_subject(subject)
+ typeof = type_of(properties.delete(RDF.type.to_s), subject)
+ prop_list = order_properties(properties)
+
+ add_debug {"props: #{prop_list.inspect}"}
+
+ render_opts = {:typeof => typeof, :property_values => properties}.merge(options)
+
+ render_subject_template(subject, prop_list, render_opts)
+ end
+
+ # @param [RDF::Resource] subject
+ # @return [Hash{String => Object}]
+ def properties_for_subject(subject)
properties = {}
@graph.query(:subject => subject) do |st|
- properties[st.predicate.to_s] ||= []
- properties[st.predicate.to_s] << st.object
+ key = st.predicate.to_s.freeze
+ properties[key] ||= []
+ properties[key] << st.object
end
- prop_list = order_properties(properties)
+ properties
+ end
+ # @param [Array,NilClass] type
+ # @param [RDF::Resource] subject
+ # @return [String] string representation of the specific RDF.type uri
+ def type_of(type, subject)
# Find appropriate template
- curie ||= case
+ curie = case
when subject.node?
- subject.to_s if ref_count(subject) >= (@depth == 0 ? 0 : 1)
+ subject.to_s if ref_count(subject) > 1
else
get_curie(subject)
end
- # See if there's a template based on the sorted concatenation of all types of this subject
- # or any type of this subject
- tmpl = find_template(subject)
-
- typeof = Array(properties.delete(RDF.type.to_s)).map {|r| get_curie(r)}.join(" ")
+ typeof = Array(type).map {|r| get_curie(r)}.join(" ")
typeof = nil if typeof.empty?
# Nodes without a curie need a blank @typeof to generate a subject
typeof ||= "" unless curie
- prop_list -= [RDF.type.to_s]
+ add_debug {"subject: #{curie.inspect}, typeof: #{typeof.inspect}" }
+
+ typeof.freeze
+ end
+
+ # @param [RDF::Resource] subject
+ # @param [Array] prop_list
+ # @param [Hash] render_opts
+ # @return [String]
+ def render_subject_template(subject, prop_list, render_opts)
+ # See if there's a template based on the sorted concatenation of all types of this subject
+ # or any type of this subject
+ tmpl = find_template(subject)
add_debug {"subject: found template #{tmpl[:identifier] || tmpl.inspect}"} if tmpl
- add_debug {"subject: #{curie.inspect}, typeof: #{typeof.inspect}, props: #{prop_list.inspect}"}
# Render this subject
# If :rel is specified and :typeof is nil, use @resource instead of @about.
# Pass other options from calling context
- render_opts = {:typeof => typeof}.merge(options)
with_template(tmpl) do
render_subject(subject, prop_list, render_opts) do |pred|
depth do
pred = RDF::URI(pred) if pred.is_a?(String)
- values = properties[pred.to_s]
+ values = render_opts[:property_values][pred.to_s]
add_debug {"subject: #{get_curie(subject)}, pred: #{get_curie(pred)}, values: #{values.inspect}"}
predicate(pred, values)
end
end
end
@@ -545,14 +582,14 @@
add_debug {"predicate: #{predicate.inspect}, objects: #{objects}"}
return if objects.to_a.empty?
add_debug {"predicate: #{get_curie(predicate)}"}
- render_property(predicate, objects) do |o|
+ render_property(predicate, objects) do |o, inlist=nil|
# Yields each object, for potential recursive definition.
# If nil is returned, a leaf is produced
- depth {subject(o, :rel => get_curie(predicate), :element => (:li if objects.length > 1))} if !is_done?(o) && @subjects.include?(o)
+ depth {subject(o, :rel => get_curie(predicate), :inlist => inlist, :element => (:li if objects.length > 1 || inlist))} if !is_done?(o) && @subjects.include?(o)
end
end
# Haml rendering helper. Return CURIE for the literal datatype, if the literal is a typed literal.
#
@@ -565,15 +602,15 @@
end
# Haml rendering helper. Return language for plain literal, if there is no language, or it is the same as the document, return nil
#
# @param [RDF::Literal] literal
- # @return [String, nil]
+ # @return [Symbol, nil]
# @raise [RDF::WriterError]
def get_lang(literal)
raise RDF::WriterError, "Getting datatype CURIE for #{literal.inspect}, which must be a literal" unless literal.is_a?(RDF::Literal)
- literal.language if literal.literal? && literal.language && literal.language != @lang
+ literal.language if literal.literal? && literal.language && literal.language.to_s != @lang.to_s
end
# Haml rendering helper. Data to be added to a @content value
#
# @param [RDF::Literal] literal
@@ -656,11 +693,11 @@
end
#add_debug {"get_curie(#{resource}) => #{curie}"}
@uri_to_term_or_curie[uri] = curie
- rescue Argument => e
+ rescue ArgumentError => e
raise RDF::WriterError, "Invalid URI #{uri.inspect}: #{e.message}"
end
private
##
@@ -674,18 +711,27 @@
def escape_entities(str)
CGI.escapeHTML(str).gsub(/[\n\r]/) {|c| '&#x' + c.unpack('h').first + ';'}
end
# Increase depth around a method invocation
+ # @yield
+ # Yields with no arguments
+ # @yieldreturn [Object] returns the result of yielding
+ # @return [Object]
def depth
@depth += 1
ret = yield
@depth -= 1
ret
end
# Set the template to use within block
+ # @param [Hash{Symbol => String}] templ template to use for block evaluation; merged in with the existing template.
+ # @yield
+ # Yields with no arguments
+ # @yieldreturn [Object] returns the result of yielding
+ # @return [Object]
def with_template(templ)
if templ
new_template = @options[:haml].
reject {|k,v| ![:subject, :property_value, :property_values, :rel].include?(k)}.
merge(templ || {})
@@ -732,19 +778,33 @@
# @param [RDF::URI] subject
# @return [Hash] # return matched matched template
def find_template(subject); end
# Mark a subject as done.
+ # @param [RDF::Resource] subject
+ # @return [Boolean]
def subject_done(subject)
@serialized[subject] = true
end
+ # Determine if the subject has been completed
+ # @param [RDF::Resource] subject
+ # @return [Boolean]
def is_done?(subject)
@serialized.include?(subject)
end
+ # Increase the reference count of this resource
+ # @param [RDF::Resource] resource
+ # @return [Integer] resulting reference count
+ def bump_reference(resource)
+ @references[resource] = ref_count(resource) + 1
+ end
+
# Return the number of times this node has been referenced in the object position
+ # @param [RDF::Node] node
+ # @return [Boolean]
def ref_count(node)
@references.fetch(node, 0)
end
# Add debug event to debug array, if specified
@@ -754,10 +814,10 @@
def add_debug(message = "")
return unless ::RDF::RDFa.debug? || @debug
message = message + yield if block_given?
msg = "#{' ' * @depth}#{message}"
STDERR.puts msg if ::RDF::RDFa.debug?
- @debug << msg if @debug.is_a?(Array)
+ @debug << msg.force_encoding("utf-8") if @debug.is_a?(Array)
end
end
end
require 'rdf/rdfa/writer/haml_templates'