Class: RDF::Literal

Inherits:
Object
  • Object
show all
Includes:
Term
Defined in:
lib/rdf/model/literal.rb,
lib/rdf/model/literal/date.rb,
lib/rdf/model/literal/time.rb,
lib/rdf/model/literal/token.rb,
lib/rdf/model/literal/double.rb,
lib/rdf/model/literal/boolean.rb,
lib/rdf/model/literal/decimal.rb,
lib/rdf/model/literal/integer.rb,
lib/rdf/model/literal/numeric.rb,
lib/rdf/model/literal/datetime.rb

Overview

An RDF literal.

Subclasses of 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 Value instances call Value#type_error, which, returns false. Implementations wishing to have TypeError raised should mix-in 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 literals. Internally, plain literals are given the ‘xsd:string` datatype and language tagged literals 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 literals. 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 literals.

Examples:

Creating a plain literal

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

Creating a language-tagged literal (1)

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

Creating a language-tagged literal (2)

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

Creating an explicitly datatyped literal

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

Creating an implicitly datatyped literal

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

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 Also:

Direct Known Subclasses

Boolean, Date, DateTime, Numeric, Time, Token

Defined Under Namespace

Classes: Boolean, Date, DateTime, Decimal, Double, Integer, Numeric, Time, Token

Constant Summary collapse

TRUE =
RDF::Literal.new(true)
FALSE =
RDF::Literal.new(false)
ZERO =
RDF::Literal.new(0)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Term

#term?, #terms, #to_base, #to_term

Methods included from Value

#anonymous?, #canonicalize, #constant?, #graph?, #inspect!, #invalid?, #iri?, #list?, #node?, #resource?, #start_with?, #statement?, #term?, #to_nquads, #to_ntriples, #to_rdf, #to_term, #type_error, #uri?, #variable?

Constructor Details

#initialize(value, language: nil, datatype: nil, lexical: nil, validate: false, canonicalize: false, **options) ⇒ Literal

Literals without a datatype are given either xsd:string or rdf:langString depending on if there is language

Parameters:

  • value (Object)
  • language (Symbol) (defaults to: nil)

    (nil) Language is downcased to ensure proper matching

  • lexical (String) (defaults to: nil)

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

  • datatype (URI) (defaults to: nil)

    (nil)

  • validate (Boolean) (defaults to: false)

    (false)

  • canonicalize (Boolean) (defaults to: false)

    (false)

Raises:

  • (ArgumentError)

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

See Also:



165
166
167
168
169
170
171
172
173
174
175
# File 'lib/rdf/model/literal.rb', line 165

def initialize(value, language: nil, datatype: 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
  @datatype = RDF::URI(datatype).freeze if datatype
  @datatype ||= self.class.const_get(:DATATYPE) if self.class.const_defined?(:DATATYPE)
  @datatype ||= instance_variable_defined?(:@language) && @language ? RDF.langString : RDF::URI("http://www.w3.org/2001/XMLSchema#string")
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#String (protected)

This method is implemented when the datatype is ‘xsd:string` or `rdf:langString`

Returns:



540
541
542
543
544
545
546
# File 'lib/rdf/model/literal.rb', line 540

def method_missing(name, *args)
  case name
  when :to_str
    return to_s if @datatype == RDF.langString || @datatype == RDF::URI("http://www.w3.org/2001/XMLSchema#string")
  end
  super
end

Instance Attribute Details

#datatypeURI

Returns The XML Schema datatype URI (optional).

Returns:

  • (URI)

    The XML Schema datatype URI (optional).



144
145
146
# File 'lib/rdf/model/literal.rb', line 144

def datatype
  @datatype
end

#languageSymbol

Returns The language tag (optional).

Returns:

  • (Symbol)

    The language tag (optional).



141
142
143
# File 'lib/rdf/model/literal.rb', line 141

def language
  @language
end

Class Method Details

.datatype_mapObject

Return Hash mapping from datatype URI to class



90
91
92
93
94
95
96
# File 'lib/rdf/model/literal.rb', line 90

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) ⇒ Object

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



101
102
103
# File 'lib/rdf/model/literal.rb', line 101

def self.datatyped_class(uri)
  datatype_map[uri]
end

.new(value, language: nil, datatype: nil, lexical: nil, validate: false, canonicalize: false, **options) ⇒ Object

Raises:

  • (ArgumentError)


107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/rdf/model/literal.rb', line 107

def self.new(value, language: nil, datatype: nil, lexical: nil, validate: false, canonicalize: false, **options)
  raise ArgumentError, "datatype with language must be rdf:langString" if language && (datatype || RDF.langString).to_s != RDF.langString.to_s

  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, **options)
  literal.validate!     if validate
  literal.canonicalize! if canonicalize
  literal
end

Instance Method Details

#<=>(other) ⇒ Integer

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

Parameters:

  • other (Object)

Returns:

  • (Integer)

    ‘-1`, `0`, or `1`



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/rdf/model/literal.rb', line 316

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) ⇒ Boolean Also known as: ===

