class RDF::Literal

An RDF literal.

Subclasses of {RDF::Literal} should define DATATYPE and GRAMMAR constants, which are used for identifying the appropriate class to use for a datatype URI and to perform lexical matching on the value.

Literal comparison with other {RDF::Value} instances call {RDF::Value#type_error}, which, returns false. Implementations wishing to have {RDF::TypeError} raised should mix-in {RDF::TypeCheck}. This is required for strict SPARQL conformance.

Specific typed literals may have behavior different from the default implementation. See the following defined sub-classes for specific documentation. Additional sub-classes may be defined, and will interoperate by defining ‘DATATYPE` and `GRAMMAR` constants, in addition other required overrides of RDF::Literal behavior.

In RDF 1.1, all literals are typed, including plain literals and language-tagged strings. Internally, plain literals are given the ‘xsd:string` datatype and language-tagged strings are given the `rdf:langString` datatype. Creating a plain literal, without a datatype or language, will automatically provide the `xsd:string` datatype; similar for language-tagged strings. Note that most serialization formats will remove this datatype. Code which depends on a literal having the `xsd:string` datatype being different from a plain literal (formally, without a datatype) may break. However note that the `#has_datatype?` will continue to return `false` for plain or language-tagged strings.

RDF 1.2 adds **directional language-tagged strings** which are effectively a subclass of **language-tagged strings** contining an additional direction component with value either ltr or rtl for Left-to-Right or Right-to-Left. This determines the general direction of a string when presented in n a user agent, where it might be in conflict with the inherent direction of the leading Unicode code points. Directional language-tagged strings are given the ‘rdf:langString` datatype.

@example Creating a plain literal

value = RDF::Literal.new("Hello, world!")
value.plain?                                   #=> true`

@example Creating a language-tagged string (1)

value = RDF::Literal.new("Hello!", language: :en)
value.language?                                #=> true
value.language                                 #=> :en

@example Creating a language-tagged string (2)

RDF::Literal.new("Wazup?", language: :"en-US")
RDF::Literal.new("Hej!",   language: :sv)
RDF::Literal.new("¡Hola!", language: :es)

@example Creating a directional language-tagged string

value = RDF::Literal.new("Hello!", language: :en, direction: :ltr)
value.language?                                #=> true
value.language                                 #=> :en
value.direction?                               #=> true
value.direction                                #=> :ltr

@example Creating an explicitly datatyped literal

value = RDF::Literal.new("2009-12-31", datatype: RDF::XSD.date)
value.datatype?                                #=> true
value.datatype                                 #=> RDF::XSD.date

@example Creating an implicitly datatyped literal

value = RDF::Literal.new(Date.today)
value.datatype?                                #=> true
value.datatype                                 #=> RDF::XSD.date

@example Creating implicitly datatyped literals

RDF::Literal.new(false).datatype               #=> XSD.boolean
RDF::Literal.new(true).datatype                #=> XSD.boolean
RDF::Literal.new(123).datatype                 #=> XSD.integer
RDF::Literal.new(9223372036854775807).datatype #=> XSD.integer
RDF::Literal.new(3.1415).datatype              #=> XSD.double
RDF::Literal.new(Time.now).datatype            #=> XSD.dateTime
RDF::Literal.new(Date.new(2010)).datatype      #=> XSD.date
RDF::Literal.new(DateTime.new(2010)).datatype  #=> XSD.dateTime

@see www.w3.org/TR/rdf11-concepts/#section-Graph-Literal @see www.w3.org/TR/rdf11-concepts/#section-Datatypes

Constants

FALSE
TRUE
XSD_STRING
ZERO

Attributes

datatype[RW]

@return [URI] The XML Schema datatype URI (optional).

direction[RW]

@return [Symbol] The base direction (optional). Implies ‘datatype` is `rdf:dirLangString`.

language[RW]

@return [Symbol] The language-tag (optional). Implies ‘datatype` is `rdf:langString`.

Public Class Methods

datatype_map() click to toggle source

@private Return Hash mapping from datatype URI to class

# File lib/rdf/model/literal.rb, line 102
def self.datatype_map
  @@datatype_map ||= Hash[
    @@subclasses
      .select {|klass| klass.const_defined?(:DATATYPE)}
      .map {|klass| [klass.const_get(:DATATYPE).to_s, klass]}
  ]
end
datatyped_class(uri) click to toggle source

@private Return datatype class for uri, or nil if none is found

# File lib/rdf/model/literal.rb, line 113
def self.datatyped_class(uri)
  datatype_map[uri]
end
inherited(child) click to toggle source

@private @return [void]

Calls superclass method
# File lib/rdf/model/literal.rb, line 78
def self.inherited(child)
  @@subclasses << child
  @@datatype_map = nil
  super
end
new(value, language: nil, datatype: nil, direction: nil, lexical: nil, validate: false, canonicalize: false, **options) click to toggle source

@private

# File lib/rdf/model/literal.rb, line 119
def self.new(value, language: nil, datatype: nil, direction: nil, lexical: nil, validate: false, canonicalize: false, **options)
  if language && direction
    raise ArgumentError, "datatype with language and direction must be rdf:dirLangString" if (datatype || RDF.dirLangString).to_s != RDF.dirLangString.to_s
  elsif language
    raise ArgumentError, "datatype with language must be rdf:langString" if (datatype || RDF.langString).to_s != RDF.langString.to_s
  else
    raise ArgumentError, "datatype not compatible with language or direction" if language || direction
  end

  klass = case
    when !self.equal?(RDF::Literal)
      self # subclasses can be directly constructed without type dispatch
    when typed_literal = datatyped_class(datatype.to_s)
      typed_literal
    else case value
      when ::TrueClass  then RDF::Literal::Boolean
      when ::FalseClass then RDF::Literal::Boolean
      when ::Integer    then RDF::Literal::Integer
      when ::Float      then RDF::Literal::Double
      when ::BigDecimal then RDF::Literal::Decimal
      when ::Rational   then RDF::Literal::Double
      when ::DateTime   then RDF::Literal::DateTime
      when ::Time       then RDF::Literal::DateTime
      when ::Date       then RDF::Literal::Date
      when ::Symbol     then RDF::Literal::Token
      else self
    end
  end
  literal = klass.allocate
  literal.send(:initialize, value, language: language, datatype: datatype, direction: direction, **options)
  literal.validate!     if validate
  literal.canonicalize! if canonicalize
  literal
end
new(value, language: nil, datatype: nil, direction: nil, lexical: nil, validate: false, canonicalize: false, **options) click to toggle source

Literals without a datatype are given either ‘xsd:string`, `rdf:langString`, or `rdf:dirLangString`, depending on if there is `language` and/or `direction`.

@param [Object] value @param [Symbol] direction (nil)

Initial text direction.

@param [Symbol] language (nil)

Language is downcased to ensure proper matching

@param [String] lexical (nil)

Supplied lexical representation of this literal,
otherwise it comes from transforming `value` to a string form..

@param [URI] datatype (nil) @param [Boolean] validate (false) @param [Boolean] canonicalize (false) @raise [ArgumentError]

if there is a language and datatype is no rdf:langString
or datatype is rdf:langString and there is no language

@see www.w3.org/TR/rdf11-concepts/#section-Graph-Literal @see www.w3.org/TR/rdf11-concepts/#section-Datatypes @see to_s

# File lib/rdf/model/literal.rb, line 189
def initialize(value, language: nil, datatype: nil, direction: nil, lexical: nil, validate: false, canonicalize: false, **options)
  @object   = value.freeze
  @string   = lexical if lexical
  @string   = value if !defined?(@string) && value.is_a?(String)
  @string   = @string.encode(Encoding::UTF_8).freeze if instance_variable_defined?(:@string)
  @object   = @string if instance_variable_defined?(:@string) && @object.is_a?(String)
  @language = language.to_s.downcase.to_sym if language
  @direction = direction.to_s.to_sym if direction
  @datatype = RDF::URI(datatype).freeze if datatype
  @datatype ||= self.class.const_get(:DATATYPE) if self.class.const_defined?(:DATATYPE)
  @datatype ||= if instance_variable_defined?(:@language) && @language &&
                   instance_variable_defined?(:@direction) && @direction
    RDF.dirLangString
  elsif instance_variable_defined?(:@language) && @language
    RDF.langString
  else
    XSD_STRING
  end
end

Public Instance Methods

<=>(other) click to toggle source

Compares ‘self` to `other` for sorting purposes (with type check).

@param [Object] other @return [Integer] ‘-1`, `0`, or `1`

# File lib/rdf/model/literal.rb, line 352
def <=>(other)
  case other
  when Literal
    case
    when self.eql?(other)
      0
    when self.language? && other.language?
      # Literals with languages can compare if languages are identical
      self.to_s <=> other.to_s
    when self.simple? && other.simple?
      self.to_s <=> other.to_s
    when !self.valid?
      type_error("#{self.inspect} is invalid") || 0
    when !other.valid?
      type_error("#{other.inspect} is invalid") || 0
    when self.comperable_datatype2?(other)
      self.object <=> other.object
    else
      type_error("#{self.inspect} and #{other.inspect} are not comperable") || 0
    end
  when String
    self.simple? && self.value <=> other
  else 1
  end
end
==(other) click to toggle source

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 www.w3.org/TR/rdf-sparql-query/#func-RDFterm-equal @see www.w3.org/TR/rdf-concepts/#section-Literal-Equality

# File lib/rdf/model/literal.rb, line 319
def ==(other)
  case other
  when Literal
    case
    when self.eql?(other)
      true
    when self.direction? && self.direction == other.direction
      # Literals with directions can compare if languages and directions are identical
      self.value_hash == other.value_hash && self.value == other.value
    when self.language? && self.language == other.language
      # Literals with languages can compare if languages are identical
      self.value_hash == other.value_hash && self.value == other.value
    when self.simple? && other.simple?
      self.value_hash == other.value_hash && self.value == other.value
    when other.comperable_datatype?(self) || self.comperable_datatype?(other)
      # Comparing 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.simple? && self.value.eql?(other)
  else false
  end
end
Also aliased as: ===
===(other)
Alias for: ==
canonicalize!() click to toggle source

Converts this literal into its canonical lexical representation.

Subclasses should override this as needed and appropriate.

@return [RDF::Literal] ‘self` @since 0.3.0

# File lib/rdf/model/literal.rb, line 522
def canonicalize!
  self
end
compatible?(other) click to toggle source

Term compatibility according to SPARQL

Compatibility of two arguments is defined as:

  • The arguments are simple literals or literals typed as xsd:string

  • The arguments are plain literals with identical language-tags and directions

  • The first argument is a plain literal with language-tag and the second argument is a simple literal or literal typed as xsd:string

@example

compatible?("abc"     "b")                         #=> true
compatible?("abc"     "b"^^xsd:string)             #=> true
compatible?("abc"^^xsd:string "b")             #=> true
compatible?("abc"^^xsd:string "b"^^xsd:string) #=> true
compatible?("abc"@en  "b")                     #=> true
compatible?("abc"@en  "b"^^xsd:string)         #=> true
compatible?("abc"@en  "b"@en)                  #=> true
compatible?("abc"@fr  "b"@ja)                  #=> false
compatible?("abc"     "b"@ja)                      #=> false
compatible?("abc"     "b"@en)                      #=> false
compatible?("abc"^^xsd:string "b"@en)          #=> false

@see www.w3.org/TR/sparql11-query/#func-arg-compatibility @since 2.0

# File lib/rdf/model/literal.rb, line 254
def compatible?(other)
  return false unless other.literal? && plain? && other.plain?

  # * The arguments are simple literals or literals typed as xsd:string
  # * The arguments are plain literals with identical language-tags
  # * The first argument is a plain literal with language-tag and the second argument is a simple literal or literal typed as xsd:string
  language? || direction? ?
    (language == other.language && direction == other.direction || other.datatype == XSD_STRING) :
    other.datatype == XSD_STRING
end
comperable_datatype2?(other) click to toggle source

Returns ‘true` if the literals are comperable.

Used for <=> operator.

@return [Boolean]

# File lib/rdf/model/literal.rb, line 501
def comperable_datatype2?(other)
  case self
  when RDF::Literal::Numeric, RDF::Literal::Boolean
    case other
    when RDF::Literal::Numeric, RDF::Literal::Boolean
      true
    else
      false
    end
  else
    self.datatype == other.datatype
  end
end
comperable_datatype?(other) click to toggle source

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]

# File lib/rdf/model/literal.rb, line 481
def comperable_datatype?(other)
  return false unless self.plain? || self.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.language?
  else
    # An unknown datatype may not be used for comparison, unless it has a language? (open-eq-8)
    self.language?
  end
end
datatype?() click to toggle source

Returns ‘true` if this is a datatyped literal.

For historical reasons, this excludes xsd:string and rdf:langString

@return [Boolean] ‘true` or `false` @see www.w3.org/TR/rdf-concepts/#dfn-typed-literal

# File lib/rdf/model/literal.rb, line 430
def datatype?
  !plain? && !language? && !direction?
end
Also aliased as: has_datatype?, typed?, datatyped?
datatyped?()
Alias for: datatype?
direction?() click to toggle source

Returns ‘true` if this is a directional language-tagged string.

@return [Boolean] ‘true` or `false` @see www.w3.org/TR/rdf-concepts/#dfn-dir-lang-string

# File lib/rdf/model/literal.rb, line 419
def direction?
  datatype == RDF.dirLangString
end
english?() click to toggle source

Returns ‘true` if this is a language-tagged literal in the English language.

@return [Boolean] ‘true` or `false` @since 3.3.2

# File lib/rdf/model/literal.rb, line 459
def english?
  /\Aen(?:-[A-Za-z]{2})?\z/ === language.to_s
end
eql?(other) click to toggle source

Determins if ‘self` is the same term as `other`.

@example

RDF::Literal(1).eql?(RDF::Literal(1.0))  #=> false

@param [Object] other @return [Boolean] ‘true` or `false`

# File lib/rdf/model/literal.rb, line 298
def eql?(other)
  self.equal?(other) ||
    (self.class.eql?(other.class) &&
     self.value_hash == other.value_hash &&
     self.value.eql?(other.value) &&
     self.language.to_s.eql?(other.language.to_s) &&
     self.direction.to_s.eql?(other.direction.to_s) &&
     self.datatype.eql?(other.datatype))
end
escape(string) click to toggle source

Escape a literal using ECHAR escapes.

ECHAR ::= '\' [tbnrf"'\]

@note N-Triples only requires ‘"nr’ to be escaped.

@param [String] string @return [String] @see RDF::Term#escape

# File lib/rdf/model/literal.rb, line 560
def escape(string)
  string.gsub('\\', '\\\\').
         gsub("\t", '\\t').
         gsub("\b", '\\b').
         gsub("\n", '\\n').
         gsub("\r", '\\r').
         gsub("\f", '\\f').
         gsub('"', '\\"').
         freeze
end
freeze() click to toggle source

@private

Calls superclass method
# File lib/rdf/model/literal.rb, line 284
def freeze
  hash.freeze
  value_hash.freeze
  super
end
has_datatype?()
Alias for: datatype?
has_language?()
Alias for: language?
hash() click to toggle source

Returns a hash code for this literal.

@return [Integer]

# File lib/rdf/model/literal.rb, line 269
def hash
  @hash ||= [to_s, datatype, language, direction].compact.hash
end
humanize(lang = :en) click to toggle source

Returns a human-readable value for the literal

@return [String] @since 1.1.6

# File lib/rdf/model/literal.rb, line 584
def humanize(lang = :en)
  to_s.freeze
end
inspect() click to toggle source

Returns a developer-friendly representation of ‘self`.

@return [String]

# File lib/rdf/model/literal.rb, line 592
def inspect
  sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, RDF::NTriples.serialize(self))
