Skip to content
Browse files

Merge 0.4.x work

  • Loading branch information...
2 parents 08b329c + d976b7f commit b93dfad65f0d5b3b37ba8e8b1c9ccad5832713bc @gkellogg gkellogg committed Sep 7, 2011
View
8 etc/bendiken.nt
@@ -0,0 +1,8 @@
+<http://ar.to/#self> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://xmlns.com/foaf/0.1/Person> .
+<http://ar.to/#self> <http://xmlns.com/foaf/0.1/name> "Arto Bendiken" .
+<http://ar.to/#self> <http://xmlns.com/foaf/0.1/mbox> <mailto:arto.bendiken@gmail.com> .
+<http://ar.to/#self> <http://xmlns.com/foaf/0.1/mbox_sha1sum> "d0737cceb55eb7d740578d2db1bc0727e3ed49ce" .
+<http://ar.to/#self> <http://xmlns.com/foaf/0.1/mbox_sha1sum> "a033f652c84a4d73b8c26d318c2395699dd2bdfb" .
+<http://ar.to/#self> <http://xmlns.com/foaf/0.1/homepage> <http://ar.to/> .
+<http://ar.to/#self> <http://xmlns.com/foaf/0.1/made> <http://rubygems.org/gems/rdf> .
+<http://ar.to/#self> <http://www.w3.org/2000/01/rdf-schema#isDefinedBy> <http://datagraph.org/bendiken/foaf> .
View
17 etc/bendiken.ttl
@@ -0,0 +1,17 @@
+@base <http://rubygems.org/gems/rdf> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix dc: <http://purl.org/dc/terms/> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix ex: <http://example.org/> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+<http://ar.to/#self> a foaf:Person ;
+ foaf:name "Arto Bendiken" ;
+ foaf:mbox <mailto:arto.bendiken@gmail.com> ;
+ foaf:mbox_sha1sum "d0737cceb55eb7d740578d2db1bc0727e3ed49ce",
+ "a033f652c84a4d73b8c26d318c2395699dd2bdfb" ;
+ foaf:homepage <http://ar.to/> ;
+ foaf:made <> ;
+ rdfs:isDefinedBy <http://datagraph.org/bendiken/foaf> .
View
6 etc/bhuga.nt
@@ -0,0 +1,6 @@
+<http://bhuga.net/#ben> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://xmlns.com/foaf/0.1/Person> .
+<http://bhuga.net/#ben> <http://xmlns.com/foaf/0.1/name> "Ben Lavender" .
+<http://bhuga.net/#ben> <http://xmlns.com/foaf/0.1/mbox> <mailto:blavender@gmail.com> .
+<http://bhuga.net/#ben> <http://xmlns.com/foaf/0.1/mbox_sha1sum> "dbf45f4ffbd27b67aa84f02a6a31c144727d10af" .
+<http://bhuga.net/#ben> <http://xmlns.com/foaf/0.1/homepage> <http://bhuga.net/> .
+<http://bhuga.net/#ben> <http://www.w3.org/2000/01/rdf-schema#isDefinedBy> <http://datagraph.org/bhuga/foaf> .
View
15 etc/bhuga.ttl
@@ -0,0 +1,15 @@
+@base <http://rubygems.org/gems/rdf> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix dc: <http://purl.org/dc/terms/> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix ex: <http://example.org/> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+<http://bhuga.net/#ben> a foaf:Person ;
+ foaf:name "Ben Lavender" ;
+ foaf:mbox <mailto:blavender@gmail.com> ;
+ foaf:mbox_sha1sum "dbf45f4ffbd27b67aa84f02a6a31c144727d10af" ;
+ foaf:homepage <http://bhuga.net/> ;
+ rdfs:isDefinedBy <http://datagraph.org/bhuga/foaf> .
View
1 lib/rdf.rb
@@ -34,6 +34,7 @@ module RDF
autoload :Mutable, 'rdf/mixin/mutable'
autoload :Queryable, 'rdf/mixin/queryable'
autoload :Readable, 'rdf/mixin/readable'
+ autoload :TypeCheck, 'rdf/mixin/type_check'
autoload :Writable, 'rdf/mixin/writable'
# RDF objects
View
21 lib/rdf/mixin/type_check.rb
@@ -0,0 +1,21 @@
+module RDF
+ ##
+ # An RDF type check mixin.
+ #
+ # This module implements #raise_error, which will raise RDF::TypeError.
+ #
+ # @see RDF::Value
+ # @see RDF::Literal
+ # @see RDF::Literal
+ module TypeCheck
+ ##
+ # Default implementation of type_error, which returns false.
+ # Classes including RDF::TypeCheck will raise TypeError
+ # instead.
+ #
+ # @raise [TypeError]
+ def type_error(message)
+ raise TypeError, message
+ end
+ end # TypeCheck
+end # RDF
View
131 lib/rdf/model/literal.rb
@@ -39,16 +39,30 @@ module RDF
# @see http://www.w3.org/TR/rdf-concepts/#section-Literals
# @see http://www.w3.org/TR/rdf-concepts/#section-Datatypes-intro
class Literal
- autoload :Boolean, 'rdf/model/literal/boolean'
- autoload :Numeric, 'rdf/model/literal/numeric'
- autoload :Integer, 'rdf/model/literal/integer'
- autoload :Double, 'rdf/model/literal/double'
- autoload :Decimal, 'rdf/model/literal/decimal'
- autoload :Date, 'rdf/model/literal/date'
- autoload :DateTime, 'rdf/model/literal/datetime'
- autoload :Time, 'rdf/model/literal/time'
- autoload :Token, 'rdf/model/literal/token'
- autoload :XML, 'rdf/model/literal/xml'
+ autoload :Boolean, 'rdf/model/literal/boolean'
+ autoload :Numeric, 'rdf/model/literal/numeric'
+ autoload :Integer, 'rdf/model/literal/integer'
+ autoload :NonPositiveInteger, 'rdf/model/literal/integer'
+ autoload :NegativeInteger, 'rdf/model/literal/integer'
+ autoload :Long, 'rdf/model/literal/integer'
+ autoload :Int, 'rdf/model/literal/integer'
+ autoload :Short, 'rdf/model/literal/integer'
+ autoload :Byte, 'rdf/model/literal/integer'
+ autoload :NonNegativeInteger, 'rdf/model/literal/integer'
+ autoload :UnsignedLong, 'rdf/model/literal/integer'
+ autoload :UnsignedInt, 'rdf/model/literal/integer'
+ autoload :UnsignedShort, 'rdf/model/literal/integer'
+ autoload :UnsignedInteger, 'rdf/model/literal/integer'
+ autoload :UnsignedByte, 'rdf/model/literal/integer'
+ autoload :PositiveInteger, 'rdf/model/literal/integer'
+ autoload :Double, 'rdf/model/literal/double'
+ autoload :Decimal, 'rdf/model/literal/decimal'
+ autoload :Date, 'rdf/model/literal/date'
+ autoload :DateTime, 'rdf/model/literal/datetime'
+ autoload :Float, 'rdf/model/literal/double'
+ autoload :Time, 'rdf/model/literal/time'
+ autoload :Token, 'rdf/model/literal/token'
+ autoload :XML, 'rdf/model/literal/xml'
include RDF::Term
@@ -62,9 +76,19 @@ def self.new(value, options = {})
case RDF::URI(datatype)
when XSD.boolean
RDF::Literal::Boolean
- when XSD.integer, XSD.long, XSD.int, XSD.short, XSD.byte
+ when XSD.integer
RDF::Literal::Integer
- when XSD.double, XSD.float
+ when XSD.long
+ RDF::Literal::Long
+ when XSD.int
+ RDF::Literal::Int
+ when XSD.short
+ RDF::Literal::Short
+ when XSD.byte
+ RDF::Literal::Byte
+ when XSD.float
+ RDF::Literal::Float
+ when XSD.double
RDF::Literal::Double
when XSD.decimal
RDF::Literal::Decimal
@@ -74,12 +98,22 @@ def self.new(value, options = {})
RDF::Literal::DateTime
when XSD.time
RDF::Literal::Time
- when XSD.nonPositiveInteger, XSD.negativeInteger
- RDF::Literal::Integer
- when XSD.nonNegativeInteger, XSD.positiveInteger
- RDF::Literal::Integer
- when XSD.unsignedLong, XSD.unsignedInt, XSD.unsignedShort, XSD.unsignedByte
- RDF::Literal::Integer
+ when XSD.nonPositiveInteger
+ RDF::Literal::NonPositiveInteger
+ when XSD.negativeInteger
+ RDF::Literal::NegativeInteger
+ when XSD.nonNegativeInteger
+ RDF::Literal::NonNegativeInteger
+ when XSD.positiveInteger
+ RDF::Literal::PositiveInteger
+ when XSD.unsignedLong
+ RDF::Literal::UnsignedLong
+ when XSD.unsignedInt
+ RDF::Literal::UnsignedInt
+ when XSD.unsignedShort
+ RDF::Literal::UnsignedShort
+ when XSD.unsignedByte
+ RDF::Literal::UnsignedByte
when XSD.token, XSD.language
RDF::Literal::Token
when RDF.XMLLiteral
@@ -189,7 +223,7 @@ def hash
end
##
- # Returns `true` if this literal is equal to `other`.
+ # Determins if `self` is the same term as `other`.
#
# @example
# RDF::Literal(1).eql?(RDF::Literal(1.0)) #=> false
@@ -205,22 +239,37 @@ def eql?(other)
end
##
- # Returns `true` if this literal is equivalent to `other`.
+ # Returns `true` if this literal is equivalent to `other` (with type check).
#
# @example
# RDF::Literal(1) == RDF::Literal(1.0) #=> true
#
# @param [Object] other
# @return [Boolean] `true` or `false`
+ #
+ # @see http://www.w3.org/TR/rdf-sparql-query/#func-RDFterm-equal
+ # @see http://www.w3.org/TR/rdf-concepts/#section-Literal-Equality
def ==(other)
case other
- when Literal
- self.value.eql?(other.value) &&
- self.language.to_s.downcase == other.language.to_s.downcase &&
- self.datatype.eql?(other.datatype)
- when String
- self.plain? && self.value.eql?(other)
- else false
+ when Literal
+ case
+ when self.eql?(other)
+ true
+ when self.has_language? && self.language.to_s.downcase == other.language.to_s.downcase
+ # Literals with languages can compare if languages are identical
+ self.value == other.value
+ when (self.simple? || self.datatype == XSD.string) && (other.simple? || other.datatype == XSD.string)
+ self.value == other.value
+ when other.comperable_datatype?(self) || self.comperable_datatype?(other)
+ # Comoparing plain with undefined datatypes does not generate an error, but returns false
+ # From data-r2/expr-equal/eq-2-2.
+ false
+ else
+ type_error("unable to determine whether #{self.inspect} and #{other.inspect} are equivalent")
+ end
+ when String
+ self.plain? && self.value.eql?(other)
+ else false
end
end
alias_method :===, :==
@@ -279,6 +328,34 @@ def invalid?
end
##
+ # Returns `true` if the literal has a datatype and the comparison should
+ # return false instead of raise a type error.
+ #
+ # This behavior is intuited from SPARQL data-r2/expr-equal/eq-2-2
+ # @return [Boolean]
+ def comperable_datatype?(other)
+ return false unless self.plain? || self.has_language?
+
+ case other
+ when RDF::Literal::Numeric, RDF::Literal::Boolean,
+ RDF::Literal::Date, RDF::Literal::Time, RDF::Literal::DateTime
+ # Invald types can be compared without raising a TypeError if literal has a language (open-eq-08)
+ !other.valid? && self.has_language?
+ else
+ case other.datatype
+ when XSD.string
+ true
+ when nil
+ # A different language will not generate a type error
+ other.has_language?
+ else
+ # An unknown datatype may not be used for comparison, unless it has a language? (open-eq-8)
+ self.has_language?
+ end
+ end
+ end
+
+ ##
# Validates the value using {#valid?}, raising an error if the value is
# invalid.
#
View
16 lib/rdf/model/literal/boolean.rb
@@ -59,10 +59,20 @@ def <=>(other)
# @return [Boolean] `true` or `false`
# @since 0.3.0
def ==(other)
- (cmp = (self <=> other)) ? cmp.zero? : false
- end
- alias_method :===, :==
+ # If lexically invalid, use regular literal testing
+ return super unless self.valid?
+
+ other = Literal::Boolean.new(other) if other.class == TrueClass || other.class == FalseClass
+ case other
+ when Literal::Boolean
+ return super unless other.valid?
+ (cmp = (self <=> other)) ? cmp.zero? : false
+ else
+ super
+ end
+ end
+
##
# Returns the value as a string.
#
View
127 lib/rdf/model/literal/decimal.rb
@@ -10,12 +10,10 @@ module RDF; class Literal
#
# @see http://www.w3.org/TR/xmlschema-2/#decimal
# @since 0.2.1
- class Decimal < Literal
+ class Decimal < Numeric
DATATYPE = XSD.decimal
GRAMMAR = /^[\+\-]?\d+(\.\d*)?$/.freeze
- include RDF::Literal::Numeric
-
##
# @param [BigDecimal] value
# @option options [String] :lexical (nil)
@@ -50,33 +48,6 @@ def canonicalize!
end
##
- # Compares this literal to `other` for sorting purposes.
- #
- # @param [Object] other
- # @return [Integer] `-1`, `0`, or `1`
- # @since 0.3.0
- def <=>(other)
- case other
- when ::Numeric
- to_d <=> other
- when RDF::Literal::Decimal, RDF::Literal::Double
- to_d <=> other.to_d
- else super
- end
- end
-
- ##
- # Returns `true` if this literal is equivalent to `other`.
- #
- # @param [Object] other
- # @return [Boolean] `true` or `false`
- # @since 0.3.0
- def ==(other)
- (cmp = (self <=> other)) ? cmp.zero? : false
- end
- alias_method :===, :==
-
- ##
# Returns the absolute value of `self`.
#
# @return [RDF::Literal]
@@ -104,108 +75,12 @@ def nonzero?
end
##
- # Returns `self`.
- #
- # @return [RDF::Literal]
- # @since 0.2.3
- def +@
- self # unary plus
- end
-
- ##
- # Returns `self` negated.
- #
- # @return [RDF::Literal]
- # @since 0.2.3
- def -@
- RDF::Literal(-to_d) # unary minus
- end
-
- ##
- # Returns the sum of `self` plus `other`.
- #
- # @param [#to_d] other
- # @return [RDF::Literal]
- # @since 0.2.3
- def +(other)
- RDF::Literal(to_d + (other.respond_to?(:to_d) ? other.to_d : BigDecimal(other.to_s)))
- end
-
- ##
- # Returns the difference of `self` minus `other`.
- #
- # @param [#to_d] other
- # @return [RDF::Literal]
- # @since 0.2.3
- def -(other)
- RDF::Literal(to_d - (other.respond_to?(:to_d) ? other.to_d : BigDecimal(other.to_s)))
- end
-
- ##
- # Returns the product of `self` times `other`.
- #
- # @param [#to_d] other
- # @return [RDF::Literal]
- # @since 0.2.3
- def *(other)
- RDF::Literal(to_d * (other.respond_to?(:to_d) ? other.to_d : BigDecimal(other.to_s)))
- end
-
- ##
- # Returns the quotient of `self` divided by `other`.
- #
- # @param [#to_d] other
- # @return [RDF::Literal]
- # @since 0.2.3
- def /(other)
- RDF::Literal(to_d / (other.respond_to?(:to_d) ? other.to_d : BigDecimal(other.to_s)))
- end
-
- ##
# Returns the value as a string.
#
# @return [String]
# @see BigDecimal#to_s
def to_s
@string || @object.to_s('F')
end
-
- ##
- # Returns the value as an integer.
- #
- # @return [Integer]
- # @see BigDecimal#to_i
- def to_i
- @object.to_i
- end
-
- ##
- # Returns the value as a floating point number.
- #
- # The usual accuracy limits and errors of binary float arithmetic apply.
- #
- # @return [Float]
- # @see BigDecimal#to_f
- def to_f
- @object.to_f
- end
-
- ##
- # Returns the value as a decimal number.
- #
- # @return [BigDecimal]
- # @see BigDecimal#to_d
- def to_d
- @object.respond_to?(:to_d) ? @object.to_d : BigDecimal(@object.to_s)
- end
-
- ##
- # Returns the value as a rational number.
- #
- # @return [Rational]
- # @see BigDecimal#to_r
- def to_r
- @object.to_r # only available on Ruby 1.9+
- end
end # Decimal
end; end # RDF::Literal
View
108 lib/rdf/model/literal/double.rb
@@ -10,7 +10,7 @@ module RDF; class Literal
#
# @see http://www.w3.org/TR/xmlschema-2/#double
# @since 0.2.1
- class Double < Literal
+ class Double < Numeric
DATATYPE = XSD.double
GRAMMAR = /^NaN|(?:[\+\-]?(?:INF|(?:\d+(\.\d*)?([eE][\+\-]?\d+)?)))$/.freeze
@@ -74,17 +74,6 @@ def <=>(other)
end
##
- # Returns `true` if this literal is equivalent to `other`.
- #
- # @param [Object] other
- # @return [Boolean] `true` or `false`
- # @since 0.3.0
- def ==(other)
- (cmp = (self <=> other)) ? cmp.zero? : false
- end
- alias_method :===, :==
-
- ##
# Returns `true` if the value is an invalid IEEE floating point number.
#
# @example
@@ -186,64 +175,6 @@ def nonzero?
end
##
- # Returns `self`.
- #
- # @return [RDF::Literal]
- # @since 0.2.3
- def +@
- self # unary plus
- end
-
- ##
- # Returns `self` negated.
- #
- # @return [RDF::Literal]
- # @since 0.2.3
- def -@
- RDF::Literal(-to_f, :datatype => datatype) # unary minus
- end
-
- ##
- # Returns the sum of `self` plus `other`.
- #
- # @param [#to_f] other
- # @return [RDF::Literal]
- # @since 0.2.3
- def +(other)
- RDF::Literal(to_f + other.to_f)
- end
-
- ##
- # Returns the difference of `self` minus `other`.
- #
- # @param [#to_f] other
- # @return [RDF::Literal]
- # @since 0.2.3
- def -(other)
- RDF::Literal(to_f - other.to_f)
- end
-
- ##
- # Returns the product of `self` times `other`.
- #
- # @param [#to_f] other
- # @return [RDF::Literal]
- # @since 0.2.3
- def *(other)
- RDF::Literal(to_f * other.to_f)
- end
-
- ##
- # Returns the quotient of `self` divided by `other`.
- #
- # @param [#to_f] other
- # @return [RDF::Literal]
- # @since 0.2.3
- def /(other)
- RDF::Literal(to_f / other.to_f)
- end
-
- ##
# Returns the value as a string.
#
# @return [String]
@@ -254,29 +185,22 @@ def to_s
else @object.to_s
end
end
+ end # Double
+
+ # Derived types
+ # @see http://www.w3.org/TR/xpath-functions/#datatypes
+
+ # Note that in XML Schema, Float is not really derived from Double,
+ # but implementations are identical in Ruby
+ # @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#float
+ class Float < Double
+ DATATYPE = XSD.float
##
- # Returns the value as an integer.
- #
- # @return [Integer]
- def to_i
- @object.to_i
- end
-
- ##
- # Returns the value as a floating point number.
- #
- # @return [Float]
- def to_f
- @object.to_f
- end
-
- ##
- # Returns the value as a decimal number.
- #
- # @return [BigDecimal]
- def to_d
- @object.respond_to?(:to_d) ? @object.to_d : BigDecimal(@object.to_s)
+ # @param [Float, #to_f] value
+ # @option options [String] :lexical (nil)
+ def initialize(value, options = {})
+ super(value, options.merge(:datatype => DATATYPE))
end
##
@@ -285,6 +209,6 @@ def to_d
# @return [Rational]
def to_r
@object.to_r # only available on Ruby 1.9+
- end
+ end
end # Double
end; end # RDF::Literal
View
203 lib/rdf/model/literal/integer.rb
@@ -9,13 +9,12 @@ module RDF; class Literal
# RDF::Literal(84) / 2 #=> RDF::Literal(42)
#
# @see http://www.w3.org/TR/xmlschema-2/#integer
+ # @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#integer
# @since 0.2.1
class Integer < Decimal
DATATYPE = XSD.integer
GRAMMAR = /^[\+\-]?\d+$/.freeze
- include RDF::Literal::Numeric
-
##
# @param [Integer, #to_i] value
# @option options [String] :lexical (nil)
@@ -106,115 +105,155 @@ def nonzero?
end
##
- # Returns `self`.
+ # Returns the value as a string.
#
- # @return [RDF::Literal]
- # @since 0.2.3
- def +@
- self # unary plus
+ # @return [String]
+ def to_s
+ @string || @object.to_s
end
##
- # Returns `self` negated.
+ # Returns the value as an `OpenSSL::BN` instance.
#
- # @return [RDF::Literal]
- # @since 0.2.3
- def -@
- RDF::Literal(-to_i) # unary minus
+ # @return [OpenSSL::BN]
+ # @see http://ruby-doc.org/stdlib/libdoc/openssl/rdoc/classes/OpenSSL/BN.html
+ # @since 0.2.4
+ def to_bn
+ require 'openssl' unless defined?(OpenSSL::BN)
+ OpenSSL::BN.new(to_s)
end
+ end # Integer
+
+ # Derived types
+ # @see http://www.w3.org/TR/xpath-functions/#datatypes
+
+ # @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#nonPositiveInteger
+ class NonPositiveInteger < Integer
+ GRAMMAR = /^(?:[\+\-]?0)|(?:-\d+)$/.freeze
##
- # Returns the sum of `self` plus `other`.
- #
- # @param [#to_i] other
- # @return [RDF::Literal]
- # @since 0.2.3
- def +(other)
- RDF::Literal(to_i + other.to_i)
+ # @param [#to_i] value
+ # @option options [String] :lexical (nil)
+ def initialize(value, options = {})
+ super(value, options.merge(:datatype => XSD.nonPositiveInteger))
end
+ end
+
+ # @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#negativeInteger
+ class NegativeInteger < NonPositiveInteger
+ GRAMMAR = /^\-\d+$/.freeze
##
- # Returns the difference of `self` minus `other`.
- #
- # @param [#to_i] other
- # @return [RDF::Literal]
- # @since 0.2.3
- def -(other)
- RDF::Literal(to_i - other.to_i)
+ # @param [#to_i] value
+ # @option options [String] :lexical (nil)
+ def initialize(value, options = {})
+ super(value, options.merge(:datatype => XSD.negativeInteger))
end
-
+ end
+
+ # @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#long
+ class Long < Integer
##
- # Returns the product of `self` times `other`.
- #
- # @param [#to_i] other
- # @return [RDF::Literal]
- # @since 0.2.3
- def *(other)
- RDF::Literal(to_i * other.to_i)
+ # @param [#to_i] value
+ # @option options [String] :lexical (nil)
+ def initialize(value, options = {})
+ super(value, options.merge(:datatype => XSD.long))
end
-
+ end
+
+ # @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#int
+ class Int < Long
##
- # Returns the quotient of `self` divided by `other`.
- #
- # @param [#to_i] other
- # @return [RDF::Literal]
- # @raise [ZeroDivisionError] if divided by zero
- # @since 0.2.3
- def /(other)
- RDF::Literal(to_i / other.to_i)
+ # @param [#to_i] value
+ # @option options [String] :lexical (nil)
+ def initialize(value, options = {})
+ super(value, options.merge(:datatype => XSD.int))
end
-
+ end
+
+ # @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#short
+ class Short < Int
##
- # Returns the value as a string.
- #
- # @return [String]
- def to_s
- @string || @object.to_s
+ # @param [#to_i] value
+ # @option options [String] :lexical (nil)
+ def initialize(value, options = {})
+ super(value, options.merge(:datatype => XSD.short))
end
-
+ end
+
+ # @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#byte
+ class Byte < Short
##
- # Returns the value as an integer.
- #
- # @return [Integer]
- def to_i
- @object.to_i
+ # @param [#to_i] value
+ # @option options [String] :lexical (nil)
+ def initialize(value, options = {})
+ super(value, options.merge(:datatype => XSD.byte))
end
- alias_method :to_int, :to_i
- alias_method :ord, :to_i
+ end
+
+ # @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#nonNegativeInteger
+ class NonNegativeInteger < Integer
+ GRAMMAR = /^(?:[\+\-]?0)|(?:\+?\d+)$/.freeze
##
- # Returns the value as a floating point number.
- #
- # @return [Float]
- def to_f
- @object.to_f
+ # @param [#to_i] value
+ # @option options [String] :lexical (nil)
+ def initialize(value, options = {})
+ super(value, options.merge(:datatype => XSD.nonNegativeInteger))
end
+ end
+
+ # @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#unsignedLong
+ class UnsignedLong < NonNegativeInteger
+ GRAMMAR = /^\d+$/.freeze
##
- # Returns the value as a decimal number.
- #
- # @return [BigDecimal]
- def to_d
- @object.respond_to?(:to_d) ? @object.to_d : BigDecimal(@object.to_s)
+ # @param [#to_i] value
+ # @option options [String] :lexical (nil)
+ def initialize(value, options = {})
+ super(value, options.merge(:datatype => XSD.unsignedLong))
end
-
+ end
+
+ # @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#unsignedInt
+ class UnsignedInt < UnsignedLong
##
- # Returns the value as a rational number.
- #
- # @return [Rational]
- def to_r
- @object.to_r
+ # @param [#to_i] value
+ # @option options [String] :lexical (nil)
+ def initialize(value, options = {})
+ super(value, options.merge(:datatype => XSD.unsignedInt))
+ end
+ end
+
+ # @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#unsignedShort
+ class UnsignedShort < UnsignedInt
+ ##
+ # @param [#to_i] value
+ # @option options [String] :lexical (nil)
+ def initialize(value, options = {})
+ super(value, options.merge(:datatype => XSD.unsignedShort))
end
+ end
+
+ # @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#unsignedByte
+ class UnsignedByte < UnsignedShort
+ ##
+ # @param [#to_i] value
+ # @option options [String] :lexical (nil)
+ def initialize(value, options = {})
+ super(value, options.merge(:datatype => XSD.unsignedByte))
+ end
+ end
+
+ # @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#positiveInteger
+ class PositiveInteger < NonNegativeInteger
+ GRAMMAR = /^\+?\d+$/.freeze
##
- # Returns the value as an `OpenSSL::BN` instance.
- #
- # @return [OpenSSL::BN]
- # @see http://ruby-doc.org/stdlib/libdoc/openssl/rdoc/classes/OpenSSL/BN.html
- # @since 0.2.4
- def to_bn
- require 'openssl' unless defined?(OpenSSL::BN)
- OpenSSL::BN.new(to_s)
+ # @param [#to_i] value
+ # @option options [String] :lexical (nil)
+ def initialize(value, options = {})
+ super(value, options.merge(:datatype => XSD.positiveInteger))
end
- end # Integer
+ end
end; end # RDF::Literal
View
184 lib/rdf/model/literal/numeric.rb
@@ -1,9 +1,187 @@
module RDF; class Literal
##
- # Shared methods for numeric literal classes.
+ # Shared methods and class ancestry for numeric literal classes.
#
# @since 0.3.0
- module Numeric
- # TODO
+ class Numeric < Literal
+ ##
+ # Compares this literal to `other` for sorting purposes.
+ #
+ # @param [Object] other
+ # @return [Integer] `-1`, `0`, or `1`
+ # @since 0.3.0
+ def <=>(other)
+ case other
+ when ::Numeric
+ to_d <=> other
+ when Numeric
+ to_d <=> other.to_d
+ else super
+ end
+ end
+
+ ##
+ # Returns `true` if this literal is equal to `other`.
+ #
+ # @param [Object] other
+ # @return [Boolean] `true` or `false`
+ # @since 0.3.0
+ def ==(other)
+ # If lexically invalid, use regular literal testing
+ return super unless self.valid?
+
+ case other
+ when Literal::Numeric
+ return super unless other.valid?
+ (cmp = (self <=> other)) ? cmp.zero? : false
+ when RDF::URI, RDF::Node
+ # Interpreting SPARQL data-r2/expr-equal/eq-2-2, numeric can't be compared with other types
+ type_error("unable to determine whether #{self.inspect} and #{other.inspect} are equivalent")
+ else
+ super
+ end
+ end
+
+ ##
+ # Returns `self`.
+ #
+ # @return [RDF::Literal::Numeric]
+ # @since 0.2.3
+ def +@
+ self # unary plus
+ end
+
+ ##
+ # Returns `self` negated.
+ #
+ # @return [RDF::Literal::Numeric]
+ # @since 0.2.3
+ def -@
+ if (self.class == NonPositiveInteger || self.class == NegativeInteger) && object != 0
+ # XXX Raise error?
+ end
+ self.class.new(-self.object)
+ end
+
+ ##
+ # Returns the sum of `self` plus `other`.
+ #
+ # For xs:float or xs:double values, if one of the operands is a zero or a finite number
+ # and the other is INF or -INF, INF or -INF is returned. If both operands are INF, INF is returned.
+ # If both operands are -INF, -INF is returned. If one of the operands is INF
+ # and the other is -INF, NaN is returned.
+ # @param [Literal::Numeric, #to_i, #to_f, #to_d] other
+ # @return [RDF::Literal::Numeric]
+ # @since 0.2.3
+ # @see http://www.w3.org/TR/xpath-functions/#func-numeric-add
+ def +(other)
+ if self.class == Double || other.class == Double
+ RDF::Literal::Double.new(to_f + other.to_f)
+ elsif self.class == Float || other.class == Float
+ RDF::Literal::Float.new(to_f + other.to_f)
+ elsif self.class == Decimal || other.class == Decimal
+ RDF::Literal::Decimal.new(to_d + (other.respond_to?(:to_d) ? other.to_d : BigDecimal(other.to_s)))
+ else
+ RDF::Literal::Integer.new(to_i + other.to_i)
+ end
+ end
+
+ ##
+ # Returns the difference of `self` minus `other`.
+ #
+ # @param [Literal::Numeric, #to_i, #to_f, #to_d] other
+ # @return [RDF::Literal::Numeric]
+ # @since 0.2.3
+ # @see http://www.w3.org/TR/xpath-functions/#func-numeric-subtract
+ def -(other)
+ if self.class == Double || other.class == Double
+ RDF::Literal::Double.new(to_f - other.to_f)
+ elsif self.class == Float || other.class == Float
+ RDF::Literal::Float.new(to_f - other.to_f)
+ elsif self.class == Decimal || other.class == Decimal
+ RDF::Literal::Decimal.new(to_d - (other.respond_to?(:to_d) ? other.to_d : BigDecimal(other.to_s)))
+ else
+ RDF::Literal::Integer.new(to_i - other.to_i)
+ end
+ end
+
+ ##
+ # Returns the product of `self` times `other`.
+ #
+ # @param [Literal::Numeric, #to_i, #to_f, #to_d] other
+ # @return [RDF::Literal::Numeric]
+ # @since 0.2.3
+ # @see http://www.w3.org/TR/xpath-functions/#func-numeric-multiply
+ def *(other)
+ if self.class == Double || other.class == Double
+ RDF::Literal::Double.new(to_f * other.to_f)
+ elsif self.class == Float || other.class == Float
+ RDF::Literal::Float.new(to_f * other.to_f)
+ elsif self.class == Decimal || other.class == Decimal
+ RDF::Literal::Decimal.new(to_d * (other.respond_to?(:to_d) ? other.to_d : BigDecimal(other.to_s)))
+ else
+ RDF::Literal::Integer.new(to_i * other.to_i)
+ end
+ end
+
+ ##
+ # Returns the quotient of `self` divided by `other`.
+ #
+ # As a special case, if the types of both $arg1 and $arg2 are xs:integer,
+ # then the return type is xs:decimal.
+ #
+ # @param [Literal::Numeric, #to_i, #to_f, #to_d] other
+ # @return [RDF::Literal::Numeric]
+ # @raise [ZeroDivisionError] if divided by zero
+ # @since 0.2.3
+ # @see http://www.w3.org/TR/xpath-functions/#func-numeric-divide
+ def /(other)
+ if self.class == Double || other.class == Double
+ RDF::Literal::Double.new(to_f / other.to_f)
+ elsif self.class == Float || other.class == Float
+ RDF::Literal::Float.new(to_f / other.to_f)
+ elsif self.class == Decimal || other.class == Decimal
+ RDF::Literal::Decimal.new(to_d / (other.respond_to?(:to_d) ? other.to_d : BigDecimal(other.to_s)))
+ else
+ RDF::Literal::Integer.new(to_i / other.to_i)
+ end
+ end
+
+ ##
+ # Returns the value as an integer.
+ #
+ # @return [Integer]
+ def to_i
+ @object.to_i
+ end
+ alias_method :to_int, :to_i
+ alias_method :ord, :to_i
+
+ ##
+ # Returns the value as a floating point number.
+ #
+ # The usual accuracy limits and errors of binary float arithmetic apply.
+ #
+ # @return [Float]
+ # @see BigDecimal#to_f
+ def to_f
+ @object.to_f
+ end
+
+ ##
+ # Returns the value as a decimal number.
+ #
+ # @return [BigDecimal]
+ def to_d
+ @object.respond_to?(:to_d) ? @object.to_d : BigDecimal(@object.to_s)
+ end
+
+ ##
+ # Returns the value as a rational number.
+ #
+ # @return [Rational]
+ def to_r
+ @object.to_r
+ end
end # Numeric
end; end # RDF::Literal
View
40 lib/rdf/model/node.rb
@@ -44,6 +44,23 @@ def self.intern(id)
self.new(id)
end
+ ##
+ # Override #dup to remember original object.
+ # This allows .eql? to determine that two nodes
+ # are the same thing, and not different nodes
+ # instantiated with the same identifier.
+ # @return [RDF::Node]
+ def dup
+ node = super
+ node.original = self.original || self
+ node
+ end
+
+ ##
+ # Originally instantiated node, if any
+ # @return [RDF::Node]
+ attr_accessor :original
+
# @return [String]
attr_accessor :id
@@ -88,23 +105,36 @@ def hash
end
##
- # Checks whether this blank node is equal to `other`.
+ # Determins if `self` is the same term as `other`.
+ #
+ # In this case, nodes must be the same object
#
# @param [Node] other
# @return [Boolean]
def eql?(other)
- other.is_a?(Node) && self == other
+ other.is_a?(RDF::Node) && (self.original || self).equal?(other.original || other)
end
##
- # Checks whether this blank node is equal to `other`.
+ # Checks whether this blank node is equal to `other` (type checking).
+ #
+ # In this case, different nodes having the same id are considered the same.
+ #
+ # Per SPARQL data-r2/expr-equal/eq-2-2, numeric can't be compared with other types
#
# @param [Object] other
# @return [Boolean]
+ # @see http://www.w3.org/TR/rdf-sparql-query/#func-RDFterm-equal
def ==(other)
- other.respond_to?(:node?) && other.node? &&
- other.respond_to?(:id) && @id == other.id
+ case other
+ when Literal
+ # If other is a Literal, reverse test to consolodate complex type checking logic
+ other == self
+ else
+ other.respond_to?(:node?) && other.node? && other.respond_to?(:id) && @id == other.id
+ end
end
+ alias_method :===, :==
##
# Returns a string representation of this blank node.
View
13 lib/rdf/model/statement.rb
@@ -27,7 +27,8 @@ class Statement
def self.from(statement, options = {})
case statement
when Array, Query::Pattern
- self.new(statement[0], statement[1], statement[2], options.merge(:context => statement[3] || nil))
+ context = statement[3] == false ? nil : statement[3]
+ self.new(statement[0], statement[1], statement[2], options.merge(:context => context))
when Statement then statement
when Hash then self.new(options.merge(statement))
else raise ArgumentError, "expected RDF::Statement, Hash, or Array, but got #{statement.inspect}"
@@ -176,7 +177,7 @@ def has_blank_nodes?
# @param [Statement] other
# @return [Boolean]
def eql?(other)
- other.is_a?(Statement) && self == other && self.context == other.context
+ other.is_a?(Statement) && self == other && (self.context || false) == (other.context || false)
end
##
@@ -190,10 +191,10 @@ def ==(other)
# @param [Statement] other
# @return [Boolean]
def ===(other)
- return false if has_context? && context != other.context
- return false if has_subject? && subject != other.subject
- return false if has_predicate? && predicate != other.predicate
- return false if has_object? && object != other.object
+ return false if has_context? && !context.eql?(other.context)
+ return false if has_subject? && !subject.eql?(other.subject)
+ return false if has_predicate? && !predicate.eql?(other.predicate)
+ return false if has_object? && !object.eql?(other.object)
return true
end
View
32 lib/rdf/model/term.rb
@@ -25,6 +25,38 @@ def <=>(other)
end
##
+ # Compares `self` to `other` to implement RDFterm-equal.
+ #
+ # Subclasses should override this to provide a more meaningful
+ # implementation than the default which simply performs a string
+ # comparison based on `#to_s`.
+ #
+ # @abstract
+ # @param [Object] other
+ # @return [Integer] `-1`, `0`, or `1`
+ #
+ # @see http://www.w3.org/TR/rdf-sparql-query/#func-RDFterm-equal
+ def ==(other)
+ super
+ end
+
+ ##
+ # Determins if `self` is the same term as `other`.
+ #
+ # Subclasses should override this to provide a more meaningful
+ # implementation than the default which simply performs a string
+ # comparison based on `#to_s`.
+ #
+ # @abstract
+ # @param [Object] other
+ # @return [Integer] `-1`, `0`, or `1`
+ #
+ # @see http://www.w3.org/TR/rdf-sparql-query/#func-sameTerm
+ def eql?(other)
+ super
+ end
+
+ ##
# Returns `true` if this term is constant.
#
# @return [Boolean] `true` or `false`
View
16 lib/rdf/model/uri.rb
@@ -435,7 +435,7 @@ def end_with?(string)
alias_method :ends_with?, :end_with?
##
- # Checks whether this URI is equal to `other`.
+ # Checks whether this URI the same term as `other'.
#
# @example
# RDF::URI('http://t.co/').eql?(RDF::URI('http://t.co/')) #=> true
@@ -449,7 +449,9 @@ def eql?(other)
end
##
- # Checks whether this URI is equal to `other`.
+ # Checks whether this URI is equal to `other` (type checking).
+ #
+ # Per SPARQL data-r2/expr-equal/eq-2-2, numeric can't be compared with other types
#
# @example
# RDF::URI('http://t.co/') == RDF::URI('http://t.co/') #=> true
@@ -458,11 +460,15 @@ def eql?(other)
#
# @param [Object] other
# @return [Boolean] `true` or `false`
+ # @see http://www.w3.org/TR/rdf-sparql-query/#func-RDFterm-equal
def ==(other)
case other
- when String then to_s == other
- when URI, Addressable::URI then to_s == other.to_s
- else other.respond_to?(:to_uri) && to_s == other.to_uri.to_s
+ when Literal
+ # If other is a Literal, reverse test to consolodate complex type checking logic
+ other == self
+ when String then to_s == other
+ when URI, Addressable::URI then to_s == other.to_s
+ else other.respond_to?(:to_uri) && to_s == other.to_uri.to_s
end
end
View
10 lib/rdf/model/value.rb
@@ -119,5 +119,15 @@ def inspect
def inspect!
warn(inspect)
end
+
+ ##
+ # Default implementation of raise_error, which returns false.
+ # Classes including RDF::TypeCheck will raise RDF::TypeError
+ # instead.
+ #
+ # @return [false]
+ def type_error(message)
+ false
+ end
end # Value
end # RDF
View
1 lib/rdf/ntriples/writer.rb
@@ -122,6 +122,7 @@ def self.serialize(value)
writer = self.new
case value
when nil then nil
+ when FalseClass then value.to_s
when RDF::Statement
writer.format_statement(value) + "\n"
when RDF::Term
View
130 lib/rdf/query.rb
@@ -2,6 +2,15 @@ module RDF
##
# An RDF basic graph pattern (BGP) query.
#
+ # Named queries either match against a specifically named
+ # contexts if the name is an RDF::Term or bound RDF::Query::Variable.
+ # Names that are against unbound variables match either detault
+ # or named contexts.
+ # The name of `false' will only match against the default context.
+ #
+ # Variable names cause the variable to be added to the solution set
+ # elements.
+ #
# @example Constructing a basic graph pattern query (1)
# query = RDF::Query.new do
# pattern [:person, RDF.type, FOAF.Person]
@@ -36,6 +45,27 @@ module RDF
# }
# })
#
+ # @example In this example, the default graph contains the names of the publishers of two named graphs. The triples in the named graphs are not visible in the default graph in this example.
+ # # default graph
+ # @prefix dc: <http://purl.org/dc/elements/1.1/
+ #
+ # <http://example.org/bob> dc:publisher "Bob" .
+ # <http://example.org/alice> dc:publisher "Alice" .
+ #
+ # # Named graph: http://example.org/bob
+ # @prefix foaf: <http://xmlns.com/foaf/0.1/> .
+ #
+ # _:a foaf:name "Bob" .
+ # _:a foaf:mbox <mailto:bob@oldcorp.example.org> .
+ #
+ # # Named graph: http://example.org/alice
+ # @prefix foaf: <http://xmlns.com/foaf/0.1/> .
+ #
+ # _:a foaf:name "Alice" .
+ # _:a foaf:mbox <mailto:alice@work.example.org> .
+ #
+ #
+ # @see http://www.w3.org/TR/rdf-sparql-query/#rdfDataset
# @since 0.3.0
class Query
autoload :Pattern, 'rdf/query/pattern'
@@ -95,6 +125,13 @@ def self.execute(queryable, patterns = nil, options = {}, &block)
# @param [Hash{Symbol => Object}] options
# any additional keyword options
# @option options [RDF::Query::Solutions] :solutions (Solutions.new)
+ # @option options [RDF::Term, RDF::Query::Variable, Boolean] :context (nil)
+ # Default context for matching against queryable.
+ # Named queries either match against a specifically named
+ # contexts if the name is an RDF::Term or bound RDF::Query::Variable.
+ # Names that are against unbound variables match either detault
+ # or named contexts.
+ # The name of `false' will only match against the default context.
# @yield [query]
# @yieldparam [RDF::Query] query
# @yieldreturn [void] ignored
@@ -105,20 +142,30 @@ def self.execute(queryable, patterns = nil, options = {}, &block)
# @param [Hash{Symbol => Object}] options
# any additional keyword options
# @option options [RDF::Query::Solutions] :solutions (Solutions.new)
+ # @option options [RDF::Term, RDF::Query::Variable, Boolean] :context (nil)
+ # Default context for matching against queryable.
+ # Named queries either match against a specifically named
+ # contexts if the name is an RDF::Term or bound RDF::Query::Variable.
+ # Names that are against unbound variables match either detault
+ # or named contexts.
# @yield [query]
# @yieldparam [RDF::Query] query
# @yieldreturn [void] ignored
- def initialize(patterns = nil, options = {}, &block)
- @options = options.dup
+ def initialize(*patterns, &block)
+ @options = patterns.last.is_a?(Hash) ? patterns.pop.dup : {}
+ patterns << @options if patterns.empty?
@variables = {}
@solutions = @options.delete(:solutions) || Solutions.new
+ context = @options.delete(:context)
- @patterns = case patterns
- when Hash then compile_hash_patterns(patterns.dup)
- when Array then patterns
- else []
+ @patterns = case patterns.first
+ when Hash then compile_hash_patterns(patterns.first.dup)
+ when Array then patterns.first
+ else patterns
end
+ self.context = context
+
if block_given?
case block.arity
when 0 then instance_eval(&block)
@@ -183,10 +230,20 @@ def optimize!(options = {})
##
# Executes this query on the given `queryable` graph or repository.
#
+ # Named queries either match against a specifically named
+ # contexts if the name is an RDF::Term or bound RDF::Query::Variable.
+ # Names that are against unbound variables match either detault
+ # or named contexts.
+ # The name of `false' will only match against the default context.
+ #
# @param [RDF::Queryable] queryable
# the graph or repository to query
# @param [Hash{Symbol => Object}] options
# any additional keyword options
+ # @option options [Hash{Symbol => RDF::Term}] bindings
+ # optional variable bindings to use
+ # @option options [Hash{Symbol => RDF::Term}] solutions
+ # optional initial solutions for chained queries
# @return [RDF::Query::Solutions]
# the resulting solution sequence
# @see http://www.holygoat.co.uk/blog/entry/2005-10-25-1
@@ -196,13 +253,24 @@ def execute(queryable, options = {})
# just so we can call #keys below without worrying
options[:bindings] ||= {}
- @solutions = Solutions.new
- # A quick empty solution simplifies the logic below; no special case for
+ # Use provided solutions to allow for query chaining
+ # Otherwise, a quick empty solution simplifies the logic below; no special case for
# the first pattern
- @solutions << RDF::Query::Solution.new({})
+ @solutions = options[:solutions] || (Solutions.new << RDF::Query::Solution.new({}))
+
+ patterns = @patterns
+
+ # Add context to pattern, if necessary
+ unless self.context.nil?
+ if patterns.empty?
+ patterns = [Pattern.new(nil, nil, nil, :context => self.context)]
+ elsif patterns.first.context.nil?
+ patterns.first.context = self.context
+ end
+ end
+
+ patterns.each do |pattern|
- @patterns.each do |pattern|
-
old_solutions, @solutions = @solutions, Solutions.new
options[:bindings].keys.each do |variable|
@@ -223,6 +291,8 @@ def execute(queryable, options = {})
end
end
+ #puts "solutions after #{pattern} are #{@solutions.to_a.inspect}"
+
# It's important to abort failed queries quickly because later patterns
# that can have constraints are often broad without them.
# We have no solutions at all:
@@ -259,6 +329,44 @@ def matched?
!@failed
end
+ # Add patterns from another query to form a new Query
+ # @param [RDF::Query] other
+ # @return [RDF::Query]
+ def +(other)
+ Query.new(self.patterns + other.patterns)
+ end
+
+ # Is this is a named query?
+ # @return [Boolean]
+ def named?
+ !!options[:context]
+ end
+
+ # Is this is an unamed query?
+ # @return [Boolean]
+ def unnamed?
+ !named?
+ end
+
+ # Add name to query
+ # @param [RDF::Value] value
+ # @return [RDF::Value]
+ def context=(value)
+ options[:context] = value
+ end
+
+ # Name of this query, if any
+ # @return [RDF::Value]
+ def context
+ options[:context]
+ end
+
+ # Query has no patterns
+ # @return [Boolean]
+ def empty?
+ patterns.empty?
+ end
+
##
# Enumerates over each matching query solution.
#
View
43 lib/rdf/query/pattern.rb
@@ -22,6 +22,7 @@ def self.from(pattern, options = {})
# @option options [Variable, URI] :predicate (nil)
# @option options [Variable, Term] :object (nil)
# @option options [Variable, Resource] :context (nil)
+ # A context of nil matches any context, a context of false, matches only the default context.
# @option options [Boolean] :optional (false)
#
# @overload initialize(subject, predicate, object, options = {})
@@ -118,10 +119,12 @@ def optional?
##
# Executes this query pattern on the given `queryable` object.
#
- # By default any variable terms in this pattern will be treated as `nil`
- # wildcards when executing the query. If the optional `bindings` are
- # given, variables will be substituted with their values when executing
- # the query.
+ # Values are matched using using Queryable#query_pattern.
+ #
+ # If the optional `bindings` are given, variables will be substituted with their values
+ # when executing the query.
+ #
+ # To match triples only in the default context, set context to `false'.
#
# @example
# Pattern.new(:s, :p, :o).execute(RDF::Repository.load('data.nt'))
@@ -140,11 +143,11 @@ def optional?
# @since 0.3.0
def execute(queryable, bindings = {}, &block)
query = {
- :subject => subject && subject.variable? ? bindings[subject.to_sym] : subject,
- :predicate => predicate && predicate.variable? ? bindings[predicate.to_sym] : predicate,
- :object => object && object.variable? ? bindings[object.to_sym] : object,
- # TODO: context handling?
- }
+ :subject => subject.is_a?(Variable) && bindings[subject.to_sym] ? bindings[subject.to_sym] : subject,
+ :predicate => predicate.is_a?(Variable) && bindings[predicate.to_sym] ? bindings[predicate.to_sym] : predicate,
+ :object => object.is_a?(Variable) && bindings[object.to_sym] ? bindings[object.to_sym] : object,
+ :context => context.is_a?(Variable) && bindings[context.to_sym] ? bindings[context.to_sym] : context,
+ }.delete_if{|k,v| v.nil?}
# Do all the variable terms refer to distinct variables?
variables = self.variables
@@ -184,9 +187,10 @@ def execute(queryable, bindings = {}, &block)
# @since 0.3.0
def solution(statement)
RDF::Query::Solution.new do |solution|
- solution[subject.to_sym] = statement.subject if subject.variable?
- solution[predicate.to_sym] = statement.predicate if predicate.variable?
- solution[object.to_sym] = statement.object if object.variable?
+ solution[subject.to_sym] = statement.subject if subject.is_a?(Variable)
+ solution[predicate.to_sym] = statement.predicate if predicate.is_a?(Variable)
+ solution[object.to_sym] = statement.object if object.is_a?(Variable)
+ solution[context.to_sym] = statement.context if context.is_a?(Variable)
end
end
@@ -205,6 +209,7 @@ def variable_terms(name = nil)
terms << :subject if subject.is_a?(Variable) && (!name || name.eql?(subject.name))
terms << :predicate if predicate.is_a?(Variable) && (!name || name.eql?(predicate.name))
terms << :object if object.is_a?(Variable) && (!name || name.eql?(object.name))
+ terms << :context if context.is_a?(Variable) && (!name || name.eql?(context.name))
terms
end
@@ -220,6 +225,7 @@ def variable_count
count += 1 if subject.is_a?(Variable)
count += 1 if predicate.is_a?(Variable)
count += 1 if object.is_a?(Variable)
+ count += 1 if context.is_a?(Variable)
count
end
alias_method :cardinality, :variable_count
@@ -236,6 +242,7 @@ def variables
variables.merge!(subject.variables) if subject.is_a?(Variable)
variables.merge!(predicate.variables) if predicate.is_a?(Variable)
variables.merge!(object.variables) if object.is_a?(Variable)
+ variables.merge!(context.variables) if context.is_a?(Variable)
variables
end
@@ -264,6 +271,7 @@ def bindings
bindings.merge!(subject.bindings) if subject.is_a?(Variable)
bindings.merge!(predicate.bindings) if predicate.is_a?(Variable)
bindings.merge!(object.bindings) if object.is_a?(Variable)
+ bindings.merge!(context.bindings) if context.is_a?(Variable)
bindings
end
@@ -306,9 +314,14 @@ def unbound_variables
def to_s
StringIO.open do |buffer| # FIXME in RDF::Statement
buffer << 'OPTIONAL ' if optional?
- buffer << (subject.is_a?(Variable) ? subject.to_s : "<#{subject}>") << ' '
- buffer << (predicate.is_a?(Variable) ? predicate.to_s : "<#{predicate}>") << ' '
- buffer << (object.is_a?(Variable) ? object.to_s : "<#{object}>") << ' .'
+ buffer << [subject, predicate, object].map do |r|
+ r.is_a?(RDF::Query::Variable) ? r.to_s : RDF::NTriples.serialize(r)
+ end.join(" ")
+ buffer << case context
+ when nil, false then " ."
+ when Variable then " #{context.to_s} ."
+ else " #{RDF::NTriples.serialize(context)} ."
+ end
buffer.string
end
end
View
47 lib/rdf/query/solution.rb
@@ -172,6 +172,33 @@ def merge(other)
end
##
+ # Compatible Mappings
+ # Two solution mappings μ1 and μ2 are compatible if, for every variable v in dom(μ1) and in dom(μ2), μ1(v) = μ2(v).
+ #
+ # @param [RDF::Query::Solution, #to_hash] other
+ # another query solution or hash bindings
+ # @return [Boolean]
+ def compatible?(other)
+ @bindings.all? do |k, v|
+ !other.to_hash.has_key?(k) || other[k].eql?(v)
+ end
+ end
+
+ ##
+ # Isomorphic Mappings
+ # Two solution mappings μ1 and μ2 are isomorphic if,
+ # for every variable v in dom(μ1) and in dom(μ2), μ1(v) = μ2(v).
+ #
+ # @param [RDF::Query::Solution, #to_hash] other
+ # another query solution or hash bindings
+ # @return [Boolean]
+ def isomorphic_with?(other)
+ @bindings.all? do |k, v|
+ !other.to_hash.has_key?(k) || other[k].eql?(v)
+ end
+ end
+
+ ##
# @return [Array<Array(Symbol, RDF::Term)>}
def to_a
@bindings.to_a
@@ -182,6 +209,26 @@ def to_a
def to_hash
@bindings.dup
end
+
+ ##
+ # Integer hash of this solution
+ # @return [Integer]
+ def hash
+ @bindings.hash
+ end
+
+ ##
+ # Equivalence of solution
+ def eql?(other)
+ other.is_a?(Solution) && @bindings.eql?(other.bindings)
+ end
+ alias_method :==, :eql?
+
+ ##
+ # Equals of solution
+ def ==(other)
+ other.is_a?(Solution) && @bindings == other.bindings
+ end
##
# @return [String]
View
55 lib/rdf/query/solutions.rb
@@ -16,13 +16,15 @@ module RDF; class Query
# solutions.filter { |solution| solution.age.datatype == RDF::XSD.integer }
# solutions.filter { |solution| solution.name.language == :es }
#
- # @example Reordering solutions based on a variable
+ # @example Reordering solutions based on a variable or proc
# solutions.order_by(:updated)
# solutions.order_by(:updated, :created)
+ # solutions.order_by(:updated, lambda {|a, b| b <=> a})
#
- # @example Selecting particular variables only
+ # @example Selecting/Projecting particular variables only
# solutions.select(:title)
# solutions.select(:title, :description)
+ # solutions.project(:title)
#
# @example Eliminating duplicate solutions
# solutions.distinct
@@ -59,6 +61,22 @@ def count(&block)
end
##
+ # Returns hash of bindings from each solution. Each bound variable will have
+ # an array of bound values representing those from each solution, where a given
+ # solution will have just a single value for each bound variable
+ # @return [Hash{Symbol => Array<RDF::Term>}]
+ def bindings
+ bindings = {}
+ each do |solution|
+ solution.each do |key, value|
+ bindings[key] ||= []
+ bindings[key] << value
+ end
+ end
+ bindings
+ end
+
+ ##
# Filters this solution sequence by the given `criteria`.
#
# @param [Hash{Symbol => Object}] criteria
@@ -87,18 +105,35 @@ def filter(criteria = {}, &block)
##
# Reorders this solution sequence by the given `variables`.
#
- # @param [Array<Symbol, #to_sym>] variables
+ # Variables may be symbols or {Query::Variable} instances.
+ # A variable may also be a Procedure/Lambda, compatible with {Enumerable#sort}.
+ # This takes two arguments (solutions) and returns -1, 0, or 1 equivalently to <=>.
+ #
+ # If called with a block, variables are ignored, and the block is invoked with
+ # pairs of solutions. The block is expected to return -1, 0, or 1 equivalently to <=>.
+ #
+ # @param [Array<Proc, Query::Variable, Symbol, #to_sym>] variables
+ # @yield [solution]
+ # @yieldparam [RDF::Query::Solution] q
+ # @yieldparam [RDF::Query::Solution] b
+ # @yieldreturn [Integer] -1, 0, or 1 depending on value of comparator
# @return [void] `self`
- def order(*variables)
- if variables.empty?
+ def order(*variables, &block)
+ if variables.empty? && !block_given?
raise ArgumentError, "wrong number of arguments (0 for 1)"
else
- # TODO: support for descending sort, e.g. `order(:s => :asc, :p => :desc)`
- variables.map!(&:to_sym)
self.sort! do |a, b|
- a = variables.map { |variable| a[variable].to_s } # FIXME
- b = variables.map { |variable| b[variable].to_s } # FIXME
- a <=> b
+ if block_given?
+ block.call((a.is_a?(Solution) ? a : Solution.new(a)), (b.is_a?(Solution) ? b : Solution.new(b)))
+ else
+ # Try each variable until a difference is found.
+ variables.inject(nil) do |memo, v|
+ memo || begin
+ comp = v.is_a?(Proc) ? v.call(a, b) : (v = v.to_sym; a[v] <=> b[v])
+ comp == 0 ? false : comp
+ end
+ end || 0
+ end
end
end
self
View
43 lib/rdf/query/variable.rb
@@ -103,6 +103,23 @@ def unbound?
end
##
+ # Returns `true` if this variable is distinguished.
+ #
+ # @return [Boolean]
+ def distinguished?
+ @distinguished.nil? || @distinguished
+ end
+
+ ##
+ # Sets if variable is distinguished or non-distinguished.
+ # By default, variables are distinguished
+ #
+ # @return [Boolean]
+ def distinguished=(value)
+ @distinguished = value
+ end
+
+ ##
# Rebinds this variable to the given `value`.
#
# @param [RDF::Term] value
@@ -153,14 +170,21 @@ def hash
##
# Returns `true` if this variable is equivalent to a given `other`
- # variable.
+ # variable. Or, to another Term if bound, or to any other Term
#
# @param [Object] other
# @return [Boolean] `true` or `false`
# @since 0.3.0
def eql?(other)
- other.is_a?(RDF::Query::Variable) && @name.eql?(other.name)
+ if unbound?
+ other.is_a?(RDF::Term) # match any Term when unbound
+ elsif other.is_a?(RDF::Query::Variable)
+ @name.eql?(other.name)
+ else
+ value.eql?(other)
+ end
end
+ alias_method :==, :eql?
##
# Compares this variable with the given value.
@@ -169,7 +193,7 @@ def eql?(other)
# @return [Boolean]
def ===(other)
if unbound?
- true # match anything when unbound
+ other.is_a?(RDF::Term) # match any Term when unbound
else
value === other
end
@@ -178,9 +202,20 @@ def ===(other)
##
# Returns a string representation of this variable.
#
+ # Distinguished variables are indicated with a single `?`.
+ #
+ # Non-distinguished variables are indicated with a double `??`
+ #
+ # @example
+ # v = Variable.new("a")
+ # v.to_s => '?a'
+ # v.distinguished = false
+ # v.to_s => '??a'
+ #
# @return [String]
def to_s
- unbound? ? "?#{name}" : "?#{name}=#{value}"
+ prefix = distinguished? ? '?' : "??"
+ unbound? ? "#{prefix}#{name}" : "#{prefix}#{name}=#{value}"
end
end # Variable
end # RDF::Query
View
12 lib/rdf/reader.rb
@@ -209,6 +209,18 @@ def initialize(input = $stdin, options = {}, &block)
attr_reader :options
##
+ # Returns the base URI determined by this reader.
+ #
+ # @example
+ # reader.prefixes[:dc] #=> RDF::URI('http://purl.org/dc/terms/')
+ #
+ # @return [Hash{Symbol => RDF::URI}]
+ # @since 0.3.0
+ def base_uri
+ @options[:base_uri]
+ end
+
+ ##
# Returns the URI prefixes currently defined for this reader.
#
# @example
View
10 lib/rdf/repository.rb
@@ -326,6 +326,8 @@ def each_context(&block)
protected
##
+ # Match elements with eql?, not ==
+ # Context of `false` matches default context. Unbound variable matches non-false context
# @private
# @see RDF::Queryable#query
def query_pattern(pattern, &block)
@@ -336,16 +338,16 @@ def query_pattern(pattern, &block)
cs = @data.has_key?(context) ? {context => @data[context]} : @data.dup
cs.each do |c, ss|
- next unless context.nil? || context == c
+ next unless context.nil? || context == false && !c || context.eql?(c)
ss = ss.has_key?(subject) ? {subject => ss[subject]} : ss.dup
ss.each do |s, ps|
- next unless subject.nil? || subject == s
+ next unless subject.nil? || subject.eql?(s)
ps = ps.has_key?(predicate) ? {predicate => ps[predicate]} : ps.dup
ps.each do |p, os|
- next unless predicate.nil? || predicate == p
+ next unless predicate.nil? || predicate.eql?(p)
os = os.dup # TODO: is this really needed?
os.each do |o|
- next unless object.nil? || object == o
+ next unless object.nil? || object.eql?(o)
block.call(RDF::Statement.new(s, p, o, :context => c.equal?(DEFAULT_CONTEXT) ? nil : c))
end
end
View
3 spec/mixin_queryable_spec.rb
@@ -3,9 +3,6 @@
describe RDF::Queryable do
before :each do
- # The available reference implementations are `RDF::Repository` and
- # `RDF::Graph`, but a plain Ruby array will do fine as well:
- #@queryable = [].extend(RDF::Queryable) # FIXME
@queryable = RDF::Repository.new
end
View
440 spec/model_literal_spec.rb
@@ -427,7 +427,7 @@ def self.literals(*selector)
literal(:xml_def_ns) => %("foo <sup xmlns=\\"http://purl.org/dc/terms/\\">bar</sup> baz!"^^<http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral>),
literal(:xml_ns2) => %(fixme),
}.each_pair do |args, rep|
- it "returns ntriples rep for #{args.inspect}" do
+ it "returns n3 rep for #{args.inspect}" do
literal = RDF::Literal.new(*args)
pending {literal.to_s.should == rep}
end
@@ -626,4 +626,442 @@ def self.literals(*selector)
end
end
end
+
+ describe RDF::Literal::Numeric do
+ context "type-promotion" do
+ context "for numbers" do
+ {
+ :integer => {
+ :integer => :integer,
+ :nonPositiveInteger => :integer,
+ :negativeInteger => :integer,
+ :long => :integer,
+ :int => :integer,
+ :short => :integer,
+ :byte => :integer,
+ :nonNegativeInteger => :integer,
+ :unsignedLong => :integer,
+ :unsignedInt => :integer,
+ :unsignedShort => :integer,
+ :unsignedByte => :integer,
+ :positiveInteger => :integer,
+ :decimal => :decimal,
+ :float => :float,
+ :double => :double,
+ },
+ :decimal => {
+ :integer => :decimal,
+ :nonPositiveInteger => :decimal,
+ :negativeInteger => :decimal,
+ :long => :decimal,
+ :int => :decimal,
+ :short => :decimal,
+ :byte => :decimal,
+ :nonNegativeInteger => :decimal,
+ :unsignedLong => :decimal,
+ :unsignedInt => :decimal,
+ :unsignedShort => :decimal,
+ :unsignedByte => :decimal,
+ :positiveInteger => :decimal,
+ :decimal => :decimal,
+ :float => :float,
+ :double => :double,
+ },
+ :float => {
+ :integer => :float,
+ :nonPositiveInteger => :float,
+ :negativeInteger => :float,
+ :long => :float,
+ :int => :float,
+ :short => :float,
+ :byte => :float,
+ :nonNegativeInteger => :float,
+ :unsignedLong => :float,
+ :unsignedInt => :float,
+ :unsignedShort => :float,
+ :unsignedByte => :float,
+ :positiveInteger => :float,
+ :decimal => :float,
+ :float => :float,
+ :double => :double,
+ },
+ :double => {
+ :integer => :double,
+ :nonPositiveInteger => :double,
+ :negativeInteger => :double,
+ :long => :double,
+ :int => :double,
+ :short => :double,
+ :byte => :double,
+ :nonNegativeInteger => :double,
+ :unsignedLong => :double,
+ :unsignedInt => :double,
+ :unsignedShort => :double,
+ :unsignedByte => :double,
+ :positiveInteger => :double,
+ :decimal => :double,
+ :float => :double,
+ :double => :double,
+ },
+ }.each do |left, right_result|
+ if left == :integer
+ # Type promotion is equivalent for sub-types of xsd:integer
+ (right_result.keys - [:integer, :decimal, :float, :double]).each do |l|
+ o_l = RDF::Literal.new(([:nonPositiveInteger, :negativeInteger].include?(l) ? "-1" : "1"), :datatype => RDF::XSD.send(l))
+ right_result.each do |right, result|
+ o_r = RDF::Literal.new(([:nonPositiveInteger, :negativeInteger].include?(right) ? "-1" : "1"), :datatype => RDF::XSD.send(right))
+
+ it "returns #{result} for #{l} + #{right}" do
+ (o_l + o_r).datatype.should == RDF::XSD.send(result)
+ end
+ it "returns #{result} for #{l} - #{right}" do
+ (o_l - o_r).datatype.should == RDF::XSD.send(result)
+ end
+ it "returns #{result} for #{l} * #{right}" do
+ (o_l * o_r).datatype.should == RDF::XSD.send(result)
+ end
+ it "returns #{result} for #{l} / #{right}" do
+ (o_l / o_r).datatype.should == RDF::XSD.send(result)
+ end
+
+ it "returns #{result} for #{right} + #{l}" do
+ (o_r + o_l).datatype.should == RDF::XSD.send(result)
+ end
+ it "returns #{result} for #{right} - #{l}" do
+ (o_r - o_l).datatype.should == RDF::XSD.send(result)
+ end
+ it "returns #{result} for #{right} * #{l}" do
+ (o_r * o_l).datatype.should == RDF::XSD.send(result)
+ end
+ it "returns #{result} for #{right} / #{l}" do
+ (o_r / o_l).datatype.should == RDF::XSD.send(result)
+ end
+ end
+ end
+ end
+
+ o_l = RDF::Literal.new("1", :datatype => RDF::XSD.send(left))
+ right_result.each do |right, result|
+ o_r = RDF::Literal.new(([:nonPositiveInteger, :negativeInteger].include?(right) ? "-1" : "1"), :datatype => RDF::XSD.send(right))
+
+ it "returns #{result} for #{left} + #{right}" do
+ (o_l + o_r).datatype.should == RDF::XSD.send(result)
+ end
+ it "returns #{result} for #{left} - #{right}" do
+ (o_l - o_r).datatype.should == RDF::XSD.send(result)
+ end
+ it "returns #{result} for #{left} * #{right}" do
+ (o_l * o_r).datatype.should == RDF::XSD.send(result)
+ end
+ it "returns #{result} for #{left} / #{right}" do
+ (o_l / o_r).datatype.should == RDF::XSD.send(result)
+ end
+
+ it "returns #{result} for #{right} + #{left}" do
+ (o_r + o_l).datatype.should == RDF::XSD.send(result)
+ end
+ it "returns #{result} for #{right} - #{left}" do
+ (o_r - o_l).datatype.should == RDF::XSD.send(result)
+ end
+ it "returns #{result} for #{right} * #{left}" do
+ (o_r * o_l).datatype.should == RDF::XSD.send(result)
+ end
+ it "returns #{result} for #{right} / #{left}" do
+ (o_r / o_l).datatype.should == RDF::XSD.send(result)
+ end
+ end
+ end
+ end
+ end
+
+ [RDF::Literal::Float, RDF::Literal::Double].each do |c|
+ describe c do
+ before(:each) do
+ @nan = c.new("NaN")
+ @inf = c.new("INF")
+ end
+
+ it "recognizes INF" do
+ @inf.should be_infinite
+ RDF::Literal.new('INF', :datatype => c::DATATYPE).should == @inf
+ end
+
+ it "recognizes -INF" do
+ @inf.should be_infinite
+ RDF::Literal.new('-INF', :datatype => c::DATATYPE).should == -@inf
+ end
+
+ it "recognizes NaN" do
+ @nan.should be_nan
+ RDF::Literal.new('NaN', :datatype => c::DATATYPE).should be_nan
+ end
+
+ [-1, 0, 1].map {|n| c.new(n)}.each do |n|
+ {
+ :"+" => [c.new("INF"), c.new("INF"), c.new("-INF"), c.new("-INF")],
+ :"-" => [c.new("INF"), c.new("-INF"), c.new("-INF"), c.new("INF")],
+ }.each do |op, (lp, rp, lm, rm)|
+ it "returns #{lp} for INF #{op} #{n}" do
+ @inf.send(op, n).should == lp
+ end
+
+ it "returns #{rp} for #{n} #{op} INF" do
+ n.send(op, @inf).should == rp
+ end
+
+ it "returns #{lm} for -INF #{op} #{n}" do
+ (-@inf).send(op, n).should == lm
+ end
+
+ it "returns #{rm} for #{n} #{op} -INF" do
+ n.send(op, -@inf).should == rm
+ end
+ end
+
+ it "#{n} + NaN" do
+ (n + -@nan).should be_nan
+ (-@nan + n).should be_nan
+ end
+ end
+
+ # Multiplication
+ {
+ -1 => [c.new("-INF"), c.new("-INF")],
+ 0 => [:nan, :nan],
+ 1 => [c.new("INF"), c.new("INF")],
+ }.each do |n, (p, m)|
+ it "returns #{p} for #{n} * INF" do
+ if p == :nan
+ (c.new(n) * @inf).should be_nan
+ else
+ (c.new(n) * @inf).should == p
+ end
+ end
+
+ it "returns #{p} for INF * #{n}" do
+ if p == :nan
+ (@inf * c.new(n)).should be_nan
+ else