Returns ‘true` if this literal is equivalent to `other` (with type check).

Examples:

RDF::Literal(1) == RDF::Literal(1.0)     #=> true

Parameters:

  • other (Object)

Returns:

  • (Boolean)

    ‘true` or `false`

See Also:



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/rdf/model/literal.rb', line 286

def ==(other)
  case other
  when Literal
    case
    when self.eql?(other)
      true
    when self.language? && self.language.to_s == other.language.to_s
      # 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)
      # 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.simple? && self.value.eql?(other)
  else false
  end
end

#canonicalize!RDF::Literal

Converts this literal into its canonical lexical representation.

Subclasses should override this as needed and appropriate.

Returns:

Since:

  • 0.3.0



460
461
462
# File 'lib/rdf/model/literal.rb', line 460

def canonicalize!
  self
end

#compatible?(other) ⇒ Boolean

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

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

Examples:

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

Returns:

See Also:

Since:

  • 2.0



222
223
224
225
226
227
228
229
230
231
# File 'lib/rdf/model/literal.rb', line 222

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? ?
    (language == other.language || other.datatype == RDF::URI("http://www.w3.org/2001/XMLSchema#string")) :
    other.datatype == RDF::URI("http://www.w3.org/2001/XMLSchema#string")
end

#comperable_datatype2?(other) ⇒ Boolean

Returns ‘true` if the literals are comperable.

Used for <=> operator.

Returns:



439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/rdf/model/literal.rb', line 439

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) ⇒ Boolean

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

Returns:



419
420
421
422
423
424
425
426
427
428
429
430
431
# File 'lib/rdf/model/literal.rb', line 419

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?Boolean Also known as: has_datatype?, typed?, datatyped?

Returns ‘true` if this is a datatyped literal.

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

Returns:

  • (Boolean)

    ‘true` or `false`

See Also:



381
382
383
# File 'lib/rdf/model/literal.rb', line 381

def datatype?
  !plain? && !language?
end

#eql?(other) ⇒ Boolean

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

Examples:

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

Parameters:

  • other (Object)

Returns:

  • (Boolean)

    ‘true` or `false`



266
267
268
269
270
271
272
273
# File 'lib/rdf/model/literal.rb', line 266

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.datatype.eql?(other.datatype))
end

#escape(string) ⇒ String

Note:

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

Escape a literal using ECHAR escapes.

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

Parameters:

Returns:

See Also:

  • Term#escape


498
499
500
501
502
503
504
505
506
507
# File 'lib/rdf/model/literal.rb', line 498

def escape(string)
  string.gsub('\\', '\\\\').
         gsub("\t", '\\t').
         gsub("\b", '\\b').
         gsub("\n", '\\n').
         gsub("\r", '\\r').
         gsub("\f", '\\f').
         gsub('"', '\\"').
         freeze
end

#freezeObject



