autoload :Date, 'date' autoload :DateTime, 'date' autoload :Time, 'time' module RDF class Literal ## # Re-define initialize/new to call _normalize_ on value. # @param [Object] # @option options [Symbol] :language (nil) # @option options [URI] :datatype (nil) # @option options[Hash] :namespaces ({}) def initialize_with_normalization(value, options = {}) initialize_without_normalization(value, options) normalize(options) end alias_method :initialize_without_normalization, :initialize alias_method :initialize, :initialize_with_normalization def valid? case datatype when XSD.boolean then %w(1 true 0 false).include?(value.to_s.downcase) when XSD.decimal then !!value.to_s.match(/^[\+\-]?\d+(\.\d*)?$/) when XSD.double then !!value.to_s.match(/^[\+\-]?\d+(\.\d*([eE][\+\-]?\d+)?)?$/) when XSD.integer then !!value.to_s.match(/^[\+\-]?\d+$/) else true end end protected # Normalize literal value # # Options is a hash passed to initialize def normalize(options = {}) return unless valid? # Only normalize valid value @value = case datatype when XSD.boolean then %(1 true).include?(@value.to_s.downcase) ? "true" : "false" when XSD.integer then @value.to_i.to_s when XSD.decimal then normalize_decimal(@value, options) when XSD.double then normalize_double(@value, options) when XSD.time then @value.is_a?(Time) ? @value.strftime("%H:%M:%S%Z").sub(/\+00:00|UTC/, "Z") : @value.to_s when XSD.dateTime then @value.is_a?(DateTime) ? @value.strftime("%Y-%m-%dT%H:%M:%S%Z").sub(/\+00:00|UTC/, "Z") : @value.to_s when XSD.date then @value.is_a?(Date) ? @value.strftime("%Y-%m-%d%Z").sub(/\+00:00|UTC/, "Z") : @value.to_s when RDF.XMLLiteral then normalize_xmlliteral(@value, options) else @value.to_s end end def normalize_decimal(contents, options) # Can't use simple %f transformation do to special requirements from N3 tests in representation i, f = contents.to_s.split(".") f = f.to_s[0,16] # Truncate after 15 decimal places i.sub!(/^\+?0+(\d)$/, '\1') f.sub!(/0*$/, '') f = "0" if f.empty? "#{i}.#{f}" end def normalize_double(contents, options) i, f, e = ("%.16E" % contents.to_f).split(/[\.E]/) f.sub!(/0*$/, '') f = "0" if f.empty? e.sub!(/^\+?0+(\d)$/, '\1') "#{i}.#{f}E#{e}" end # Normalize an XML Literal, by adding necessary namespaces. # This should be done as part of initialize # # namespaces is a hash of prefix => URIs def normalize_xmlliteral(contents, options = {}) options[:namespaces] ||= {} begin # Only normalize if Nokogiri is included require 'nokogiri' unless defined?(Nokogiri) rescue LoadError => e contents.to_s # No normalization end if contents.is_a?(String) ns_hash = {} options[:namespaces].each_pair do |prefix, uri| attr = prefix.to_s.empty? ? "xmlns" : "xmlns:#{prefix}" ns_hash[attr] = uri.to_s end 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("<foo #{ns_strs.join(" ")}>#{contents}</foo>").root.children end # Add already mapped namespaces and language contents.map do |c| if c.is_a?(Nokogiri::XML::Element) c = Nokogiri::XML.parse(c.dup.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS)).root # 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.to_s unless c.namespaces[prefix] end # Add lanuage if options[:language] && c["lang"].to_s.empty? c["xml:lang"] = options[:language] end end c.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS) end.join("") end end class NormalizationError < IOError; end end