lib/rdf/rdfa/reader.rb in rdf-rdfa-0.3.5.1 vs lib/rdf/rdfa/reader.rb in rdf-rdfa-0.3.6
- old
+ new
@@ -10,10 +10,12 @@
# @see http://www.w3.org/TR/2011/WD-xhtml-rdfa-20110331/ XHTML+RDFa 1.1
#
# @author [Gregg Kellogg](http://kellogg-assoc.com/)
class Reader < RDF::Reader
format Format
+ include Expansion
+
XHTML = "http://www.w3.org/1999/xhtml"
SafeCURIEorCURIEorURI = {
:"rdfa1.0" => [:term, :safe_curie, :uri, :bnode],
:"rdfa1.1" => [:safe_curie, :curie, :term, :uri, :bnode],
@@ -55,80 +57,104 @@
attr_reader :base_uri
# The Recursive Baggage
# @private
class EvaluationContext # :nodoc:
+ ##
# The base.
#
# This will usually be the URL of the document being processed,
# but it could be some other URL, set by some other mechanism,
# such as the (X)HTML base element. The important thing is that it establishes
# a URL against which relative paths can be resolved.
#
- # @return [URI]
+ # @attr [RDF::URI]
attr :base, true
+
+ ##
# The parent subject.
#
# The initial value will be the same as the initial value of base,
# but it will usually change during the course of processing.
#
- # @return [URI]
+ # @attr [RDF::URI]
attr :parent_subject, true
+
+ ##
# The parent object.
#
# In some situations the object of a statement becomes the subject of any nested statements,
# and this property is used to convey this value.
# Note that this value may be a bnode, since in some situations a number of nested statements
# are grouped together on one bnode.
# This means that the bnode must be set in the containing statement and passed down,
# and this property is used to convey this value.
#
- # @return URI
+ # @attr [RDF::URI]
attr :parent_object, true
+
+ ##
# A list of current, in-scope URI mappings.
#
- # @return [Hash{Symbol => String}]
+ # @attr [Hash{Symbol => String}]
attr :uri_mappings, true
+
+ ##
# A list of current, in-scope Namespaces. This is the subset of uri_mappings
# which are defined using xmlns.
#
- # @return [Hash{String => Namespace}]
+ # @attr [Hash{String => Namespace}]
attr :namespaces, true
+
+ ##
# A list of incomplete triples.
#
# A triple can be incomplete when no object resource
# is provided alongside a predicate that requires a resource (i.e., @rel or @rev).
# The triples can be completed when a resource becomes available,
# which will be when the next subject is specified (part of the process called chaining).
#
- # @return [Array<Array<URI, Resource>>]
+ # @attr [Array<Array<RDF::URI, RDF::Resource>>]
attr :incomplete_triples, true
+
+ ##
# The language. Note that there is no default language.
#
- # @return [Symbol]
+ # @attr [Symbol]
attr :language, true
+
+ ##
# The term mappings, a list of terms and their associated URIs.
#
# This specification does not define an initial list.
# Host Languages may define an initial list.
# If a Host Language provides an initial list, it should do so via an RDFa Profile document.
#
- # @return [Hash{Symbol => URI}]
+ # @attr [Hash{Symbol => RDF::URI}]
attr :term_mappings, true
+
+ ##
# The default vocabulary
#
# A value to use as the prefix URI when a term is used.
# This specification does not define an initial setting for the default vocabulary.
# Host Languages may define an initial setting.
#
- # @return [URI]
+ # @attr [RDF::URI]
attr :default_vocabulary, true
+ ##
+ # collections
+ #
+ # A hash associating collections with properties.
+ # @attr [Hash{RDF::URI => Array<RDF::Resource>}]
+ attr :collection_mappings, true
+
# @param [RDF::URI] base
# @param [Hash] host_defaults
- # @option host_defaults [Hash{String => URI}] :term_mappings Hash of NCName => URI
- # @option host_defaults [Hash{String => URI}] :vocabulary Hash of prefix => URI
+ # @option host_defaults [Hash{String => RDF::URI}] :term_mappings Hash of NCName => URI
+ # @option host_defaults [Hash{String => RDF::URI}] :vocabulary Hash of prefix => URI
def initialize(base, host_defaults)
# Initialize the evaluation context, [5.1]
@base = base
@parent_subject = @base
@parent_object = nil
@@ -146,17 +172,21 @@
def initialize_copy(from)
# clone the evaluation context correctly
@uri_mappings = from.uri_mappings.clone
@incomplete_triples = from.incomplete_triples.clone
@namespaces = from.namespaces.clone
+ @collection_mappings = from.collection_mappings # Don't clone
end
def inspect
- v = %w(base parent_subject parent_object language default_vocabulary).map {|a| "#{a}='#{self.send(a).inspect}'"}
+ v = ['base', 'parent_subject', 'parent_object', 'language', 'default_vocabulary'].map do |a|
+ "#{a}='#{self.send(a).inspect}'"
+ end
v << "uri_mappings[#{uri_mappings.keys.length}]"
v << "incomplete_triples[#{incomplete_triples.length}]"
v << "term_mappings[#{term_mappings.keys.length}]"
+ v << "collections[#{collection_mappings.keys.length}]" if collection_mappings
v.join(", ")
end
end
##
@@ -170,24 +200,26 @@
# the encoding of the input stream (Ruby 1.9+)
# @option options [Boolean] :validate (false)
# whether to validate the parsed statements and values
# @option options [Boolean] :canonicalize (false)
# whether to canonicalize parsed literals
+ # @option options [Boolean] :expand (false)
+ # whether to perform RDFS expansion on the resulting graph
# @option options [Boolean] :intern (true)
# whether to intern all parsed URIs
# @option options [Hash] :prefixes (Hash.new)
# the prefix mappings to use (not supported by all readers)
# @option options [#to_s] :base_uri (nil)
# the base URI to use when resolving relative URIs
# @option options [:xml1, :xhtml1, :xhtml5, :html4, :html5, :svg] :host_language (:xhtml1)
# Host Language
# @option options [:"rdfa1.0", :"rdfa1.1"] :version (:"rdfa1.1")
# Parser version information
- # @option options [Graph] :processor_graph (nil)
+ # @option options [RDF::Writable] :processor_graph (nil)
# Graph to record information, warnings and errors.
- # @option options [Repository] :profile_repository (nil)
- # Repository to save profile graphs.
+ # @option options [Repository] :vocab_repository (nil)
+ # Repository to save loaded vocabularies.
# @option options [Array] :debug
# Array to place debug messages
# @return [reader]
# @yield [reader] `self`
# @yieldparam [RDF::Reader] reader
@@ -252,11 +284,10 @@
add_info(@doc, "version = #{@version}, host_language = #{@host_language}")
block.call(self) if block_given?
end
- self.profile_repository = options[:profile_repository] if options[:profile_repository]
end
# Determine the host language and/or version from options and the input document
def detect_host_language_version(input, options)
@host_language = options[:host_language] ? options[:host_language].to_sym : nil
@@ -339,37 +370,34 @@
end
@host_language ||= :xml1
end
- # @return [RDF::Repository]
- def profile_repository
- Profile.repository
- end
-
- # @param [RDF::Repository] repo
- # @return [RDF::Repository]
- def profile_repository=(repo)
- Profile.repository = repo
- end
-
##
# Iterates the given block for each RDF statement in the input.
#
+ # Reads to graph and performs expansion if required.
+ #
# @yield [statement]
# @yieldparam [RDF::Statement] statement
# @return [void]
def each_statement(&block)
- @callback = block
+ if @options[:expand]
+ @options[:expand] = false
+ expand.each_statement(&block)
+ @options[:expand] = true
+ else
+ @callback = block
- # Add prefix definitions from host defaults
- @host_defaults[:uri_mappings].each_pair do |prefix, value|
- prefix(prefix, value)
- end
+ # Add prefix definitions from host defaults
+ @host_defaults[:uri_mappings].each_pair do |prefix, value|
+ prefix(prefix, value)
+ end
- # parse
- parse_whole_document(@doc, @base_uri)
+ # parse
+ parse_whole_document(@doc, @base_uri)
+ end
end
##
# Iterates the given block for each RDF triple in the input.
#
@@ -400,13 +428,16 @@
end
end
# Add debug event to debug array, if specified
#
- # @param [XML Node, any] node:: XML Node or string for showing context
+ # @param [Nokogiri::XML::Node, #to_s] node:: XML Node or string for showing context
# @param [String] message::
- def add_debug(node, message)
+ # @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
+ def add_debug(node, message = "")
+ return unless ::RDF::RDFa.debug? || @debug
+ message = message + yield if block_given?
add_processor_message(node, message, RDF::RDFA.Info)
end
def add_info(node, message, process_class = RDF::RDFA.Info)
add_processor_message(node, message, process_class)
@@ -437,23 +468,22 @@
end
end
# add a statement, object can be literal or URI or bnode
#
- # @param [Nokogiri::XML::Node, any] node:: XML Node or string for showing context
- # @param [URI, BNode] subject:: the subject of the statement
- # @param [URI] predicate:: the predicate of the statement
- # @param [URI, BNode, Literal] object:: the object of the statement
- # @return [Statement]:: Added statement
- # @raise [ReaderError]:: Checks parameter types and raises if they are incorrect if parsing mode is _validate_.
+ # @param [Nokogiri::XML::Node, #to_s] node:: XML Node or string for showing context
+ # @param [RDF::URI, RDF::BNode] subject:: the subject of the statement
+ # @param [RDF::URI] predicate:: the predicate of the statement
+ # @param [URI, RDF::BNode, RDF::Literal] object:: the object of the statement
+ # @return [RDF::Statement]:: Added statement
+ # @raise [RDF::ReaderError]:: Checks parameter types and raises if they are incorrect if parsing mode is _validate_.
def add_triple(node, subject, predicate, object)
statement = RDF::Statement.new(subject, predicate, object)
add_info(node, "statement: #{RDF::NTriples.serialize(statement)}")
@callback.call(statement)
end
-
# Parsing an RDFa document (this is *not* the recursive method)
def parse_whole_document(doc, base)
# find if the document has a base element
case @host_language
when :xhtml1, :xhtml5, :html4, :html5
@@ -466,20 +496,20 @@
if (base)
# Strip any fragment from base
base = base.to_s.split("#").first
base = uri(base)
- add_debug("", "parse_whole_doc: base='#{base}'")
+ add_debug("") {"parse_whole_doc: base='#{base}'"}
end
# initialize the evaluation context with the appropriate base
evaluation_context = EvaluationContext.new(base, @host_defaults)
if @version != :"rdfa1.0"
# Process default vocabularies
process_profile(doc.root, @host_defaults[:profiles]) do |which, value|
- add_debug(doc.root, "parse_whole_document, #{which}: #{value.inspect}")
+ add_debug(doc.root) { "parse_whole_document, #{which}: #{value.inspect}"}
case which
when :uri_mappings then evaluation_context.uri_mappings.merge!(value)
when :term_mappings then evaluation_context.term_mappings.merge!(value)
when :default_vocabulary then evaluation_context.default_vocabulary = value
end
@@ -592,11 +622,11 @@
if element.nil?
add_error(element, "Can't parse nil element")
return nil
end
- add_debug(element, "traverse, ec: #{evaluation_context.inspect}")
+ add_debug(element) { "ec: #{evaluation_context.inspect}" }
# local variables [7.5 Step 1]
recurse = true
skip = false
new_subject = nil
@@ -605,13 +635,12 @@
namespaces = evaluation_context.namespaces.clone
incomplete_triples = []
language = evaluation_context.language
term_mappings = evaluation_context.term_mappings.clone
default_vocabulary = evaluation_context.default_vocabulary
+ collection_mappings = evaluation_context.collection_mappings
- current_object_literal = nil # XXX Not explicit
-
# shortcut
attrs = element.attributes
about = attrs['about']
src = attrs['src']
@@ -627,87 +656,65 @@
typeof = attrs['typeof'].to_s.strip if attrs.has_key?('typeof')
datatype = attrs['datatype'].to_s if attrs['datatype']
content = attrs['content'].to_s if attrs['content']
rel = attrs['rel'].to_s.strip if attrs['rel']
rev = attrs['rev'].to_s.strip if attrs['rev']
- profiles = attrs['profile'].to_s.split(/\s/) # In-scope profiles in order for passing to XMLLiteral
- attrs = {
- :about => about,
- :src => src,
- :resource => resource,
- :href => href,
- :vocab => vocab,
- :base => xml_base,
- :property => property,
- :typeof => typeof,
- :datatype => datatype,
- :rel => rel,
- :rev => rev,
- :profiles => (profiles.empty? ? nil : profiles),
- }.select{|k,v| v}
-
- add_debug(element, "traverse " + attrs.map{|a| "#{a.first}: #{a.last}"}.join(", ")) unless attrs.empty?
+ # Collections:
+ # @member
+ # an attribute (value ignored) used to indicate that the object associated with a
+ # @rel or @property attribute on the same element is to be added to the collection
+ # for that property. Causes a collection to be created if it does not already exist.
+ member = attrs['member'].to_s.strip if attrs.has_key?('member')
- # Local term mappings [7.5 Steps 2]
- # Next the current element is parsed for any updates to the local term mappings and local list of URI mappings via @profile.
- # If @profile is present, its value is processed as defined in RDFa Profiles.
- unless @version == :"rdfa1.0"
- begin
- process_profile(element, profiles) do |which, value|
- add_debug(element, "[Step 2] traverse, #{which}: #{value.inspect}")
- case which
- when :uri_mappings
- value.each do |k, v|
- if k.to_s.match(NC_REGEXP)
- uri_mappings[k] = v
- else
- add_error(element, "[Step 2] traverse: Prefix #{k.to_s.inspect} does not match NCName production")
- end
- end
- when :term_mappings
- value.each do |k, v|
- if k.to_s.match(NC_REGEXP)
- term_mappings[k] = v
- else
- add_error(element, "[Step 2] traverse: Term #{k.to_s.inspect} does not match NCName production")
- end
- end
- when :default_vocabulary
- default_vocabulary = value
- end
- end
- rescue
- # Skip this element and all sub-elements
- # If any referenced RDFa Profile is not available, then the current element and its children must not place any
- # triples in the default graph .
- raise if validate?
- return
- end
- end
+ add_debug(element) do
+ attrs = {
+ :about => about,
+ :src => src,
+ :resource => resource,
+ :href => href,
+ :vocab => vocab,
+ :base => xml_base,
+ :property => property,
+ :typeof => typeof,
+ :datatype => datatype,
+ :rel => rel,
+ :rev => rev,
+ :member => member,
+ }.select {|k,v| v}
- # Default vocabulary [7.5 Step 3]
+ "attrs " + attrs.map {|a| "#{a.first}: #{a.last}"}.join(", ")
+ end unless attrs.empty?
+
+ # Default vocabulary [7.5 Step 2]
# Next the current element is examined for any change to the default vocabulary via @vocab.
# If @vocab is present and contains a value, its value updates the local default vocabulary.
# If the value is empty, then the local default vocabulary must be reset to the Host Language defined default.
unless vocab.nil?
default_vocabulary = if vocab.to_s.empty?
# Set default_vocabulary to host language default
- add_debug(element, "[Step 3] traverse, reset default_vocaulary to #{@host_defaults.fetch(:vocabulary, nil).inspect}")
+ add_debug(element) {
+ "[Step 3] reset default_vocaulary to #{@host_defaults.fetch(:vocabulary, nil).inspect}"
+ }
@host_defaults.fetch(:vocabulary, nil)
else
+ # Generate a triple indicating that the vocabulary is used
+ add_triple(element, base, RDF::RDFA.hasVocabulary, uri(vocab))
+
uri(vocab)
end
- add_debug(element, "[Step 3] traverse, default_vocaulary: #{default_vocabulary.inspect}")
+ add_debug(element) {
+ "[Step 2] default_vocaulary: #{default_vocabulary.inspect}"
+ }
end
- # Local term mappings [7.5 Steps 4]
+ # Local term mappings [7.5 Step 3]
# Next, the current element is then examined for URI mapping s and these are added to the local list of URI mappings.
# Note that a URI mapping will simply overwrite any current mapping in the list that has the same name
extract_mappings(element, uri_mappings, namespaces)
- # Language information [7.5 Step 5]
+ # Language information [7.5 Step 4]
# From HTML5 [3.2.3.3]
# If both the lang attribute in no namespace and the lang attribute in the XML namespace are set
# on an element, user agents must use the lang attribute in the XML namespace, and the lang
# attribute in no namespace must be ignored for the purposes of determining the element's
# language.
@@ -722,11 +729,11 @@
element.at_xpath("@lang").to_s
else
language
end
language = nil if language.to_s.empty?
- add_debug(element, "HTML5 [3.2.3.3] traverse, lang: #{language || 'nil'}") if language
+ add_debug(element, "HTML5 [3.2.3.3] lang: #{language || 'nil'}") if language
# rels and revs
rels = process_uris(element, rel, evaluation_context, base,
:uri_mappings => uri_mappings,
:term_mappings => term_mappings,
@@ -736,14 +743,16 @@
:uri_mappings => uri_mappings,
:term_mappings => term_mappings,
:vocab => default_vocabulary,
:restrictions => TERMorCURIEorAbsURI[@version])
- add_debug(element, "traverse, rels: #{rels.join(" ")}, revs: #{revs.join(" ")}") unless (rels + revs).empty?
+ add_debug(element) do
+ "rels: #{rels.join(" ")}, revs: #{revs.join(" ")}"
+ end unless (rels + revs).empty?
if !(rel || rev)
- # Establishing a new subject if no rel/rev [7.5 Step 6]
+ # Establishing a new subject if no rel/rev [7.5 Step 5]
# May not be valid, but can exist
new_subject = if about
process_uri(element, about, evaluation_context, base,
:uri_mappings => uri_mappings,
:restrictions => SafeCURIEorCURIEorURI[@version])
@@ -775,13 +784,13 @@
else
# if it's null, it's null and nothing changes
skip = true unless property
evaluation_context.parent_object
end
- add_debug(element, "[Step 6] new_subject: #{new_subject}, skip = #{skip}")
+ add_debug(element, "[Step 5] new_subject: #{new_subject}, skip = #{skip}")
else
- # [7.5 Step 7]
+ # [7.5 Step 6]
# If the current element does contain a @rel or @rev attribute, then the next step is to
# establish both a value for new subject and a value for current object resource:
new_subject = process_uri(element, about, evaluation_context, base,
:uri_mappings => uri_mappings,
:restrictions => SafeCURIEorCURIEorURI[@version]) ||
@@ -813,14 +822,14 @@
elsif href
process_uri(element, href, evaluation_context, base,
:restrictions => [:uri])
end
- add_debug(element, "[Step 7] new_subject: #{new_subject}, current_object_resource = #{current_object_resource.nil? ? 'nil' : current_object_resource}")
+ add_debug(element, "[Step 6] new_subject: #{new_subject}, current_object_resource = #{current_object_resource.nil? ? 'nil' : current_object_resource}")
end
- # Process @typeof if there is a subject [Step 8]
+ # Process @typeof if there is a subject [Step 7]
if new_subject and typeof
# Typeof is TERMorCURIEorAbsURIs
types = process_uris(element, typeof, evaluation_context, base,
:uri_mappings => uri_mappings,
:term_mappings => term_mappings,
@@ -829,35 +838,78 @@
add_debug(element, "typeof: #{typeof}")
types.each do |one_type|
add_triple(element, new_subject, RDF["type"], one_type)
end
end
-
- # Generate triples with given object [Step 9]
+
+ # Collections: If new subject is set and is not the same as parent subject,
+ # replace the collection mappings taken from
+ # the evaluation context with a new empty mappings.
+ if (new_subject && new_subject != evaluation_context.parent_subject) || collection_mappings.nil?
+ collection_mappings = {}
+ add_debug(element) do
+ "collections: create new collection mappings(#{collection_mappings.object_id}) " +
+ "ns: #{new_subject}, " +
+ "ps: #{evaluation_context.parent_subject}"
+ end
+ end
+
+ # Generate triples with given object [Step 8]
+ #
+ # Collections: if the current element has a @member attribute, add the property to the
+ # collection associated with that property, creating a new collection if necessary.
if new_subject and current_object_resource
rels.each do |r|
- add_triple(element, new_subject, r, current_object_resource)
+ if member
+ # If the current collection mappings does not contain a collection associated with this IRI,
+ # instantiate a new collection
+ unless collection_mappings[r]
+ collection_mappings[r] = RDF::List.new
+ add_debug(element) {"collections(#{r}): create #{collection_mappings[r]}"}
+ end
+ add_debug(element, "member: add #{current_object_resource} to #{r} collection")
+ collection_mappings[r] << current_object_resource
+ else
+ add_triple(element, new_subject, r, current_object_resource)
+ end
end
revs.each do |r|
add_triple(element, current_object_resource, r, new_subject)
end
elsif rel || rev
- # Incomplete triples and bnode creation [Step 10]
- add_debug(element, "[Step 10] incompletes: rels: #{rels}, revs: #{revs}")
+ # Incomplete triples and bnode creation [Step 9]
+ add_debug(element) {"[Step 9] incompletes: rels: #{rels}, revs: #{revs}"}
current_object_resource = RDF::Node.new
+ # predicate: full IRI
+ # direction: forward/reverse
+ # collection: Save into collection, don't generate triple
+
rels.each do |r|
- incomplete_triples << {:predicate => r, :direction => :forward}
+ if member
+ # If the current collection mappings does not contain a collection associated with this IRI,
+ # instantiate a new collection
+ unless collection_mappings[r]
+ collection_mappings[r] = RDF::List.new
+ add_debug(element) {"collections(#{r}): create #{collection_mappings[r]}"}
+ end
+ incomplete_triples << {:collection => collection_mappings[r]}
+ else
+ incomplete_triples << {:predicate => r, :direction => :forward}
+ end
end
revs.each do |r|
incomplete_triples << {:predicate => r, :direction => :reverse}
end
end
- # Establish current object literal [Step 11]
+ # Establish current object literal [Step 10]
+ #
+ # Collections: if the current element has a @member attribute, add the property to the
+ # collection associated with that property, creating a new collection if necessary.
if property
properties = process_uris(element, property, evaluation_context, base,
:uri_mappings => uri_mappings,
:term_mappings => term_mappings,
:vocab => default_vocabulary,
@@ -882,16 +934,16 @@
:vocab => default_vocabulary,
:restrictions => TERMorCURIEorAbsURI[@version]) unless datatype.to_s.empty?
begin
current_object_literal = if !datatype.to_s.empty? && datatype.to_s != RDF.XMLLiteral.to_s
# typed literal
- add_debug(element, "[Step 11] typed literal (#{datatype})")
+ add_debug(element, "[Step 10] typed literal (#{datatype})")
RDF::Literal.new(content || element.inner_text.to_s, :datatype => datatype, :language => language, :validate => validate?, :canonicalize => canonicalize?)
elsif @version == :"rdfa1.1"
if datatype.to_s == RDF.XMLLiteral.to_s
# XML Literal
- add_debug(element, "[Step 11(1.1)] XML Literal: #{element.inner_html}")
+ add_debug(element) {"[Step 10(1.1)] XML Literal: #{element.inner_html}"}
# In order to maintain maximum portability of this literal, any children of the current node that are
# elements must have the current in scope XML namespace declarations (if any) declared on the
# serialized element using their respective attributes. Since the child element node could also
# declare new XML namespaces, the RDFa Processor must be careful to merge these together when
@@ -907,21 +959,21 @@
rescue ArgumentError => e
add_error(element, e.message)
end
else
# plain literal
- add_debug(element, "[Step 11(1.1)] plain literal")
+ add_debug(element, "[Step 10(1.1)] plain literal")
RDF::Literal.new(content || element.inner_text.to_s, :language => language, :validate => validate?, :canonicalize => canonicalize?)
end
else
if content || (children_node_types == [Nokogiri::XML::Text]) || (element.children.length == 0) || datatype == ""
# plain literal
- add_debug(element, "[Step 11 (1.0)] plain literal")
+ add_debug(element, "[Step 10 (1.0)] plain literal")
RDF::Literal.new(content || element.inner_text.to_s, :language => language, :validate => validate?, :canonicalize => canonicalize?)
elsif children_node_types != [Nokogiri::XML::Text] and (datatype == nil or datatype.to_s == RDF.XMLLiteral.to_s)
# XML Literal
- add_debug(element, "[Step 11 (1.0)] XML Literal: #{element.inner_html}")
+ add_debug(element) {"[Step 10 (1.0)] XML Literal: #{element.inner_html}"}
recurse = false
RDF::Literal.new(element.inner_html,
:datatype => RDF.XMLLiteral,
:language => language,
:namespaces => namespaces,
@@ -933,45 +985,67 @@
add_error(element, e.message)
end
# add each property
properties.each do |p|
- add_triple(element, new_subject, p, current_object_literal) if new_subject
+ # Collections: If element has an @member attribute, add the value to a collection
+ if member
+ # If the current collection mappings does not contain a collection associated with this IRI,
+ # instantiate a new collection
+ unless collection_mappings[p]
+ collection_mappings[p] = RDF::List.new
+ add_debug(element) {"collections(#{p}): create #{collection_mappings[p]}"}
+ end
+ add_debug(element) {"member: add #{current_object_literal} to #{p} collection"}
+ collection_mappings[p] << current_object_literal
+ elsif new_subject
+ add_triple(element, new_subject, p, current_object_literal)
+ end
end
end
if not skip and new_subject && !evaluation_context.incomplete_triples.empty?
- # Complete the incomplete triples from the evaluation context [Step 12]
- add_debug(element, "[Step 12] complete incomplete triples: new_subject=#{new_subject}, completes=#{evaluation_context.incomplete_triples.inspect}")
+ # Complete the incomplete triples from the evaluation context [Step 11]
+ add_debug(element) do
+ "[Step 11] complete incomplete triples: " +
+ "new_subject=#{new_subject.inspect}, " +
+ "completes=#{evaluation_context.incomplete_triples.inspect}"
+ end
+
evaluation_context.incomplete_triples.each do |trip|
- if trip[:direction] == :forward
+ if trip[:collection]
+ add_debug(element) {"member: add #{current_object_resource} to #{trip[:collection]} collection"}
+ trip[:collection] << new_subject
+ elsif trip[:direction] == :forward
add_triple(element, evaluation_context.parent_subject, trip[:predicate], new_subject)
elsif trip[:direction] == :reverse
add_triple(element, new_subject, trip[:predicate], evaluation_context.parent_subject)
end
end
end
- # Create a new evaluation context and proceed recursively [Step 13]
+ # Create a new evaluation context and proceed recursively [Step 12]
if recurse
if skip
if language == evaluation_context.language &&
uri_mappings == evaluation_context.uri_mappings &&
term_mappings == evaluation_context.term_mappings &&
default_vocabulary == evaluation_context.default_vocabulary &&
- base == evaluation_context.base
+ base == evaluation_context.base &&
+ collection_mappings == evaluation_context.collection_mappings
new_ec = evaluation_context
- add_debug(element, "[Step 13] skip: reused ec")
+ add_debug(element, "[Step 12] skip: reused ec")
else
new_ec = evaluation_context.clone
new_ec.base = base
new_ec.language = language
new_ec.uri_mappings = uri_mappings
new_ec.namespaces = namespaces
new_ec.term_mappings = term_mappings
new_ec.default_vocabulary = default_vocabulary
- add_debug(element, "[Step 13] skip: cloned ec")
+ new_ec.collection_mappings = collection_mappings
+ add_debug(element, "[Step 12] skip: cloned ec")
end
else
# create a new evaluation context
new_ec = EvaluationContext.new(base, @host_defaults)
new_ec.parent_subject = new_subject || evaluation_context.parent_subject
@@ -980,54 +1054,78 @@
new_ec.namespaces = namespaces
new_ec.incomplete_triples = incomplete_triples
new_ec.language = language
new_ec.term_mappings = term_mappings
new_ec.default_vocabulary = default_vocabulary
- add_debug(element, "[Step 13] new ec")
+ new_ec.collection_mappings = collection_mappings
+ add_debug(element, "[Step 12] new ec")
end
element.children.each do |child|
# recurse only if it's an element
traverse(child, new_ec) if child.class == Nokogiri::XML::Element
end
+
+ # Collections: after traversing through child elements, for each collection associated with
+ # a property
+ collection_mappings.each do |p, c|
+ # if that collection is different from the evaluation context
+ ec_col = evaluation_context.collection_mappings[p] if evaluation_context.collection_mappings
+ add_debug(element) {"collections: time to create #{c}? #{(ec_col != c).inspect}"}
+ if ec_col != c
+ add_debug(element) {"collection(#{p}) create #{c}"}
+ # Generate an rdf:List with the elements of that collection.
+ c.each_statement do |st|
+ add_triple(element, st.subject, st.predicate, st.object) unless st.object == RDF.List
+ end
+
+ # Generate a triple relating new_subject, property and the collection BNode,
+ # or rdf:nil if the collection is empty.
+ if c.empty?
+ add_triple(element, new_subject, p, RDF.nil)
+ else
+ add_triple(element, new_subject, p, c.subject)
+ end
+ end
+ end
end
end
# space-separated TERMorCURIEorAbsURI or SafeCURIEorCURIEorURI
def process_uris(element, value, evaluation_context, base, options)
return [] if value.to_s.empty?
- add_debug(element, "process_uris: #{value}")
+ add_debug(element) {"process_uris: #{value}"}
value.to_s.split(/\s+/).map {|v| process_uri(element, v, evaluation_context, base, options)}.compact
end
def process_uri(element, value, evaluation_context, base, options = {})
return if value.nil?
restrictions = options[:restrictions]
- add_debug(element, "process_uri: #{value}, restrictions = #{restrictions.inspect}")
+ add_debug(element) {"process_uri: #{value}, restrictions = #{restrictions.inspect}"}
options = {:uri_mappings => {}}.merge(options)
if !options[:term_mappings] && options[:uri_mappings] && value.to_s.match(/^\[(.*)\]$/) && restrictions.include?(:safe_curie)
# SafeCURIEorCURIEorURI
# When the value is surrounded by square brackets, then the content within the brackets is
# evaluated as a CURIE according to the CURIE Syntax definition. If it is not a valid CURIE, the
# value must be ignored.
uri = curie_to_resource_or_bnode(element, $1, options[:uri_mappings], evaluation_context.parent_subject, restrictions)
- add_debug(element, "process_uri: #{value} => safeCURIE => <#{uri}>")
+ add_debug(element) {"process_uri: #{value} => safeCURIE => <#{uri}>"}
uri
elsif options[:term_mappings] && NC_REGEXP.match(value.to_s) && restrictions.include?(:term)
# TERMorCURIEorAbsURI
# If the value is an NCName, then it is evaluated as a term according to General Use of Terms in
# Attributes. Note that this step may mean that the value is to be ignored.
uri = process_term(element, value.to_s, options)
- add_debug(element, "process_uri: #{value} => term => <#{uri}>")
+ add_debug(element) {"process_uri: #{value} => term => <#{uri}>"}
uri
else
# SafeCURIEorCURIEorURI or TERMorCURIEorAbsURI
# Otherwise, the value is evaluated as a CURIE.
# If it is a valid CURIE, the resulting URI is used; otherwise, the value will be processed as a URI.
uri = curie_to_resource_or_bnode(element, value, options[:uri_mappings], evaluation_context.parent_subject, restrictions)
if uri
- add_debug(element, "process_uri: #{value} => CURIE => <#{uri}>")
+ add_debug(element) {"process_uri: #{value} => CURIE => <#{uri}>"}
elsif @version == :"rdfa1.0" && value.to_s.match(/^xml/i)
# Special case to not allow anything starting with XML to be treated as a URI
elsif restrictions.include?(:absuri) || restrictions.include?(:uri)
begin
# AbsURI does not use xml:base
@@ -1048,22 +1146,17 @@
add_warning(element, "Undefined prefix #{$1}", RDF::RDFA.UnresolvedCURIE)
else
add_warning(element, "Relative URI #{value}")
end
end
- add_debug(element, "process_uri: #{value} => URI => <#{uri}>")
+ add_debug(element) {"process_uri: #{value} => URI => <#{uri}>"}
end
uri
end
end
# [7.4.3] General Use of Terms in Attributes
- #
- # @param [String] term:: term
- # @param [Hash] options:: Parser options, one of
- # <em>options[:term_mappings]</em>:: Term mappings
- # <em>options[:vocab]</em>:: Default vocabulary
def process_term(element, value, options)
if options[:term_mappings].is_a?(Hash)
# If the term is in the local term mappings, use the associated URI (case sensitive).
return uri(options[:term_mappings][value.to_s.to_sym]) if options[:term_mappings].has_key?(value.to_s.to_sym)
@@ -1100,16 +1193,18 @@
# No prefix, undefined (in this context, it is evaluated as a term elsewhere)
nil
else
# Prefixes always downcased
prefix = prefix.to_s.downcase unless @version == :"rdfa1.0"
- add_debug(element, "curie_to_resource_or_bnode check for #{prefix.to_s.to_sym.inspect} in #{uri_mappings.inspect}")
+ add_debug(element) do
+ "curie_to_resource_or_bnode check for #{prefix.to_s.to_sym.inspect} in #{uri_mappings.inspect}"
+ end
ns = uri_mappings[prefix.to_s.to_sym]
if ns
uri(ns + reference.to_s)
else
- add_debug(element, "curie_to_resource_or_bnode No namespace mapping for #{prefix}")
+ add_debug(element) {"curie_to_resource_or_bnode No namespace mapping for #{prefix}"}
nil
end
end
end
@@ -1120,6 +1215,6 @@
value.canonicalize! if canonicalize?
value = RDF::URI.intern(value) if intern?
value
end
end
-end
\ No newline at end of file
+end