end
language?() click to toggle source

Returns ‘true` if this is a language-tagged string.

@return [Boolean] ‘true` or `false` @see www.w3.org/TR/rdf-concepts/#dfn-language-tagged-string

# File lib/rdf/model/literal.rb, line 409
def language?
  [RDF.langString, RDF.dirLangString].include?(datatype)
end
Also aliased as: has_language?
literal?() click to toggle source

Returns ‘true`.

@return [Boolean] ‘true` or `false`

# File lib/rdf/model/literal.rb, line 227
def literal?
  true
end
object() click to toggle source

@return [Object]

# File lib/rdf/model/literal.rb, line 219
def object
  defined?(@object) ? @object : value
end
plain?() click to toggle source

Returns ‘true` if this is a plain literal. A plain literal may have a language and direction, but may not have a datatype. For all practical purposes, this includes xsd:string literals too.

@return [Boolean] ‘true` or `false` @see www.w3.org/TR/rdf-concepts/#dfn-plain-literal

# File lib/rdf/model/literal.rb, line 386
def plain?
  [
    RDF.langString,
    RDF.dirLangString,
    XSD_STRING
  ].include?(datatype)
end
simple?() click to toggle source

Returns ‘true` if this is a simple literal. A simple literal has no datatype or language.

@return [Boolean] ‘true` or `false` @see www.w3.org/TR/sparql11-query/#simple_literal

