begin
require 'xml'
$libxml_enabled = true
rescue LoadError
end
module RdfContext
# An RDF Literal, with value, encoding and language elements.
class Literal
class Encoding
attr_reader :value
# New Encoding for a literal, typed, untyped or XMLLiteral
def initialize(value)
@value = URIRef.new(value.to_s) if value
end
# Shortcut for Literal::Encoding.new("http://www.w3.org/2001/XMLSchema#int")
def self.integer
@integer ||= coerce "http://www.w3.org/2001/XMLSchema#int"
end
# Shortcut for Literal::Encoding.new("http://www.w3.org/2001/XMLSchema#float")
def self.float
@float ||= coerce "http://www.w3.org/2001/XMLSchema#float"
end
# Shortcut for Literal::Encoding.new("http://www.w3.org/2001/XMLSchema#string")
def self.string
@string ||= coerce "http://www.w3.org/2001/XMLSchema#string"
end
# Shortcut for Literal::Encoding.new("http://www.w3.org/2001/XMLSchema#date")
def self.date
@date ||= coerce "http://www.w3.org/2001/XMLSchema#date"
end
# Shortcut for Literal::Encoding.new("http://www.w3.org/2001/XMLSchema#dateTime")
def self.datetime
@datetime ||= coerce "http://www.w3.org/2001/XMLSchema#dateTime"
end
# Create from URI, empty or nil string
def self.coerce(string_or_nil)
if string_or_nil.nil? || string_or_nil == ''
the_null_encoding
elsif xmlliteral == string_or_nil.to_s
xmlliteral
else
new string_or_nil
end
end
def self.the_null_encoding
@the_null_encoding ||= Null.new(nil)
end
def self.xmlliteral
@xmlliteral ||= XMLLiteral.new("http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral")
end
# Compare against another encoding, or a URI of a literal type
def ==(other)
case other
when String
other == @value.to_s
when self.class
other.value.to_s == @value.to_s
else
false
end
end
# Generate hash of type to determine uniqueness
def hash
@value.hash
end
def to_s
@value.to_s
end
# Serialize literal, adding datatype and language elements, if present.
# XMLLiteral and String values are RDF-escaped.
def format_as_n3(content, lang)
quoted_content = "\"#{content.to_s.rdf_escape}\"^^<#{value}>"
end
# Serialize literal to TriX
def format_as_trix(content, lang)
lang = " xml:lang=\"#{lang}\"" if lang
"#{content}"
end
# Return content and hash appropriate for encoding in XML
#
# ==== Example
# Encoding.string.xml_args("foo", "en-US") => ["foo", {"rdf:datatype" => "xs:string"}]
def xml_args(content, lang)
hash = {"rdf:datatype" => @value.to_s}
[content.to_s, hash]
end
# Compare literal contents, ignore language
def compare_contents(a, b, same_lang)
a == b
end
# Encode literal contents
def encode_contents(contents, options)
contents
end
end
# The null encoding
class Null < Encoding
def to_s
''
end
# Format content for n3/N-Triples. Quote an RDF-escape and include language
def format_as_n3(content, lang)
"\"#{content.to_s.rdf_escape}\"" + (lang ? "@#{lang}" : "")
end
# Format content for TriX
def format_as_trix(content, lang)
if lang
"#{content}"
else
"#{content}"
end
end
# Return content and hash appropriate for encoding in XML
#
# ==== Example
# Encoding.the_null_encoding.xml_args("foo", "en-US") => ["foo", {"xml:lang" => "en-US"}]
def xml_args(content, lang)
hash = {}
hash["xml:lang"] = lang if lang
[content, hash]
end
# Compare literal contents, requiring languages to match
def compare_contents(a, b, same_lang)
a == b && same_lang
end
def inspect
""
end
end
class XMLLiteral < Encoding
# Compare XMLLiterals
#
# Nokogiri doesn't do a deep compare of elements
#
# Convert node-sets to hash using ActiveSupport::XmlMini and compare hashes.
def compare_contents(a, b, same_lang)
begin
a_hash = ActiveSupport::XmlMini.parse("#{a}")
b_hash = ActiveSupport::XmlMini.parse("#{b}")
a_hash == b_hash
rescue
super
end
end
def format_as_n3(content, lang)
"\"#{content.to_s.rdf_escape}\"^^<#{value}>"
end
def format_as_trix(content, lang)
"#{content}"
end
def xml_args(content, lang)
hash = {"rdf:parseType" => "Literal"}
[content, hash]
end
# Map namespaces from context to each top-level element found within node-set
def encode_contents(contents, options)
#puts "encode_contents: '#{contents}'"
if contents.is_a?(String)
ns_hash = options[:namespaces].values.inject({}) {|h, ns| h.merge(ns.xmlns_hash)}
ns_strs = []
ns_hash.each_pair {|a, u| ns_strs << "#{a}=\"#{u}\""}
# Add inherited namespaces to created root element so that they're inherited to sub-elements
contents = Nokogiri::XML::Document.parse("#{contents}").root.children
end
# Add already mapped namespaces and language
@contents = contents.map do |c|
if $libxml_enabled
c = Nokogiri::XML.parse(c.copy(true).to_s) if c.is_a?(LibXML::XML::Node)
end
if c.is_a?(Nokogiri::XML::Element)
# Gather namespaces from self and decendant nodes
c.traverse do |n|
ns = n.namespace
next unless ns
prefix = ns.prefix ? "xmlns:#{ns.prefix}" : "xmlns"
c[prefix] = ns.href unless c.namespaces[prefix]
end
# Add lanuage
if options[:language] && c["lang"].to_s.empty?
c["xml:lang"] = options[:language]
end
end
c.to_html
end.join("")
end
end
class Language
attr_accessor :value
def initialize(string)
@value = string.to_s.downcase
end
def clean(string)
case string
when "eng"; "en"
else string
end
end
def == (other)
case other
when String
other == @value
when self.class
other.value == @value
end
end
def to_s; @value; end
end
attr_accessor :contents, :encoding, :lang
# Create a new Literal. Optinally pass a namespaces hash
# for use in applying to rdf::XMLLiteral values.
def initialize(contents, encoding, options = {})
unless encoding.is_a?(Encoding)
raise TypeError, "#{encoding.inspect} should be an instance of Encoding"
end
@encoding = encoding
lang = options[:language]
@lang = Language.new(lang) if lang
options = {:namespaces => {}}.merge(options)
@contents = @encoding.encode_contents(contents, options)
end
# Create literal from a string that is already N3 encoded.
def self.n3_encoded(contents, language, encoding = nil)
encoding = encoding.nil? ? Encoding.the_null_encoding : Encoding.coerce(encoding)
options = {}
options[:language] = language if language
#puts "encoded: #{contents.dump}"
contents = contents.rdf_unescape
#puts "unencoded: #{contents.dump}"
new(contents, encoding, options)
end
# Create an un-typed literal with a language
def self.untyped(contents, language = nil)
options = {}
options[:language] = language if language
new(contents, Encoding.the_null_encoding, options)
end
# Create a typed literal
# Options include:
# _namespaces_:: A hash of namespace entries (for XMLLiteral)
def self.typed(contents, encoding, options = {})
encoding = Encoding.coerce(encoding)
new(contents, encoding, options)
end
# Create a literal appropriate for type of object by datatype introspection
def self.build_from(object)
new(object.to_s, infer_encoding_for(object))
end
# Infer the proper XML datatype for the given object
def self.infer_encoding_for(object)
case object
when Integer then Encoding.new("http://www.w3.org/2001/XMLSchema#int")
when Float then Encoding.new("http://www.w3.org/2001/XMLSchema#float")
when Time then Encoding.new("http://www.w3.org/2001/XMLSchema#time")
when DateTime then Encoding.new("http://www.w3.org/2001/XMLSchema#dateTime")
when Date then Encoding.new("http://www.w3.org/2001/XMLSchema#date")
else Encoding.new("http://www.w3.org/2001/XMLSchema#string")
end
end
class << self
protected :new
end
# Compare literal with another literal or a string.
# If a string is passed, only contents must match.
# Otherwise, compare encoding types, contents and languages.
def ==(other)
case other
when String then other == self.contents
when self.class
other.encoding == @encoding &&
@encoding.compare_contents(self.contents, other.contents, other.lang == @lang)
else false
end
end
def hash
[@contents, @encoding, @lang].hash
end
# Output literal in N3 format
def to_n3
encoding.format_as_n3(self.contents, @lang)
end
alias_method :to_ntriples, :to_n3
def inspect
"#{self.class}[#{self.to_n3}]"
end
# Output literal in TriX format
def to_trix
encoding.format_as_trix(@contents, @lang)
end
# Return content and hash appropriate for encoding in XML
#
# ==== Example
# Encoding.the_null_encoding.xml_args("foo", "en-US") => ["foo", {"xml:lang" => "en-US"}]
def xml_args
encoding.xml_args(@contents, @lang)
end
# Is this an XMLLiteral?
def xmlliteral?
encoding.is_a?(XMLLiteral)
end
# Output literal contents as a string
def to_s
self.contents.to_s
end
end
end