rdf 1.99.1 → 2.0.0.beta1

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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/{README → README.md} +9 -44
  3. data/VERSION +1 -1
  4. data/bin/rdf +1 -1
  5. data/lib/rdf.rb +40 -49
  6. data/lib/rdf/changeset.rb +161 -0
  7. data/lib/rdf/cli.rb +195 -33
  8. data/lib/rdf/cli/vocab-loader.rb +13 -3
  9. data/lib/rdf/format.rb +44 -26
  10. data/lib/rdf/mixin/enumerable.rb +133 -97
  11. data/lib/rdf/mixin/enumerator.rb +8 -0
  12. data/lib/rdf/mixin/indexable.rb +1 -1
  13. data/lib/rdf/mixin/mutable.rb +101 -22
  14. data/lib/rdf/mixin/queryable.rb +21 -32
  15. data/lib/rdf/mixin/transactable.rb +94 -0
  16. data/lib/rdf/mixin/writable.rb +12 -3
  17. data/lib/rdf/model/dataset.rb +48 -0
  18. data/lib/rdf/model/graph.rb +73 -43
  19. data/lib/rdf/model/list.rb +61 -33
  20. data/lib/rdf/model/literal.rb +20 -19
  21. data/lib/rdf/model/literal/double.rb +20 -4
  22. data/lib/rdf/model/literal/numeric.rb +15 -13
  23. data/lib/rdf/model/node.rb +15 -16
  24. data/lib/rdf/model/statement.rb +1 -43
  25. data/lib/rdf/model/term.rb +10 -8
  26. data/lib/rdf/model/uri.rb +35 -34
  27. data/lib/rdf/model/value.rb +1 -1
  28. data/lib/rdf/nquads.rb +2 -11
  29. data/lib/rdf/ntriples.rb +1 -1
  30. data/lib/rdf/ntriples/reader.rb +33 -46
  31. data/lib/rdf/ntriples/writer.rb +42 -5
  32. data/lib/rdf/query.rb +6 -40
  33. data/lib/rdf/query/pattern.rb +4 -17
  34. data/lib/rdf/query/solutions.rb +6 -6
  35. data/lib/rdf/reader.rb +65 -14
  36. data/lib/rdf/repository.rb +365 -229
  37. data/lib/rdf/transaction.rb +211 -84
  38. data/lib/rdf/util.rb +1 -0
  39. data/lib/rdf/util/cache.rb +5 -5
  40. data/lib/rdf/util/file.rb +12 -9
  41. data/lib/rdf/util/logger.rb +272 -0
  42. data/lib/rdf/version.rb +2 -2
  43. data/lib/rdf/vocab/owl.rb +82 -77
  44. data/lib/rdf/vocab/rdfs.rb +22 -17
  45. data/lib/rdf/vocab/xsd.rb +5 -0
  46. data/lib/rdf/vocabulary.rb +50 -56
  47. data/lib/rdf/writer.rb +104 -52
  48. metadata +45 -90
  49. data/lib/rdf/mixin/inferable.rb +0 -5
  50. data/lib/rdf/vocab/cc.rb +0 -128
  51. data/lib/rdf/vocab/cert.rb +0 -245
  52. data/lib/rdf/vocab/dc.rb +0 -948
  53. data/lib/rdf/vocab/dc11.rb +0 -167
  54. data/lib/rdf/vocab/dcat.rb +0 -214
  55. data/lib/rdf/vocab/doap.rb +0 -337
  56. data/lib/rdf/vocab/exif.rb +0 -941
  57. data/lib/rdf/vocab/foaf.rb +0 -614
  58. data/lib/rdf/vocab/geo.rb +0 -157
  59. data/lib/rdf/vocab/gr.rb +0 -1501
  60. data/lib/rdf/vocab/ht.rb +0 -236
  61. data/lib/rdf/vocab/ical.rb +0 -528
  62. data/lib/rdf/vocab/ma.rb +0 -513
  63. data/lib/rdf/vocab/mo.rb +0 -2412
  64. data/lib/rdf/vocab/og.rb +0 -222
  65. data/lib/rdf/vocab/ogc.rb +0 -58
  66. data/lib/rdf/vocab/prov.rb +0 -1550
  67. data/lib/rdf/vocab/rsa.rb +0 -72
  68. data/lib/rdf/vocab/rss.rb +0 -66
  69. data/lib/rdf/vocab/schema.rb +0 -10569
  70. data/lib/rdf/vocab/sioc.rb +0 -669
  71. data/lib/rdf/vocab/skos.rb +0 -238
  72. data/lib/rdf/vocab/skosxl.rb +0 -57
  73. data/lib/rdf/vocab/v.rb +0 -383
  74. data/lib/rdf/vocab/vcard.rb +0 -841
  75. data/lib/rdf/vocab/vmd.rb +0 -383
  76. data/lib/rdf/vocab/void.rb +0 -186
  77. data/lib/rdf/vocab/vs.rb +0 -28
  78. data/lib/rdf/vocab/wdrs.rb +0 -134
  79. data/lib/rdf/vocab/wot.rb +0 -167
  80. data/lib/rdf/vocab/xhtml.rb +0 -8
  81. data/lib/rdf/vocab/xhv.rb +0 -505