# File lib/rdf/model/literal.rb, line 400
def simple?
  datatype == XSD_STRING
end
squish(*other_string) click to toggle source

Returns the literal, first removing all whitespace on both ends of the value, and then changing remaining consecutive whitespace groups into one space each.

Note that it handles both ASCII and Unicode whitespace.

@see [String#squish](apidock.com/rails/String/squish) @return [RDF::Literal] a new literal based on ‘self`.

# File lib/rdf/model/literal.rb, line 533
def squish(*other_string)
  self.dup.squish!
end
squish!() click to toggle source

Performs a destructive {#squish}.

@see [String#squish!](apidock.com/rails/String/squish%21) @return self

# File lib/rdf/model/literal.rb, line 542
def squish!
  @string = value.
    gsub(/\A[[:space:]]+/, '').
    gsub(/[[:space:]]+\z/, '').
    gsub(/[[:space:]]+/, ' ')
  self
end
to_s() click to toggle source

Returns the value as a string.

@return [String]

# File lib/rdf/model/literal.rb, line 575
def to_s
  @object.to_s.freeze
end
typed?()
Alias for: datatype?
valid?() click to toggle source

Returns ‘true` if the value adheres to the defined grammar of the datatype.

@return [Boolean] ‘true` or `false` @since 0.2.1

# File lib/rdf/model/literal.rb, line 443
def valid?
  BCP47.parse(language.to_s) if language?
  return false if direction? && !%i{ltr rtl}.include?(direction)
  return false if datatype? && datatype.invalid?
  grammar = self.class.const_get(:GRAMMAR) rescue nil
  grammar.nil? || value.match?(grammar)
rescue BCP47::InvalidLanguageTag
  false
end
validate!() click to toggle source

Validates the value using {RDF::Value#valid?}, raising an error if the value is invalid.

@return [RDF::Literal] ‘self` @raise [ArgumentError] if the value is invalid @since 0.2.1

# File lib/rdf/model/literal.rb, line 470
def validate!
  raise ArgumentError, "#{to_s.inspect} is not a valid <#{datatype.to_s}> literal" if invalid?
  self
end
value() click to toggle source

Returns the value as a string.

@return [String]

# File lib/rdf/model/literal.rb, line 213
def value
  instance_variable_defined?(:@string) && @string || to_s
end
value_hash() click to toggle source

Returns a hash code for the value.

@return [Integer]

# File lib/rdf/model/literal.rb, line 278
def value_hash
  @value_hash ||= value.hash
end

Protected Instance Methods

method_missing(name, *args) click to toggle source

@overload to_str

This method is implemented when the datatype is `xsd:string`, `rdf:langString`, or `rdf:dirLangString`
@return [String]
Calls superclass method
# File lib/rdf/model/literal.rb, line 602
def method_missing(name, *args)
  case name
  when :to_str
    return to_s if [RDF.langString, RDF.dirLangString, XSD_STRING].include?(@datatype)
  end
  super
end
respond_to_missing?(name, include_private = false) click to toggle source
Calls superclass method
# File lib/rdf/model/literal.rb, line 610
def respond_to_missing?(name, include_private = false)
  case name
  when :to_str
    return true if [RDF.langString, RDF.dirLangString, XSD_STRING].include?(@datatype)
  end
  super
end