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,
lib/rdf/model/literal/temporal.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 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.

Examples:

Creating a plain literal

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

Creating a language-tagged string (1)

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

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)

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

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, Numeric, Temporal, Token

Defined Under Namespace

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

Constant Summary collapse

TRUE =
RDF::Literal.new(true)
FALSE =
RDF::Literal.new(false)
ZERO =
RDF::Literal.new(0)
XSD_STRING =
RDF::URI("http://www.w3.org/2001/XMLSchema#string")

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, direction: nil, lexical: nil, validate: false, canonicalize: false, **options) ⇒ Literal

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

Parameters:

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

    (nil) Initial text direction.

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



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/rdf/model/literal.rb', line 190

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

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`, `rdf:langString`, or `rdf:dirLangString`

Returns:

  • (String)


603
604
605
606
607
608
609
# File 'lib/rdf/model/literal.rb', line 603

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

Instance Attribute Details

#datatypeURI

Returns The XML Schema datatype URI (optional).

Returns:

  • (URI)

    The XML Schema datatype URI (optional).



167
168
169
# File 'lib/rdf/model/literal.rb', line 167

def datatype
  @datatype
end

#directionSymbol

Returns The base direction (optional). Implies ‘datatype` is `rdf:dirLangString`.

Returns:

  • (Symbol)

    The base direction (optional). Implies ‘datatype` is `rdf:dirLangString`.



164
165
166
# File 'lib/rdf/model/literal.rb', line 164

def direction
  @direction
end

#languageSymbol

Returns The language-tag (optional). Implies ‘datatype` is `rdf:langString`.

Returns:

  • (Symbol)

    The language-tag (optional). Implies ‘datatype` is `rdf:langString`.



161
162
163
# File 'lib/rdf/model/literal.rb', line 161

def language
  @language
end

Class Method Details

.datatype_mapObject

Return Hash mapping from datatype URI to class



103
104
105
106
107
108
109
# File 'lib/rdf/model/literal.rb', line 103

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



114
115
116
# File 'lib/rdf/model/literal.rb', line 114

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

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



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/rdf/model/literal.rb', line 120

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

Instance Method Details

#<=>(other) ⇒ Integer

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

Parameters:

  • other (Object)

Returns:

  • (Integer)

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



353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/rdf/model/literal.rb', line 353

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:



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/rdf/model/literal.rb', line 320

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

#canonicalize!RDF::Literal

Converts this literal into its canonical lexical representation.

Subclasses should override this as needed and appropriate.

Returns:

Since:

  • 0.3.0



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

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

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



255
256
257
258
259
260
261
262
263
264
# File 'lib/rdf/model/literal.rb', line 255

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

Returns ‘true` if the literals are comperable.

Used for <=> operator.

Returns:



502
503
504
505
506
507
508
509
510
511
512
513
514
# File 'lib/rdf/model/literal.rb', line 502

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:



482
483
484
485
486
487
488
489
490
491
492
493
494
# File 'lib/rdf/model/literal.rb', line 482

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:



431
432
433
# File 'lib/rdf/model/literal.rb', line 431

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

#direction?Boolean

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

Returns:

  • (Boolean)

    ‘true` or `false`

See Also:



420
421
422
# File 'lib/rdf/model/literal.rb', line 420

def direction?
  datatype == RDF.dirLangString
end

#english?Boolean

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

Returns:

  • (Boolean)

    ‘true` or `false`

Since:

  • 3.3.2



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

def english?
  /\Aen(?:-[A-Za-z]{2})?\z/ === language.to_s
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`



299
300
301
302
303
304
305
306
307
# File 'lib/rdf/model/literal.rb', line 299

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

Note:

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

Escape a literal using ECHAR escapes.

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

Parameters:

  • string (String)

Returns:

  • (String)

See Also:

  • Term#escape


561
562
563
564
565
566
567
568
569
570
# File 'lib/rdf/model/literal.rb', line 561

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

#freezeObject



285
286
287
288
289
# File 'lib/rdf/model/literal.rb', line 285

def freeze
  hash.freeze
  value_hash.freeze
  super
end

#hashInteger

Returns a hash code for this literal.

Returns:



270
271
272
# File 'lib/rdf/model/literal.rb', line 270

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

#humanize(lang = :en) ⇒ String

Returns a human-readable value for the literal

Returns:

  • (String)

Since:

  • 1.1.6



585
586
587
# File 'lib/rdf/model/literal.rb', line 585

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

#inspectString

Returns a developer-friendly representation of ‘self`.

Returns:

  • (String)


593
594
595
# File 'lib/rdf/model/literal.rb', line 593

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 string.

Returns:

  • (Boolean)

    ‘true` or `false`

See Also:



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

def language?
  [RDF.langString, RDF.dirLangString].include?(datatype)
end

#literal?Boolean

Returns ‘true`.

Returns:

  • (Boolean)

    ‘true` or `false`



228
229
230
# File 'lib/rdf/model/literal.rb', line 228

def literal?
  true
end

#objectObject

Returns:

  • (Object)


220
221
222
# File 'lib/rdf/model/literal.rb', line 220

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

#plain?Boolean

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.

Returns:

  • (Boolean)

    ‘true` or `false`

See Also:



387
388
389
390
391
392
393
# File 'lib/rdf/model/literal.rb', line 387

def plain?
  [
    RDF.langString,
    RDF.dirLangString,
    XSD_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:



401
402
403
# File 'lib/rdf/model/literal.rb', line 401

def simple?
  datatype == XSD_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:



534
535
536
# File 'lib/rdf/model/literal.rb', line 534

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

#squish!Object

Performs a destructive #squish.



543
544
545
546
547
548
549
# File 'lib/rdf/model/literal.rb', line 543

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

#to_sString

Returns the value as a string.

Returns:

  • (String)


576
577
578
# File 'lib/rdf/model/literal.rb', line 576

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



444
445
446
447
448
449
450
451
452
# File 'lib/rdf/model/literal.rb', line 444

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!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



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

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:

  • (String)


214
215
216
# File 'lib/rdf/model/literal.rb', line 214

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

#value_hashInteger

Returns a hash code for the value.

Returns:



279
280
281
# File 'lib/rdf/model/literal.rb', line 279

def value_hash
  @value_hash ||= value.hash
end