@@ -1,45 +1,50 @@
1
1
  # -*- encoding: utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  # This file generated automatically using vocab-fetch from http://www.w3.org/2000/01/rdf-schema#
3
4
  require 'rdf'
4
5
  module RDF
6
+ # @!parse
7
+ # # Vocabulary for <http://www.w3.org/2000/01/rdf-schema#>
8
+ # class RDFS < RDF::StrictVocabulary
9
+ # end
5
10
  class RDFS < RDF::StrictVocabulary("http://www.w3.org/2000/01/rdf-schema#")
6
11
 
7
12
  # Class definitions
8
13
  term :Class,
9
14
  comment: %(The class of classes.).freeze,
10
15
  label: "Class".freeze,
11
- "rdfs:isDefinedBy" => %(rdfs:).freeze,
16
+ :"rdfs:isDefinedBy" => %(rdfs:).freeze,
12
17
  subClassOf: "rdfs:Resource".freeze,
13
18
  type: "rdfs:Class".freeze
14
19
  term :Container,
15
20
  comment: %(The class of RDF containers.).freeze,
16
21
  label: "Container".freeze,
17
- "rdfs:isDefinedBy" => %(rdfs:).freeze,
22
+ :"rdfs:isDefinedBy" => %(rdfs:).freeze,
18
23
  subClassOf: "rdfs:Resource".freeze,
19
24
  type: "rdfs:Class".freeze
20
25
  term :ContainerMembershipProperty,
21
26
  comment: %(The class of container membership properties, rdf:_1, rdf:_2, ...,
22
27
  all of which are sub-properties of 'member'.).freeze,
23
28
  label: "ContainerMembershipProperty".freeze,
24
- "rdfs:isDefinedBy" => %(rdfs:).freeze,
29
+ :"rdfs:isDefinedBy" => %(rdfs:).freeze,
25
30
  subClassOf: "rdf:Property".freeze,
26
31
  type: "rdfs:Class".freeze
27
32
  term :Datatype,
28
33
  comment: %(The class of RDF datatypes.).freeze,
29
34
  label: "Datatype".freeze,
30
- "rdfs:isDefinedBy" => %(rdfs:).freeze,
35
+ :"rdfs:isDefinedBy" => %(rdfs:).freeze,
31
36
  subClassOf: "rdfs:Class".freeze,
32
37
  type: "rdfs:Class".freeze
33
38
  term :Literal,
34
39
  comment: %(The class of literal values, eg. textual strings and integers.).freeze,
35
40
  label: "Literal".freeze,
36
- "rdfs:isDefinedBy" => %(rdfs:).freeze,
41
+ :"rdfs:isDefinedBy" => %(rdfs:).freeze,
37
42
  subClassOf: "rdfs:Resource".freeze,
38
43
  type: "rdfs:Class".freeze
39
44
  term :Resource,
40
45
  comment: %(The class resource, everything.).freeze,
41
46
  label: "Resource".freeze,
42
- "rdfs:isDefinedBy" => %(rdfs:).freeze,
47
+ :"rdfs:isDefinedBy" => %(rdfs:).freeze,
43
48
  type: "rdfs:Class".freeze
44
49
 
45
50
  # Property definitions
@@ -48,21 +53,21 @@ module RDF
48
53
  domain: "rdfs:Resource".freeze,
