rdf 1.99.1 → 2.0.0.beta1

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