rdf 3.2.10 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 948292ebb4f6e10c59b67e873d2d14884c53c5fab199e901e4e4dadf768f379f
4
- data.tar.gz: 8731942201a15bcef71284fe0a79a3bc9a97b878ef2c53b757869cadefdadf54
3
+ metadata.gz: c90d17f92036ca2959a7be98c9cf5322374c0f0a623d4b44782bde2478eb2a65
4
+ data.tar.gz: ce40842adb100ff6d2ec068cc20b0320a30dab85b5a1a47056823e10fac59302
5
5
  SHA512:
6
- metadata.gz: 41d8fca7513dc56c6447b3b40d995b04f407e986ffc4e1a7fc86de7632ca026b6effdb8e7d3d815ba7ddb27c44e6af53a08bbde43390a065170e9a58446231d8
7
- data.tar.gz: 5c9895f52363ffee6bc408d5e476a9a345d7aec62ec8594a8ca49f5c3ef9016f307741c0ef8b537d6fc49f714a25d2a4b0bee64631951191c750ef53809533c4
6
+ metadata.gz: 4ac4099d5eadff8676d0d692433d1dc3f9875032cf6617f7e0da53a559a53a88afbd6191be491c28204ca5475d007813858beefef2ac4b51f3a13c572b83aecb
7
+ data.tar.gz: 48688b3f9da835d74fed96bca2f60063da209db8567fb29932d878ab252c85fe0b4f08413ed627bd6cf95ec08ffc6447828c8f353ef897e055bbf5b786204a18
data/README.md CHANGED
@@ -5,7 +5,7 @@ This is a pure-Ruby library for working with [Resource Description Framework
5
5
 
6
6
  * <https://ruby-rdf.github.io/rdf>
7
7
 
8
- [![Gem Version](https://badge.fury.io/rb/rdf.png)](https://badge.fury.io/rb/rdf)
8
+ [![Gem Version](https://badge.fury.io/rb/rdf.svg)](https://badge.fury.io/rb/rdf)
9
9
  [![Build Status](https://github.com/ruby-rdf/rdf/workflows/CI/badge.svg?branch=develop)](https://github.com/ruby-rdf/rdf/actions?query=workflow%3ACI)
10
10
  [![Coverage Status](https://coveralls.io/repos/ruby-rdf/rdf/badge.svg?branch=develop)](https://coveralls.io/github/ruby-rdf/rdf?branch=develop)
11
11
  [![Gitter chat](https://badges.gitter.im/ruby-rdf/rdf.png)](https://gitter.im/ruby-rdf/rdf)
@@ -14,26 +14,28 @@ This is a pure-Ruby library for working with [Resource Description Framework
14
14
 
15
15
  1. [Features](#features)
16
16
  2. [Differences between RDF 1.0 and RDF 1.1](#differences-between-rdf-1-0-and-rdf-1-1)
17
- 3. [Tutorials](#tutorials)
18
- 4. [Command Line](#command-line)
19
- 5. [Examples](#examples)
20
- 6. [Reader/Writer convenience methods](#reader/writer-convenience-methods)
21
- 7. [RDF* (RDFStar)](#rdf*-(rdfstar))
22
- 8. [Documentation](#documentation)
23
- 9. [Dependencies](#dependencies)
24
- 10. [Installation](#installation)
25
- 11. [Download](#download)
26
- 12. [Resources](#resources)
27
- 13. [Mailing List](#mailing-list)
28
- 14. [Authors](#authors)
29
- 15. [Contributors](#contributors)
30
- 16. [Contributing](#contributing)
31
- 17. [License](#license)
17
+ 3. [Differences between RDF 1.1 and RDF 1.2](#differences-between-rdf-1-1-and-rdf-1-2)
18
+ 4. [Tutorials](#tutorials)
19
+ 5. [Command Line](#command-line)
20
+ 6. [Examples](#examples)
21
+ 7. [Reader/Writer convenience methods](#reader/writer-convenience-methods)
22
+ 8. [RDF 1.2](#rdf\_12)
23
+ 9. [Documentation](#documentation)
24
+ 10. [Dependencies](#dependencies)
25
+ 11. [Installation](#installation)
26
+ 12. [Download](#download)
27
+ 13. [Resources](#resources)
28
+ 14. [Mailing List](#mailing-list)
29
+ 15. [Authors](#authors)
30
+ 16. [Contributors](#contributors)
31
+ 17. [Contributing](#contributing)
32
+ 18. [License](#license)
32
33
 
33
34
  ## Features
34
35
 
35
36
  * 100% pure Ruby with minimal dependencies and no bloat.
36
37
  * Fully compatible with [RDF 1.1][] specifications.
38
+ * Provisional support for [RDF 1.2][] specifications.
37
39
  * 100% free and unencumbered [public domain](https://unlicense.org/) software.
38
40
  * Provides a clean, well-designed RDF object model and related APIs.
39
41
  * Supports parsing and serializing [N-Triples][] and [N-Quads][] out of the box, with more
@@ -45,11 +47,10 @@ This is a pure-Ruby library for working with [Resource Description Framework
45
47
  not modify any of Ruby's core classes or standard library.
46
48
  * Based entirely on Ruby's autoloading, meaning that you can generally make
47
49
  use of any one part of the library without needing to load up the rest.
48
- * Compatible with Ruby Ruby >= 2.4, Rubinius and JRuby 9.0+.
49
- * Note, changes in mapping hashes to keyword arguments for Ruby 2.7+ may require that arguments be passed more explicitly, especially when the first argument is a Hash and there are optional keyword arguments. In this case, Hash argument may need to be explicitly included within `{}` and the optional keyword arguments may need to be specified using `**{}` if there are no keyword arguments.
50
+ * Compatible with Ruby Ruby >= 3.0, Rubinius and JRuby 9.0+.
51
+ * Note, changes in mapping hashes to keyword arguments for Ruby 3+ may require that arguments be passed more explicitly, especially when the first argument is a Hash and there are optional keyword arguments. In this case, Hash argument may need to be explicitly included within `{}` and the optional keyword arguments may need to be specified using `**{}` if there are no keyword arguments.
50
52
  * Performs auto-detection of input to select appropriate Reader class if one
51
53
  cannot be determined from file characteristics.
52
- * Provisional support for [RDF*][].
53
54
 
54
55
  ### HTTP requests
55
56
 
@@ -102,6 +103,10 @@ the 1.1 release of RDF.rb:
102
103
 
103
104
  Notably, {RDF::Queryable#query} and {RDF::Query#execute} are now completely symmetric; this allows an implementation of {RDF::Queryable} to optimize queries using implementation-specific logic, allowing for substantial performance improvements when executing BGP queries.
104
105
 
106
+ ## Differences between RDF 1.1 and RDF 1.2
107
+ * {RDF::Literal} has an optional `direction` property for directional language-tagged strings.
108
+ * Removes support for legacy `text/plain` (as an alias for `application/n-triples`) and `text/x-nquads` (as an alias for `application/n-quads`)
109
+
105
110
  ## Tutorials
106
111
 
107
112
  * [Getting data from the Semantic Web using Ruby and RDF.rb](https://semanticweb.org/wiki/Getting_data_from_the_Semantic_Web_%28Ruby%29)
@@ -260,15 +265,16 @@ A separate [SPARQL][SPARQL doc] gem builds on basic BGP support to provide full
260
265
  foaf[:name] #=> RDF::URI("http://xmlns.com/foaf/0.1/name")
261
266
  foaf['mbox'] #=> RDF::URI("http://xmlns.com/foaf/0.1/mbox")
262
267
 
263
- ## RDF* (RDFStar)
268
+ ## RDF 1.2
264
269
 
265
- [RDF.rb][] includes provisional support for [RDF*][] with an N-Triples/N-Quads syntax extension that uses inline statements in the _subject_ or _object_ position.
270
+ [RDF.rb][] includes provisional support for [RDF 1.2][] with an N-Triples/N-Quads syntax for quoted triples in the _subject_ or _object_ position.
271
+ [RDF.rb][] includes provisional support for [RDF 1.2][] directional language-tagged strings, which are literals of type `rdf:dirLangString` having both a `language` and `direction`.
266
272
 
267
273
  Internally, an `RDF::Statement` is treated as another resource, along with `RDF::URI` and `RDF::Node`, which allows an `RDF::Statement` to have a `#subject` or `#object` which is also an `RDF::Statement`.
268
274
 
269
275
  **Note: This feature is subject to change or elimination as the standards process progresses.**
270
276
 
271
- ### Serializing a Graph containing embedded statements
277
+ ### Serializing a Graph containing quoted triples
272
278
 
273
279
  require 'rdf/ntriples'
274
280
  statement = RDF::Statement(RDF::URI('bob'), RDF::Vocab::FOAF.age, RDF::Literal(23))
@@ -276,7 +282,7 @@ Internally, an `RDF::Statement` is treated as another resource, along with `RDF:
276
282
  graph.dump(:ntriples, validate: false)
277
283
  # => '<<<bob> <http://xmlns.com/foaf/0.1/age> "23"^^<http://www.w3.org/2001/XMLSchema#integer>>> <ex:certainty> "0.9"^^<http://www.w3.org/2001/XMLSchema#double> .'
278
284
 
279
- ### Reading a Graph containing embedded statements
285
+ ### Reading a Graph containing quoted triples
280
286
 
281
287
  By default, the N-Triples reader will reject a document containing a subject resource.
282
288
 
@@ -286,13 +292,6 @@ By default, the N-Triples reader will reject a document containing a subject res
286
292
  end
287
293
  # => RDF::ReaderError
288
294
 
289
- Readers support a boolean valued `rdfstar` option.
290
-
291
- graph = RDF::Graph.new do |graph|
292
- RDF::NTriples::Reader.new(nt, rdfstar: true) {|reader| graph << reader}
293
- end
294
- graph.count #=> 1
295
-
296
295
  ## Documentation
297
296
 
298
297
  <https://ruby-rdf.github.io/rdf>
@@ -398,8 +397,9 @@ from BNode identity (i.e., they each entail the other)
398
397
 
399
398
  ## Dependencies
400
399
 
401
- * [Ruby](https://ruby-lang.org/) (>= 2.6)
400
+ * [Ruby](https://ruby-lang.org/) (>= 3.0)
402
401
  * [LinkHeader][] (>= 0.0.8)
402
+ * [bcp47_spec][] ( ~> 0.2)
403
403
  * Soft dependency on [RestClient][] (>= 2.1)
404
404
 
405
405
  ## Installation
@@ -407,7 +407,7 @@ from BNode identity (i.e., they each entail the other)
407
407
  The recommended installation method is via [RubyGems](https://rubygems.org/).
408
408
  To install the latest official release of RDF.rb, do:
409
409
 
410
- % [sudo] gem install rdf # Ruby 2.6+
410
+ % [sudo] gem install rdf # Ruby 3+
411
411
 
412
412
  ## Download
413
413
 
@@ -481,8 +481,10 @@ This is free and unencumbered public domain software. For more information,
481
481
  see <https://unlicense.org/> or the accompanying {file:UNLICENSE} file.
482
482
 
483
483
  [RDF]: https://www.w3.org/RDF/
484
- [N-Triples]: https://www.w3.org/TR/n-triples/
485
- [N-Quads]: https://www.w3.org/TR/n-quads/
484
+ [LinkHeader]: https://github.com/asplake/link_header
485
+ [bcp47_spec]: https://github.com/dadah89/bcp47_spec
486
+ [N-Triples]: https://www.w3.org/TR/rdf-n-triples/
487
+ [N-Quads]: https://www.w3.org/TR/rdf-n-quads/
486
488
  [YARD]: https://yardoc.org/
487
489
  [YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md
488
490
  [PDD]: https://unlicense.org/#unlicensing-contributions
@@ -496,6 +498,7 @@ see <https://unlicense.org/> or the accompanying {file:UNLICENSE} file.
496
498
  [SPARQL doc]: https://ruby-rdf.github.io/sparql
497
499
  [RDF 1.0]: https://www.w3.org/TR/2004/REC-rdf-concepts-20040210/
498
500
  [RDF 1.1]: https://www.w3.org/TR/rdf11-concepts/
501
+ [RDF 1.2]: https://www.w3.org/TR/rdf12-concepts/
499
502
  [SPARQL 1.1]: https://www.w3.org/TR/sparql11-query/
500
503
  [RDF.rb]: https://ruby-rdf.github.io/
501
504
  [RDF::DO]: https://ruby-rdf.github.io/rdf-do
@@ -510,7 +513,6 @@ see <https://unlicense.org/> or the accompanying {file:UNLICENSE} file.
510
513
  [RDF::TriX]: https://ruby-rdf.github.io/rdf-trix
511
514
  [RDF::Turtle]: https://ruby-rdf.github.io/rdf-turtle
512
515
  [RDF::Raptor]: https://ruby-rdf.github.io/rdf-raptor
513
- [RDF*]: https://w3c.github.io/rdf-star/rdf-star-cg-spec.html
514
516
  [LinkedData]: https://ruby-rdf.github.io/linkeddata
515
517
  [JSON::LD]: https://ruby-rdf.github.io/json-ld
516
518
  [RestClient]: https://rubygems.org/gems/rest-client
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.2.10
1
+ 3.3.0
data/lib/rdf/cli.rb CHANGED
@@ -60,7 +60,7 @@ module RDF
60
60
  # RDF::CLI::Option.new(
61
61
  # symbol: :canonicalize,
62
62
  # on: ["--canonicalize"],
63
- # description: "Canonicalize input/output.") {true},
63
+ # description: "Canonicalize URI/literal forms.") {true},
64
64
  # RDF::CLI::Option.new(
65
65
  # symbol: :uri,
66
66
  # on: ["--uri STRING"],
@@ -83,7 +83,8 @@ module RDF
83
83
  # * `:literal_equality' preserves [term-equality](https://www.w3.org/TR/rdf11-concepts/#dfn-literal-term-equality) for literals. Literals are equal only if their lexical values and datatypes are equal, character by character. Literals may be "inlined" to value-space for efficiency only if `:literal_equality` is `false`.
84
84
  # * `:validity` allows a concrete Enumerable implementation to indicate that it does or does not support valididty checking. By default implementations are assumed to support validity checking.
85
85
  # * `:skolemize` supports [Skolemization](https://www.w3.org/wiki/BnodeSkolemization) of an `Enumerable`. Implementations supporting this feature must implement a `#skolemize` method, taking a base URI used for minting URIs for BNodes as stable identifiers and a `#deskolemize` method, also taking a base URI used for turning URIs having that prefix back into the same BNodes which were originally skolemized.
86
- # * `:rdfstar` supports RDF* where statements may be subjects or objects of other statements.
86
+ # * `:quoted_triples` supports RDF 1.2 quoted triples.
87
+ # * `:base_direction` supports RDF 1.2 directional language-tagged strings.
87
88
  #
88
89
  # @param [Symbol, #to_sym] feature
89
90
  # @return [Boolean]
@@ -720,13 +721,33 @@ module RDF
720
721
  end
721
722
  alias_method :enum_graphs, :enum_graph
722
723
 
724
+ ##
725
+ # Enumerates each statement using its canonical representation.
726
+ #
727
+ # @note This is updated by `RDF::Normalize` to also canonicalize blank nodes.
728
+ #
729
+ # @return [RDF::Enumerable]
730
+ def canonicalize
731
+ this = self
732
+ Enumerable::Enumerator.new do |yielder|
733
+ this.send(:each_statement) {|y| yielder << y.canonicalize}
734
+ end
735
+ end
736
+
737
+ ##
738
+ # Mutating canonicalization not supported
739
+ #
740
+ # @raise NotImplementedError
741
+ def canonicalize!
742
+ raise NotImplementedError, "Canonicalizing enumerables not supported"
743
+ end
744
+
723
745
  ##
724
746
  # Returns all RDF statements in `self` as an array.
725
747
  #
726
748
  # Mixes in `RDF::Enumerable` into the returned object.
727
749
  #
728
750
  # @return [Array]
729
- # @since 0.2.0
730
751
  def to_a
731
752
  super.extend(RDF::Enumerable)
732
753
  end
@@ -140,7 +140,7 @@ module RDF
140
140
  # method in order to provide for storage-specific optimized triple
141
141
  # pattern matching.
142
142
  #
143
- # ## RDFStar (RDF*)
143
+ # ## RDF-star
144
144
  #
145
145
  # Statements may have embedded statements as either a subject or object, recursively.
146
146
  #
@@ -127,8 +127,11 @@ module RDF
127
127
  def insert_statements(statements)
128
128
  each = statements.respond_to?(:each_statement) ? :each_statement : :each
129
129
  statements.__send__(each) do |statement|
130
- if statement.embedded? && respond_to?(:supports?) && !supports?(:rdfstar)
131
- raise ArgumentError, "Wriable does not support embedded statements"
130
+ if statement.embedded? && respond_to?(:supports?) && !supports?(:quoted_triples)
131
+ raise ArgumentError, "Writable does not support quoted triples"
132
+ end
133
+ if statement.object && statement.object.literal? && statement.object.direction? && !supports?(:base_direction)
134
+ raise ArgumentError, "Writable does not support directional languaged-tagged strings"
132
135
  end
133
136
  insert_statement(statement)
134
137
  end
@@ -104,7 +104,7 @@ module RDF
104
104
  # @private
105
105
  # @see RDF::Enumerable#supports?
106
106
  def supports?(feature)
107
- return true if %i(graph_name rdfstar).include?(feature)
107
+ return true if %i(graph_name quoted_triples).include?(feature)
108
108
  super
109
109
  end
110
110
 
@@ -305,8 +305,11 @@ module RDF
305
305
  # @private
306
306
  # @see RDF::Mutable#insert
307
307
  def insert_statement(statement)
308
- if statement.embedded? && !@data.supports?(:rdfstar)
309
- raise ArgumentError, "Graph does not support embedded statements"
308
+ if statement.embedded? && !@data.supports?(:quoted_triples)
309
+ raise ArgumentError, "Graph does not support quoted triples"
310
+ end
311
+ if statement.object && statement.object.literal? && statement.object.direction? && !@data.supports?(:base_direction)
312
+ raise ArgumentError, "Graph does not support directional languaged-tagged strings"
310
313
  end
311
314
  statement = statement.dup
312
315
  statement.graph_name = graph_name
@@ -280,19 +280,6 @@ module RDF
280
280
  end
281
281
  end
282
282
 
283
- ##
284
- # Returns the element at `index`.
285
- #
286
- # @example
287
- # RDF::List[1, 2, 3][0] #=> RDF::Literal(1)
288
- #
289
- # @param [Integer] index
290
- # @return [RDF::Term]
291
- # @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-5B-5D
292
- def [](index)
293
- at(index)
294
- end
295
-
296
283
  ##
297
284
  # Element Assignment — Sets the element at `index`, or replaces a subarray from the `start` index for `length` elements, or replaces a subarray specified by the `range` of indices.
298
285
  #
@@ -26,7 +26,7 @@ module RDF; class Literal
26
26
  when value.is_a?(::Numeric) then BigDecimal(value)
27
27
  else
28
28
  value = value.to_s
29
- value += "0" if value.end_with?(".") # Normalization required in Ruby 2.4
29
+ value += "0" if value.end_with?(".")
30
30
  BigDecimal(value) rescue BigDecimal(0)
31
31
  end
32
32
  end
@@ -1,4 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
+
3
+ require 'bcp47_spec'
4
+
2
5
  module RDF
3
6
  ##
4
7
  # An RDF literal.
@@ -9,7 +12,9 @@ module RDF
9
12
  #
10
13
  # 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.
11
14
  #
12
- # 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.
15
+ # 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.
16
+ #
17
+ # 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.
13
18
  #
14
19
  # * {RDF::Literal::Boolean}
15
20
  # * {RDF::Literal::Date}
@@ -23,16 +28,23 @@ module RDF
23
28
  # value = RDF::Literal.new("Hello, world!")
24
29
  # value.plain? #=> true`
25
30
  #
26
- # @example Creating a language-tagged literal (1)
31
+ # @example Creating a language-tagged string (1)
27
32
  # value = RDF::Literal.new("Hello!", language: :en)
28
33
  # value.language? #=> true
29
34
  # value.language #=> :en
30
35
  #
31
- # @example Creating a language-tagged literal (2)
36
+ # @example Creating a language-tagged string (2)
32
37
  # RDF::Literal.new("Wazup?", language: :"en-US")
33
38
  # RDF::Literal.new("Hej!", language: :sv)
34
39
  # RDF::Literal.new("¡Hola!", language: :es)
35
40
  #
41
+ # @example Creating a directional language-tagged string
42
+ # value = RDF::Literal.new("Hello!", language: :en, direction: :ltr)
43
+ # value.language? #=> true
44
+ # value.language #=> :en
45
+ # value.direction? #=> true
46
+ # value.direction #=> :ltr
47
+ #
36
48
  # @example Creating an explicitly datatyped literal
37
49
  # value = RDF::Literal.new("2009-12-31", datatype: RDF::XSD.date)
38
50
  # value.datatype? #=> true
@@ -105,8 +117,14 @@ module RDF
105
117
 
106
118
  ##
107
119
  # @private
108
- def self.new(value, language: nil, datatype: nil, lexical: nil, validate: false, canonicalize: false, **options)
109
- raise ArgumentError, "datatype with language must be rdf:langString" if language && (datatype || RDF.langString).to_s != RDF.langString.to_s
120
+ def self.new(value, language: nil, datatype: nil, direction: nil, lexical: nil, validate: false, canonicalize: false, **options)
121
+ if language && direction
122
+ raise ArgumentError, "datatype with language and direction must be rdf:dirLangString" if (datatype || RDF.dirLangString).to_s != RDF.dirLangString.to_s
123
+ elsif language
124
+ raise ArgumentError, "datatype with language must be rdf:langString" if (datatype || RDF.langString).to_s != RDF.langString.to_s
125
+ else
126
+ raise ArgumentError, "datatype not compatible with language or direction" if language || direction
127
+ end
110
128
 
111
129
  klass = case
112
130
  when !self.equal?(RDF::Literal)
@@ -128,7 +146,7 @@ module RDF
128
146
  end
129
147
  end
130
148
  literal = klass.allocate
131
- literal.send(:initialize, value, language: language, datatype: datatype, **options)
149
+ literal.send(:initialize, value, language: language, datatype: datatype, direction: direction, **options)
132
150
  literal.validate! if validate
133
151
  literal.canonicalize! if canonicalize
134
152
  literal
@@ -137,18 +155,24 @@ module RDF
137
155
  TRUE = RDF::Literal.new(true)
138
156
  FALSE = RDF::Literal.new(false)
139
157
  ZERO = RDF::Literal.new(0)
158
+ XSD_STRING = RDF::URI("http://www.w3.org/2001/XMLSchema#string")
140
159
 
141
- # @return [Symbol] The language tag (optional).
160
+ # @return [Symbol] The language-tag (optional). Implies `datatype` is `rdf:langString`.
142
161
  attr_accessor :language
143
162
 
163
+ # @return [Symbol] The base direction (optional). Implies `datatype` is `rdf:dirLangString`.
164
+ attr_accessor :direction
165
+
144
166
  # @return [URI] The XML Schema datatype URI (optional).
145
167
  attr_accessor :datatype
146
168
 
147
169
  ##
148
- # Literals without a datatype are given either xsd:string or rdf:langString
149
- # depending on if there is language
170
+ # Literals without a datatype are given either `xsd:string`, `rdf:langString`, or `rdf:dirLangString`,
171
+ # depending on if there is `language` and/or `direction`.
150
172
  #
151
173
  # @param [Object] value
174
+ # @param [Symbol] direction (nil)
175
+ # Initial text direction.
152
176
  # @param [Symbol] language (nil)
153
177
  # Language is downcased to ensure proper matching
154
178
  # @param [String] lexical (nil)
@@ -163,16 +187,24 @@ module RDF
163
187
  # @see http://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal
164
188
  # @see http://www.w3.org/TR/rdf11-concepts/#section-Datatypes
165
189
  # @see #to_s
166
- def initialize(value, language: nil, datatype: nil, lexical: nil, validate: false, canonicalize: false, **options)
190
+ def initialize(value, language: nil, datatype: nil, direction: nil, lexical: nil, validate: false, canonicalize: false, **options)
167
191
  @object = value.freeze
168
192
  @string = lexical if lexical
169
193
  @string = value if !defined?(@string) && value.is_a?(String)
170
194
  @string = @string.encode(Encoding::UTF_8).freeze if instance_variable_defined?(:@string)
171
195
  @object = @string if instance_variable_defined?(:@string) && @object.is_a?(String)
172
196
  @language = language.to_s.downcase.to_sym if language
197
+ @direction = direction.to_s.downcase.to_sym if direction
173
198
  @datatype = RDF::URI(datatype).freeze if datatype
174
199
  @datatype ||= self.class.const_get(:DATATYPE) if self.class.const_defined?(:DATATYPE)
175
- @datatype ||= instance_variable_defined?(:@language) && @language ? RDF.langString : RDF::URI("http://www.w3.org/2001/XMLSchema#string")
200
+ @datatype ||= if instance_variable_defined?(:@language) && @language &&
201
+ instance_variable_defined?(:@direction) && @direction
202
+ RDF.dirLangString
203
+ elsif instance_variable_defined?(:@language) && @language
204
+ RDF.langString
205
+ else
206
+ XSD_STRING
207
+ end
176
208
  end
177
209
 
178
210
  ##
@@ -202,8 +234,8 @@ module RDF
202
234
  #
203
235
  # Compatibility of two arguments is defined as:
204
236
  # * The arguments are simple literals or literals typed as xsd:string
205
- # * The arguments are plain literals with identical language tags
206
- # * The first argument is a plain literal with language tag and the second argument is a simple literal or literal typed as xsd:string
237
+ # * The arguments are plain literals with identical language-tags and directions
238
+ # * The first argument is a plain literal with language-tag and the second argument is a simple literal or literal typed as xsd:string
207
239
  #
208
240
  # @example
209
241
  # compatible?("abc" "b") #=> true
@@ -224,11 +256,11 @@ module RDF
224
256
  return false unless other.literal? && plain? && other.plain?
225
257
 
226
258
  # * The arguments are simple literals or literals typed as xsd:string
227
- # * The arguments are plain literals with identical language tags
228
- # * The first argument is a plain literal with language tag and the second argument is a simple literal or literal typed as xsd:string
229
- language? ?
230
- (language == other.language || other.datatype == RDF::URI("http://www.w3.org/2001/XMLSchema#string")) :
231
- other.datatype == RDF::URI("http://www.w3.org/2001/XMLSchema#string")
259
+ # * The arguments are plain literals with identical language-tags
260
+ # * The first argument is a plain literal with language-tag and the second argument is a simple literal or literal typed as xsd:string
261
+ language? || direction? ?
262
+ (language == other.language && direction == other.direction || other.datatype == XSD_STRING) :
263
+ other.datatype == XSD_STRING
232
264
  end
233
265
 
234
266
  ##
@@ -236,7 +268,7 @@ module RDF
236
268
  #
237
269
  # @return [Integer]
238
270
  def hash
239
- @hash ||= [to_s, datatype, language].hash
271
+ @hash ||= [to_s, datatype, language, direction].compact.hash
240
272
  end
241
273
 
242
274
 
@@ -270,6 +302,7 @@ module RDF
270
302
  self.value_hash == other.value_hash &&
271
303
  self.value.eql?(other.value) &&
272
304
  self.language.to_s.eql?(other.language.to_s) &&
305
+ self.direction.to_s.eql?(other.direction.to_s) &&
273
306
  self.datatype.eql?(other.datatype))
274
307
  end
275
308
 
@@ -290,7 +323,10 @@ module RDF
290
323
  case
291
324
  when self.eql?(other)
292
325
  true
293
- when self.language? && self.language.to_s == other.language.to_s
326
+ when self.direction? && self.direction == other.direction
327
+ # Literals with directions can compare if languages and directions are identical
328
+ self.value_hash == other.value_hash && self.value == other.value
329
+ when self.language? && self.language == other.language
294
330
  # Literals with languages can compare if languages are identical
295
331
  self.value_hash == other.value_hash && self.value == other.value
296
332
  when self.simple? && other.simple?
@@ -342,14 +378,18 @@ module RDF
342
378
 
343
379
  ##
344
380
  # Returns `true` if this is a plain literal. A plain literal
345
- # may have a language, but may not have a datatype. For
381
+ # may have a language and direction, but may not have a datatype. For
346
382
  # all practical purposes, this includes xsd:string literals
347
383
  # too.
348
384
  #
349
385
  # @return [Boolean] `true` or `false`
350
386
  # @see http://www.w3.org/TR/rdf-concepts/#dfn-plain-literal
351
387
  def plain?
352
- [RDF.langString, RDF::URI("http://www.w3.org/2001/XMLSchema#string")].include?(datatype)
388
+ [
389
+ RDF.langString,
390
+ RDF.dirLangString,
391
+ XSD_STRING
392
+ ].include?(datatype)
353
393
  end
354
394
 
355
395
  ##
@@ -359,19 +399,28 @@ module RDF
359
399
  # @return [Boolean] `true` or `false`
360
400
  # @see http://www.w3.org/TR/sparql11-query/#simple_literal
361
401
  def simple?
362
- datatype == RDF::URI("http://www.w3.org/2001/XMLSchema#string")
402
+ datatype == XSD_STRING
363
403
  end
364
404
 
365
405
  ##
366
- # Returns `true` if this is a language-tagged literal.
406
+ # Returns `true` if this is a language-tagged string.
367
407
  #
368
408
  # @return [Boolean] `true` or `false`
369
- # @see http://www.w3.org/TR/rdf-concepts/#dfn-plain-literal
409
+ # @see https://www.w3.org/TR/rdf-concepts/#dfn-language-tagged-string
370
410
  def language?
371
- datatype == RDF.langString
411
+ [RDF.langString, RDF.dirLangString].include?(datatype)
372
412
  end
373
413
  alias_method :has_language?, :language?
374
414
 
415
+ ##
416
+ # Returns `true` if this is a directional language-tagged string.
417
+ #
418
+ # @return [Boolean] `true` or `false`
419
+ # @see https://www.w3.org/TR/rdf-concepts/#dfn-dir-lang-string
420
+ def direction?
421
+ datatype == RDF.dirLangString
422
+ end
423
+
375
424
  ##
376
425
  # Returns `true` if this is a datatyped literal.
377
426
  #
@@ -380,7 +429,7 @@ module RDF
380
429
  # @return [Boolean] `true` or `false`
381
430
  # @see http://www.w3.org/TR/rdf-concepts/#dfn-typed-literal
382
431
  def datatype?
383
- !plain? && !language?
432
+ !plain? && !language? && !direction?
384
433
  end
385
434
  alias_method :has_datatype?, :datatype?
386
435
  alias_method :typed?, :datatype?
@@ -393,10 +442,13 @@ module RDF
393
442
  # @return [Boolean] `true` or `false`
394
443
  # @since 0.2.1
395
444
  def valid?
396
- return false if language? && language.to_s !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/
445
+ BCP47.parse(language.to_s) if language?
446
+ return false if direction? && !%i{ltr rtl}.include?(direction)
397
447
  return false if datatype? && datatype.invalid?
398
448
  grammar = self.class.const_get(:GRAMMAR) rescue nil
399
449
  grammar.nil? || value.match?(grammar)
450
+ rescue BCP47::InvalidLanguageTag
451
+ false
400
452
  end
401
453
 
402
454
  ##
@@ -536,12 +588,12 @@ module RDF
536
588
 
537
589
  ##
538
590
  # @overload #to_str
539
- # This method is implemented when the datatype is `xsd:string` or `rdf:langString`
591
+ # This method is implemented when the datatype is `xsd:string`, `rdf:langString`, or `rdf:dirLangString`
540
592
  # @return [String]
541
593
  def method_missing(name, *args)
542
594
  case name
543
595
  when :to_str
544
- return to_s if @datatype == RDF.langString || @datatype == RDF::URI("http://www.w3.org/2001/XMLSchema#string")
596
+ return to_s if [RDF.langString, RDF.dirLangString, XSD_STRING].include?(@datatype)
545
597
  end
546
598
  super
547
599
  end
@@ -549,7 +601,7 @@ module RDF
549
601
  def respond_to_missing?(name, include_private = false)
550
602
  case name
551
603
  when :to_str
552
- return true if @datatype == RDF.langString || @datatype == RDF::URI("http://www.w3.org/2001/XMLSchema#string")
604
+ return true if [RDF.langString, RDF.dirLangString, XSD_STRING].include?(@datatype)
553
605
  end
554
606
  super
555
607
  end
@@ -182,6 +182,7 @@ module RDF
182
182
  ##
183
183
  # Returns `true` if any element of the statement is, itself, a statement.
184
184
  #
185
+ # Note: Nomenclature is evolving, alternatives could include `#complex?` and `#nested?`
185
186
  # @return [Boolean]
186
187
  def embedded?
187
188
  subject && subject.statement? || object && object.statement?
@@ -410,7 +411,7 @@ module RDF
410
411
  end
411
412
 
412
413
  ##
413
- # Canonicalizes each unfrozen term in the statement
414
+ # Canonicalizes each unfrozen term in the statement.
414
415
  #
415
416
  # @return [RDF::Statement] `self`
416
417
  # @since 1.0.8
@@ -436,6 +437,18 @@ module RDF
436
437
  nil
437
438
  end
438
439
 
440
+ # New statement with duplicated components (other than blank nodes)
441
+ #
442
+ # @return [RDF::Statement]
443
+ def dup
444
+ options = Hash[@options]
445
+ options[:subject] = subject.is_a?(RDF::Node) ? subject : subject.dup
446
+ options[:predicate] = predicate.dup
447
+ options[:object] = object.is_a?(RDF::Node) ? object : object.dup
448
+ options[:graph_name] = graph_name.is_a?(RDF::Node) ? graph_name : graph_name.dup if graph_name
449
+ RDF::Statement.new(options)
450
+ end
451
+
439
452
  ##
440
453
  # Returns the terms of this statement as a `Hash`.
441
454
  #