252
253
254
255
256
# File 'lib/rdf/model/literal.rb', line 252

def freeze
  hash.freeze
  value_hash.freeze
  super
end

#hashInteger

Returns a hash code for this literal.

Returns:



237
238
239
# File 'lib/rdf/model/literal.rb', line 237

def hash
  @hash ||= [to_s, datatype, language].hash
end

#humanize(lang = :en) ⇒ String

Returns a human-readable value for the literal

Returns:

Since:

  • 1.1.6



522
523
524
# File 'lib/rdf/model/literal.rb', line 522

def humanize(lang = :en)
  to_s.freeze
end

#inspectString

Returns a developer-friendly representation of ‘self`.

Returns:



530
531
532
# File 'lib/rdf/model/literal.rb', line 530

def inspect
  sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, RDF::NTriples.serialize(self))
end

#language?Boolean Also known as: has_language?

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

Returns:

  • (Boolean)

    ‘true` or `false`

See Also:



369
370
371
# File 'lib/rdf/model/literal.rb', line 369

def language?
  datatype == RDF.langString
end

#literal?Boolean

Returns ‘true`.

Returns:

  • (Boolean)

    ‘true` or `false`



195
196
197
# File 'lib/rdf/model/literal.rb', line 195

def literal?
  true
end

#objectObject

Returns:

  • (Object)


187
188
189
# File 'lib/rdf/model/literal.rb', line 187

def object
  defined?(@object) ? @object : value
end

#plain?Boolean

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

Returns:

  • (Boolean)

    ‘true` or `false`

See Also:



350
351
352
# File 'lib/rdf/model/literal.rb', line 350

def plain?
  [RDF.langString, RDF::URI("http://www.w3.org/2001/XMLSchema#string")].include?(datatype)
end

#simple?Boolean

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

Returns:

  • (Boolean)

    ‘true` or `false`

See Also:



360
361
362
# File 'lib/rdf/model/literal.rb', line 360

def simple?
  datatype == RDF::URI("http://www.w3.org/2001/XMLSchema#string")
end

#squish(*other_string) ⇒ RDF::Literal

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.

Returns:

See Also:



471
472
473
# File 'lib/rdf/model/literal.rb', line 471

def squish(*other_string)
  self.dup.squish!
end

#squish!Object

Performs a destructive #squish.



480
481
482
483
484
485
486
# File 'lib/rdf/model/literal.rb', line 480

def squish!
  @string = value.
    gsub(/\A[[:space:]]+/, '').
    gsub(/[[:space:]]+\z/, '').
    gsub(/[[:space:]]+/, ' ')
  self
end

#to_sString

Returns the value as a string.

Returns:



513
514
515
# File 'lib/rdf/model/literal.rb', line 513

def to_s
  @object.to_s.freeze
end

#valid?Boolean

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

Returns:

  • (Boolean)

    ‘true` or `false`

Since:

  • 0.2.1



394
395
396
397
398
399
# File 'lib/rdf/model/literal.rb', line 394

def valid?
  return false if language? && language.to_s !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/
  return false if datatype? && datatype.invalid?
  grammar = self.class.const_get(:GRAMMAR) rescue nil
  grammar.nil? || value.match?(grammar)
end

#validate!RDF::Literal

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

Returns:

Raises:

  • (ArgumentError)

    if the value is invalid

Since:

  • 0.2.1



408
409
410
411
# File 'lib/rdf/model/literal.rb', line 408

def validate!
  raise ArgumentError, "#{to_s.inspect} is not a valid <#{datatype.to_s}> literal" if invalid?
  self
end

#valueString

Returns the value as a string.

Returns:



181
182
183
# File 'lib/rdf/model/literal.rb', line 181

def value
  instance_variable_defined?(:@string) && @string || to_s
end

#value_hashInteger

Returns a hash code for the value.

Returns:



246
247
248
# File 'lib/rdf/model/literal.rb', line 246

def value_hash
  @value_hash ||= value.hash
end