rdf 3.2.10 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
  #