49
54
  label: "comment".freeze,
50
55
  range: "rdfs:Literal".freeze,
51
- "rdfs:isDefinedBy" => %(rdfs:).freeze,
56
+ :"rdfs:isDefinedBy" => %(rdfs:).freeze,
52
57
  type: "rdf:Property".freeze
53
58
  property :domain,
54
59
  comment: %(A domain of the subject property.).freeze,
55
60
  domain: "rdf:Property".freeze,
56
61
  label: "domain".freeze,
57
62
  range: "rdfs:Class".freeze,
58
- "rdfs:isDefinedBy" => %(rdfs:).freeze,
63
+ :"rdfs:isDefinedBy" => %(rdfs:).freeze,
59
64
  type: "rdf:Property".freeze
60
65
  property :isDefinedBy,
61
66
  comment: %(The defininition of the subject resource.).freeze,
62
67
  domain: "rdfs:Resource".freeze,
63
68
  label: "isDefinedBy".freeze,
64
69
  range: "rdfs:Resource".freeze,
65
- "rdfs:isDefinedBy" => %(rdfs:).freeze,
70
+ :"rdfs:isDefinedBy" => %(rdfs:).freeze,
66
71
  subPropertyOf: "rdfs:seeAlso".freeze,
67
72
  type: "rdf:Property".freeze
68
73
  property :label,
@@ -70,49 +75,49 @@ module RDF
70
75
  domain: "rdfs:Resource".freeze,
71
76
  label: "label".freeze,
72
77
  range: "rdfs:Literal".freeze,
73
- "rdfs:isDefinedBy" => %(rdfs:).freeze,
78
+ :"rdfs:isDefinedBy" => %(rdfs:).freeze,
74
79
  type: "rdf:Property".freeze
75
80
  property :member,
76
81
  comment: %(A member of the subject resource.).freeze,
77
82
  domain: "rdfs:Resource".freeze,
78
83
  label: "member".freeze,
79
84
  range: "rdfs:Resource".freeze,
80
- "rdfs:isDefinedBy" => %(rdfs:).freeze,
85
+ :"rdfs:isDefinedBy" => %(rdfs:).freeze,
81
86
  type: "rdf:Property".freeze
82
87
  property :range,
83
88
  comment: %(A range of the subject property.).freeze,
84
89
  domain: "rdf:Property".freeze,
85
90
  label: "range".freeze,
86
91
  range: "rdfs:Class".freeze,
87
- "rdfs:isDefinedBy" => %(rdfs:).freeze,
92
+ :"rdfs:isDefinedBy" => %(rdfs:).freeze,
88
93
  type: "rdf:Property".freeze
89
94
  property :seeAlso,
90
95
  comment: %(Further information about the subject resource.).freeze,
91
96
  domain: "rdfs:Resource".freeze,
92
97
  label: "seeAlso".freeze,
93
98
  range: "rdfs:Resource".freeze,
94
- "rdfs:isDefinedBy" => %(rdfs:).freeze,
99
+ :"rdfs:isDefinedBy" => %(rdfs:).freeze,
95
100
  type: "rdf:Property".freeze
96
101
  property :subClassOf,
97
102
  comment: %(The subject is a subclass of a class.).freeze,
98
103
  domain: "rdfs:Class".freeze,
99
104
  label: "subClassOf".freeze,
100
105
  range: "rdfs:Class".freeze,
101
- "rdfs:isDefinedBy" => %(rdfs:).freeze,
106
+ :"rdfs:isDefinedBy" => %(rdfs:).freeze,
102
107
  type: "rdf:Property".freeze
103
108
  property :subPropertyOf,
104
109
  comment: %(The subject is a subproperty of a property.).freeze,
105
110
  domain: "rdf:Property".freeze,
106
111
  label: "subPropertyOf".freeze,
107
112
  range: "rdf:Property".freeze,
108
- "rdfs:isDefinedBy" => %(rdfs:).freeze,
113
+ :"rdfs:isDefinedBy" => %(rdfs:).freeze,
109
114
  type: "rdf:Property".freeze
110
115
 
111
116
  # Extra definitions
112
117
  term :"",
113
- "dc11:title" => %(The RDF Schema vocabulary \(RDFS\)).freeze,
118
+ :"dc11:title" => %(The RDF Schema vocabulary \(RDFS\)).freeze,
114
119
  label: "".freeze,
115
- "rdfs:seeAlso" => %(http://www.w3.org/2000/01/rdf-schema-more).freeze,
120
+ :"rdfs:seeAlso" => %(http://www.w3.org/2000/01/rdf-schema-more).freeze,
116
121
  type: "owl:Ontology".freeze
117
122
  end
118
123
  end
@@ -1,7 +1,12 @@
1
1
  # -*- encoding: utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  # This file generated automatically using vocab-fetch from etc/xsd.ttl
3
4
  require 'rdf'
4
5
  module RDF
6
+ # @!parse
7
+ # # Vocabulary for <http://www.w3.org/2001/XMLSchema#>
8
+ # class XSD < RDF::Vocabulary
9
+ # end
5
10
  class XSD < RDF::Vocabulary("http://www.w3.org/2001/XMLSchema#")
6
11
 
7
12
  # Datatype definitions
@@ -201,7 +201,7 @@ module RDF
201
201
  # @return [Array<RDF::Vocabulary>]
202
202
  def imports
203
203
  @imports ||= begin
204
- Array(self[""].attributes["owl:imports"]).map {|pn|find(expand_pname(pn)) rescue nil}.compact
204
+ Array(self[""].attributes[:"owl:imports"]).map {|pn|find(expand_pname(pn)) rescue nil}.compact
205
205
  rescue KeyError
206
206
  []
207
207
  end
@@ -217,22 +217,6 @@ module RDF
217
217
  end
218
218
  end
219
219
 
220
- ##
221
- # @return [String] The label for the named property
222
- # @deprecated Use {RDF::Vocabulary::Term#label} instead.
223
- def label_for(name)
224
- warn "[DEPRECATION] `Vocabulary.label_for is deprecated. Please use Vocabulary::Term#label instead. Called from #{Gem.location_of_caller.join(':')}"
225
- self[name].label || ''
226
- end
227
-
228
- ##
229
- # @return [String] The comment for the named property
230
- # @deprecated Use {RDF::Vocabulary::Term#comment} instead.
231
- def comment_for(name)
232
- warn "[DEPRECATION] `Vocabulary.comment_for is deprecated. Please use Vocabulary::Term#comment instead. Called from #{Gem.location_of_caller.join(':')}"
233
- self[name].comment || ''
234
- end
235
-
236
220
  ##
237
221
  # Returns the base URI for this vocabulary class.
238
222
  #
@@ -256,6 +240,7 @@ module RDF
256
240
  end
257
241
  end
258
242
  alias_method :to_enum, :enum_for
243
+
259
244
  ##
260
245
  # Enumerate each statement constructed from the defined vocabulary terms
261
246
  #
@@ -280,42 +265,52 @@ module RDF
280
265
  ##
281
266
  # Load a vocabulary, optionally from a separate location.
282
267
  #
283
- # @param [URI, #to_s] uri
284
- # @param [Hash{Symbol => Object}] options
285
- # @option options [String] class_name
268
+ # @param [URI, #to_s] url
269
+ # @param [String] class_name
286
270
  # The class_name associated with the vocabulary, used for creating the class name of the vocabulary. This will create a new class named with a top-level constant based on `class_name`.
287
- # @option options [URI, #to_s] :location
271
+ # @param [URI, #to_s] location
288
272
  # Location from which to load the vocabulary, if not from `uri`.
289
- # @option options [Array<Symbol>, Hash{Symbol => Hash}] :extra
273
+ # @param [Array<Symbol>, Hash{Symbol => Hash}] extra
290
274
  # Extra terms to add to the vocabulary. In the first form, it is an array of symbols, for which terms are created. In the second, it is a Hash mapping symbols to property attributes, as described in {RDF::Vocabulary.property}.
275
+ # @param [String] patch
276
+ # A patch to run on the graph after loading. Requires the `ld-patch` gem to be available.
291
277
  # @return [RDF::Vocabulary] the loaded vocabulary
292
- def load(uri, options = {})
293
- source = options.fetch(:location, uri)
294
- class_name = options[:class_name]
278
+ def load(url, class_name: nil, location: nil, extra: nil, patch: nil)
279
+ source = location || url
295
280
  vocab = if class_name
296
- Object.const_set(class_name, Class.new(self.create(uri)))
281
+ Object.const_set(class_name, Class.new(self.create(url)))
297
282
  else
298
- Class.new(self.create(uri))
283
+ Class.new(self.create(url))
299
284
  end
300
285
 
301
- graph = RDF::Graph.load(source)
286
+ graph = RDF::Repository.load(source)
287
+
288
+ if patch
289
+ begin
290
+ require 'ld/patch'
291
+ operator = LD::Patch.parse(patch)
292
+ graph.query(operator)
293
+ rescue LoadError
294
+ raise ArgumentError, "patching vocabulary requires the ld-patch gem"
295
+ end
296
+ end
302
297
  term_defs = {}
303
298
  graph.each do |statement|
304
- next unless statement.subject.uri? && statement.subject.start_with?(uri)
305
- name = statement.subject.to_s[uri.to_s.length..-1]
299
+ next unless statement.subject.uri? && statement.subject.start_with?(url)
300
+ name = statement.subject.to_s[url.to_s.length..-1]
306
301
  term = (term_defs[name.to_sym] ||= {})
307
302
  key = case statement.predicate
308
- when RDF.type then :type
309
- when RDF::RDFS.subClassOf then :subClassOf
310
- when RDF::RDFS.subPropertyOf then :subPropertyOf
311
- when RDF::RDFS.range then :range
312
- when RDF::RDFS.domain then :domain
313
- when RDF::RDFS.comment then :comment
314
- when RDF::RDFS.label then :label
315
- when RDF::SCHEMA.inverseOf then :inverseOf
316
- when RDF::SCHEMA.domainIncludes then :domainIncludes
317
- when RDF::SCHEMA.rangeIncludes then :rangeIncludes
318
- else statement.predicate.pname
303
+ when RDF.type then :type
304
+ when RDF::RDFS.subClassOf then :subClassOf
305
+ when RDF::RDFS.subPropertyOf then :subPropertyOf
306
+ when RDF::RDFS.range then :range
307
+ when RDF::RDFS.domain then :domain
308
+ when RDF::RDFS.comment then :comment
309
+ when RDF::RDFS.label then :label
310
+ when RDF::URI("http://schema.org/inverseOf") then :inverseOf
311
+ when RDF::URI("http://schema.org/domainIncludes") then :domainIncludes
312
+ when RDF::URI("http://schema.org/rangeIncludes") then :rangeIncludes
313
+ else statement.predicate.pname
319
314
  end
320
315
 
321
316
  value = if statement.object.uri?
@@ -328,11 +323,11 @@ module RDF
328
323
  end
329
324
 
330
325
  # Create extra terms
331
- term_defs = case options[:extra]
326
+ term_defs = case extra
332
327
  when Array
333
- options[:extra].inject({}) {|memo, s| memo[s.to_sym] = {label: s.to_s}; memo}.merge(term_defs)
328
+ extra.inject({}) {|memo, s| memo[s.to_sym] = {label: s.to_s}; memo}.merge(term_defs)
334
329
  when Hash
335
- options[:extra].merge(term_defs)
330
+ extra.merge(term_defs)
336
331
  else
337
332
  term_defs
338
333
  end
@@ -545,8 +540,7 @@ module RDF
545
540
  # @option options [String, #to_s] :fragment The fragment component.
546
541
  # @option options [Hash{Symbol,Resource => Term, #to_s}] :attributes
547
542
  # Attributes of this vocabulary term, used for finding `label` and `comment` and to serialize the term back to RDF
548
- def initialize(*args)
549
- options = args.last.is_a?(Hash) ? args.last : {}
543
+ def initialize(*args, **options)
550
544
  @attributes = options.fetch(:attributes)
551
545
  super
552
546
  end
@@ -612,8 +606,6 @@ module RDF
612
606
  # @yieldparam [RDF::Statement]
613
607
  def each_statement
614
608
  attributes.reject {|p| p == :vocab}.each do |prop, values|
615
- prop = RDF::Vocabulary.expand_pname(prop) unless prop.is_a?(Symbol)
616
- next unless prop
617
609
  Array(values).each do |value|
618
610
  begin
619
611
  case prop
@@ -633,21 +625,23 @@ module RDF
633
625
  prop = RDFS.range
634
626
  value = RDF::Vocabulary.expand_pname(value)
635
627
  when :inverseOf
636
- prop = RDF::SCHEMA.inverseOf
628
+ prop = RDF::URI("http://schema.org/inverseOf")
637
629
  value = RDF::Vocabulary.expand_pname(value)
638
630
  when :domainIncludes
639
- prop = RDF::SCHEMA.domainIncludes
631
+ prop = RDF::URI("http://schema.org/domainIncludes")
640
632
  value = RDF::Vocabulary.expand_pname(value)
641
633
  when :rangeIncludes
642
- prop = RDF::SCHEMA.rangeIncludes
634
+ prop = RDF::URI("http://schema.org/rangeIncludes")
643
635
  value = RDF::Vocabulary.expand_pname(value)
644
636
  when :label
645
637
  prop = RDFS.label
646
638
  when :comment
647
639
  prop = RDFS.comment
648
640
  else
649
- v = RDF::Vocabulary.expand_pname(value)
650
- value = v.valid? ? v : RDF::Literal(value)
641
+ prop = RDF::Vocabulary.expand_pname(prop.to_s)
642
+ next unless prop
643
+ v = RDF::Vocabulary.expand_pname(value.to_s)
644
+ value = v.valid? ? v : RDF::Literal(value.to_s)
651
645
  end
652
646
  yield RDF::Statement(self, prop, value)
653
647
  rescue KeyError
@@ -683,13 +677,13 @@ module RDF
683
677
  @attributes.has_key?(method) || super
684
678
  end
685
679
 
686
- # Accessor for `domainIncludes`
680
+ # Accessor for `schema:domainIncludes`
687
681
  # @return [RDF::URI]
688
682
  def domain_includes
689
683
  Array(@attributes[:domainIncludes]).map {|v| RDF::Vocabulary.expand_pname(v)}
690
684
  end
691
685
 
692
- # Accessor for `rangeIncludes`
686
+ # Accessor for `schema:rangeIncludes`
693
687
  # @return [RDF::URI]
694
688
  def range_includes
695
689
  Array(@attributes[:rangeIncludes]).map {|v| RDF::Vocabulary.expand_pname(v)}
@@ -735,7 +729,7 @@ module RDF
735
729
  # @raise [KeyError] if property not defined in vocabulary
736
730
  def [](name)
737
731
  props.fetch(name.to_sym)
738
- rescue KeyError => e
732
+ rescue KeyError
739
733
  raise KeyError, "#{name} not found in vocabulary #{self.__name__}"
740
734
  end
741
735
  end
@@ -33,12 +33,24 @@ module RDF
33
33
  # end
34
34
  # end
35
35
  #
36
+ # @example Detecting invalid output
37
+ # logger = Logger.new([])
38
+ # RDF::Writer.for(:ntriples).buffer(logger: logger) do |writer|
39
+ # statement = RDF::Statement.new(
40
+ # RDF::URI("http://rubygems.org/gems/rdf"),
41
+ # RDF::URI("http://purl.org/dc/terms/creator"),
42
+ # nil)
43
+ # writer << statement
44
+ # end # => RDF::WriterError
45
+ # logger.empty? => false
46
+ #
36
47
  # @abstract
37
48
  # @see RDF::Format
38
49
  # @see RDF::Reader
39
50
  class Writer
40
51
  extend ::Enumerable
41
52
  extend RDF::Util::Aliasing::LateBound
53
+ include RDF::Util::Logger
42
54
  include RDF::Writable
43
55
 
44
56
  ##
@@ -99,6 +111,40 @@ module RDF
99
111
  end
100
112
  end
101
113
 
114
+ ##
115
+ # Options suitable for automatic Writer provisioning.
116
+ # @return [Array<RDF::CLI::Option>]
117
+ def self.options
118
+ [
119
+ RDF::CLI::Option.new(
120
+ symbol: :canonicalize,
121
+ datatype: TrueClass,
122
+ on: ["--canonicalize"],
123
+ description: "Canonicalize input/output.") {true},
124
+ RDF::CLI::Option.new(
125
+ symbol: :encoding,
126
+ datatype: Encoding,
127
+ on: ["--encoding ENCODING"],
128
+ description: "The encoding of the input stream.") {|arg| Encoding.find arg},
129
+ RDF::CLI::Option.new(
130
+ symbol: :prefixes,
131
+ datatype: Hash,
132
+ multiple: true,
133
+ on: ["--prefixes PREFIX,PREFIX"],
134
+ description: "A comma-separated list of prefix:uri pairs.") do |arg|
135
+ arg.split(',').inject({}) do |memo, pfxuri|
136
+ pfx,uri = pfxuri.split(':', 2)
137
+ memo.merge(pfx.to_sym => RDF::URI(uri))
138
+ end
139
+ end,
140
+ RDF::CLI::Option.new(
141
+ symbol: :unique_bnodes,
142
+ datatype: TrueClass,
143
+ on: ["--unique-bnodes"],
144
+ description: "Use unique Node identifiers.") {true},
145
+ ]
146
+ end
147
+
102
148
  class << self
103
149
  alias_method :format_class, :format
104
150
  end
@@ -212,7 +258,7 @@ module RDF
212
258
  # @yieldreturn [void]
213
259
  def initialize(output = $stdout, options = {}, &block)
214
260
  @output, @options = output, options.dup
215
- @nodes, @node_id = {}, 0
261
+ @nodes, @node_id, @node_id_map = {}, 0, {}
216
262
 
217
263
  if block_given?
218
264
  write_prologue
@@ -329,7 +375,7 @@ module RDF
329
375
  ##
330
376
  # Flushes the underlying output buffer.
331
377
  #
332
- # @return [void] `self`
378
+ # @return [self]
333
379
  def flush
334
380
  @output.flush if @output.respond_to?(:flush)
335
381
  self
@@ -337,66 +383,82 @@ module RDF
337
383
  alias_method :flush!, :flush
338
384
 
339
385
  ##
340
- # @return [void] `self`
386
+ # @return [self]
341
387
  # @abstract
342
388
  def write_prologue
343
389
  self
344
390
  end
345
391
 
346
392
  ##
347
- # @return [void] `self`
393
+ # @return [self]
394
+ # @raise [RDF::WriterError] if errors logged during processing.
348
395
  # @abstract
349
396
  def write_epilogue
397
+ if log_statistics[:error]
398
+ raise RDF::WriterError, "Errors found during processing"
399
+ end
350
400
  self
351
401
  end
352
402
 
353
403
  ##
354
404
  # @param [String] text
355
- # @return [void] `self`
405
+ # @return [self]
356
406
  # @abstract
357
407
  def write_comment(text)
358
408
  self
359
409
  end
360
410
 
361
411
  ##
362
- # @param [RDF::Graph] graph
363
- # @return [void] `self`
364
- # @deprecated Use {RDF::Writable#insert_graph} instead .
365
- def write_graph(graph)
366
- warn "[DEPRECATION] `Writer#graph_write is deprecated. Please use RDF::Writable#insert instead. Called from #{Gem.location_of_caller.join(':')}"
367
- graph.each_triple { |*triple| write_triple(*triple) }
368
- self
369
- end
370
-
371
- ##
372
- # @param [Array<RDF::Statement>] statements
373
- # @return [void] `self`
374
- # @deprecated Use {RDF::Writable#insert} instead.
375
- def write_statements(*statements)
376
- warn "[DEPRECATION] `Writer#write_statements is deprecated. Please use RDF::Writable#insert instead. Called from #{Gem.location_of_caller.join(':')}"
377
- statements.each { |statement| write_statement(statement) }
378
- self
379
- end
380
-
381
- ##
412
+ # Add a statement to the writer. This will check to ensure that the statement is complete (no nil terms) and is valid, if the `:validation` option is set.
413
+ #
414
+ # Additionally, it will de-duplicate BNode terms sharing a common identifier.
415
+ #
382
416
  # @param [RDF::Statement] statement
383
- # @return [void] `self`
384
- # @raise [RDF::WriterError] if validating and attempting to write an invalid {RDF::Statement} or if canonicalizing a statement which cannot be canonicalized.
417
+ # @return [self]
418
+ # @note logs error if attempting to write an invalid {RDF::Statement} or if canonicalizing a statement which cannot be canonicalized.
385
419
  def write_statement(statement)
386
420
  statement = statement.canonicalize! if canonicalize?
387
- raise RDF::WriterError, "Statement #{statement.inspect} is incomplete" if statement.incomplete?
388
- raise RDF::WriterError, "Statement #{statement.inspect} is invalid" if validate? && statement.invalid?
389
- write_triple(*statement.to_triple)
421
+
422
+ # Make sure BNodes in statement use unique identifiers
423
+ if statement.node?
424
+ terms = statement.to_quad.map do |term|
425
+ if term.is_a?(RDF::Node)
426
+ term = term.original while term.original
427
+ @nodes[term] ||= begin
428
+ # Account for duplicated nodes
429
+ @node_id_map[term.to_s] ||= term
430
+ if !@node_id_map[term.to_s].equal?(term)
431
+ # Rename node
432
+ term.make_unique!
433
+ @node_id_map[term.to_s] = term
434
+ end
435
+ end
436
+ else
437
+ term
438
+ end
439
+ end
440
+ statement = RDF::Statement.from(statement.to_quad)
441
+ end
442
+
443
+ if statement.incomplete?
444
+ log_error "Statement #{statement.inspect} is incomplete"
445
+ elsif validate? && statement.invalid?
446
+ log_error "Statement #{statement.inspect} is invalid"
447
+ elsif respond_to?(:write_quad)
448
+ write_quad(*statement.to_quad)
449
+ else
450
+ write_triple(*statement.to_triple)
451
+ end
390
452
  self
391
453
  rescue ArgumentError => e
392
- raise WriterError, e.message
454
+ log_error e.message
393
455
  end
394
456
  alias_method :insert_statement, :write_statement # support the RDF::Writable interface
395
457
 
396
458
  ##
397
459
  # @param [Array<Array(RDF::Resource, RDF::URI, RDF::Term)>] triples
398
- # @return [void] `self`
399
- # @raise [RDF::WriterError] if validating and attempting to write an invalid {RDF::Term}.
460
+ # @return [self]
461
+ # @note logs error if attempting to write an invalid {RDF::Statement} or if canonicalizing a statement which cannot be canonicalized.
400
462
  def write_triples(*triples)
401
463
  triples.each { |triple| write_triple(*triple) }
402
464
  self
@@ -406,9 +468,9 @@ module RDF
406
468
  # @param [RDF::Resource] subject
407
469
  # @param [RDF::URI] predicate
408
470
  # @param [RDF::Term] object
409
- # @return [void] `self`
471
+ # @return [self]
410
472
  # @raise [NotImplementedError] unless implemented in subclass
411
- # @raise [RDF::WriterError] if validating and attempting to write an invalid {RDF::Term}.
473
+ # @note logs error if attempting to write an invalid {RDF::Statement} or if canonicalizing a statement which cannot be canonicalized.
412
474
  # @abstract
413
475
  def write_triple(subject, predicate, object)
414
476
  raise NotImplementedError.new("#{self.class}#write_triple") # override in subclasses
@@ -429,16 +491,6 @@ module RDF
429
491
  end
430
492
  end
431
493
 
432
- ##
433
- # @param [RDF::Term] term
434
- # @return [String]
435
- # @since 0.3.0
436
- # @deprecated Use {#format_term} instead
437
- def format_value(term, options = {})
438
- warn "[DEPRECATION] Writer#format_value is being replaced with Writer#format_term in RDF.rb 2.0. Called from #{Gem.location_of_caller.join(':')}"
439
- format_term(term, options)
440
- end
441
-
442
494
  ##
443
495
  # @param [RDF::Node] value
444
496
  # @param [Hash{Symbol => Object}] options = ({})
@@ -490,16 +542,16 @@ module RDF
490
542
  end
491
543
 
492
544
  ##
493
- # @param [RDF::Resource] uriref
545
+ # @param [RDF::Resource] term
494
546
  # @return [String]
495
- def uri_for(uriref)
547
+ def uri_for(term)
496
548
  case
497
- when uriref.is_a?(RDF::Node)
498
- @nodes[uriref]
499
- when uriref.respond_to?(:to_uri)
500
- uriref.to_uri.to_s
549
+ when term.is_a?(RDF::Node)
550
+ @nodes[term] ||= term.to_base
551
+ when term.respond_to?(:to_uri)
552
+ term.to_uri.to_s
501
553
  else
502
- uriref.to_s
554
+ term.to_s
503
555
  end
504
556
  end
505
557