rdf-vocab 2.0.0 → 2.0.1

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
  SHA1:
3
- metadata.gz: 9dfb94f2012dade90af150f17edda4c58213674f
4
- data.tar.gz: 5fd4c0ae73ebbc74c5653e5771a694f2f20db4e9
3
+ metadata.gz: bb1e1b3846cdba7d14e15c200cc8ea1d3e15e712
4
+ data.tar.gz: d787b2d4898d7c9c62d95a422b31d9d8bb5cad09
5
5
  SHA512:
6
- metadata.gz: 2e9c681c7e0a78cb1006d8944c313a59388841b4d773e78e8fc4c9666773f9d88bdb0a06c22a220c81b408da61a99af131c6bff858a8805bcd7efa2e478b73d1
7
- data.tar.gz: 66fd025a3ae4de0ec56e84b051cf062140cf1e18217ea5ad5b11ae01a5d9715b16870a0c5e83391be2e13b13eee22e619c680d812118d01a6d157f82f44a3470
6
+ metadata.gz: 042f2e6832b90e4c9c48d68d446f6c5535d201352db7be8a673685bdd7a81eea51d06b8dfa96e57292690813b38878552ef247817d6a66f85bb4d15444aabeda
7
+ data.tar.gz: 5d570b5447478ecb809df8fc8e9655cdc6683a6c67bb86857eab15d4ffe95f25a560fdf0b250dae4e5980e14d097f338aa37a063e60510b492636abce4703bc6
data/README.md CHANGED
@@ -4,7 +4,12 @@ Common OWL/RDFS Vocabularies for use with Ruby [RDF.rb][]
4
4
  [![Gem Version](https://badge.fury.io/rb/rdf-vocab.png)](http://badge.fury.io/rb/rdf-vocab)
5
5
  [![Build Status](https://travis-ci.org/ruby-rdf/rdf-vocab.png?branch=master)](http://travis-ci.org/ruby-rdf/rdf-vocab)
6
6
 
7
- ##Vocabularies
7
+ ## Extensions
8
+ This gem extends `RDF::Vocabulary` with `#to_ttl`, `#to_jsonld`, and `#to_html` methods to create special-purpose vocabulary serializations. The HTML version is templated using a Haml template to allow output to be customized.
9
+
10
+ Also extends `RDF::Vocabulary::Format` with the `gen-vocab` command extension to the `rdf` executable.
11
+
12
+ ## Vocabularies
8
13
 
9
14
  * RDF::Vocab::ACL - [Web Access Control](http://www.w3.org/wiki/WebAccessControl) (W3C)
10
15
  * RDF::Vocab::Bibframe - [Bibliographic Framework Initiaitive](http://bibframe.org/vocab/) (LoC)
@@ -80,6 +85,8 @@ then
80
85
 
81
86
  This will load all the vocabulary classes in the library.
82
87
 
88
+ Also adds the `gen-vocab` command to the `rdf` command-line executable to generate specifically generated output in Turtle, JSON-LD, and HTML+RDFa for either built-in or arbitrary vocabularies.
89
+
83
90
  ## Adding new vocabularies
84
91
 
85
92
  * First, add an entry to `lib/rdf/vocab.rb`, the key names contained within
@@ -1,5 +1,5 @@
1
- require 'rdf'
2
1
  # frozen_string_literal: true
2
+ require 'rdf'
3
3
  require 'rdf/vocabulary'
4
4
  require 'rdf/vocab'
5
5
 
@@ -20,7 +20,6 @@ module RDF
20
20
  # Ruby's autoloading facility, meaning that `@@subclasses` will be
21
21
  # empty until each subclass has been touched or require'd.
22
22
  RDF::Vocab::VOCABS.each do |n, params|
23
- class_name = params.fetch(:class_name, n.upcase).to_sym
24
23
  begin
25
24
  require "rdf/vocab/#{n}"
26
25
  rescue LoadError
@@ -30,6 +29,480 @@ module RDF
30
29
  end
31
30
  _orig_each(&block)
32
31
  end
32
+
33
+ begin
34
+ require 'rdf/turtle'
35
+ ##
36
+ # Generate Turtle representation, specific to vocabularies
37
+ #
38
+ # @param [RDF::Queryable] :graph Optional graph, otherwise uses statements from vocabulary.
39
+ # @param [Hash{#to_sym => String}] Prefixes to add to output
40
+ # @return [String]
41
+ def to_ttl(graph: nil, prefixes: nil)
42
+ output = []
43
+
44
+ # Find namespaces used in the vocabulary
45
+ graph = RDF::Graph.new {|g| each_statement {|s| g << s}} if graph.nil? || graph.empty?
46
+
47
+ prefixes = vocab_prefixes(graph).merge(prefixes || {})
48
+ pfx_width = prefixes.keys.map(&:to_s).map(&:length).max
49
+ prefixes.each do |pfx, uri|
50
+ output << "@prefix %*s: <%s> .\n" % [pfx_width, pfx, uri]
51
+ end
52
+
53
+ # Determine the category for each subject in the vocabulary graph
54
+ cats = subject_categories(graph)
55
+
56
+ writer = RDF::Turtle::Writer.new(StringIO.new, prefixes: prefixes)
57
+
58
+ {
59
+ ont: {
60
+ heading: "# #{__name__.split('::').last} Vocabulary definition\n"
61
+ },
62
+ classes: {
63
+ heading: "# Class definitions\n"
64
+ },
65
+ properties: {
66
+ heading: "# Property definitions\n"
67
+ },
68
+ datatypes: {
69
+ heading: "# Datatype definitions\n"
70
+ },
71
+ other: {
72
+ heading: "# Other definitions\n"
73
+ }
74
+ }.each do |key, hash|
75
+ next unless cats[key]
76
+
77
+ output << "\n\n#{hash[:heading]}"
78
+
79
+ cats[key].each do |subject|
80
+ po = {}
81
+
82
+ # Group predicates with their values
83
+ graph.query(subject: subject) do |statement|
84
+ # Sanity check this, as these are set to an empty string if not defined.
85
+ next if [RDF::RDFS.label, RDF::RDFS.comment].include?(statement.predicate) && statement.object.to_s.empty?
86
+ po[statement.predicate] ||= []
87
+ po[statement.predicate] << statement.object
88
+ end
89
+
90
+ next if po.empty?
91
+
92
+ po_list = []
93
+ unless (types = po.delete(RDF.type)).empty?
94
+ po_list << 'a ' + types.map {|o| writer.format_term(o)}.join(", ")
95
+ end
96
+
97
+ # Serialize other predicate/objects
98
+ po.each do |predicate, objects|
99
+ resource = predicate.qname ? predicate.pname : "<#{predicate}>"
100
+ po_list << resource + ' ' + objects.map {|o| writer.format_term(o)}.join(", ")
101
+ end
102
+
103
+ # Output statements for this subject
104
+ subj = subject.qname ? subject.pname : "<#{subject}>"
105
+ output << "#{subj} " + po_list.join(";\n ") + "\n .\n"
106
+ end
107
+ end
108
+
109
+ output.join("")
110
+ end
111
+ rescue LoadError
112
+ # No Turtle serialization unless gem loaded
113
+ end
114
+
115
+ begin
116
+ require 'json/ld'
117
+
118
+ ##
119
+ # Generate JSON-LD representation, specific to vocabularies
120
+ #
121
+ # @param [RDF::Queryable] :graph Optional graph, otherwise uses statements from vocabulary.
122
+ # @param [Hash{#to_sym => String}] Prefixes to add to output
123
+ # @return [String]
124
+ def to_jsonld(graph: nil, prefixes: nil)
125
+ context = {}
126
+ rdfs_context = ::JSON.parse %({
127
+ "dc:title": {"@container": "@language"},
128
+ "dc:description": {"@container": "@language"},
129
+ "dc:date": {"@type": "xsd:date"},
130
+ "rdfs:comment": {"@container": "@language"},
131
+ "rdfs:domain": {"@type": "@vocab"},
132
+ "rdfs:label": {"@container": "@language"},
133
+ "rdfs:range": {"@type": "@vocab"},
134
+ "rdfs:seeAlso": {"@type": "@id"},
135
+ "rdfs:subClassOf": {"@type": "@vocab"},
136
+ "rdfs:subPropertyOf": {"@type": "@vocab"},
137
+ "schema:domainIncludes": {"@type": "@vocab"},
138
+ "schema:rangeIncludes": {"@type": "@vocab"},
139
+ "owl:equivalentClass": {"@type": "@vocab"},
140
+ "owl:equivalentProperty": {"@type": "@vocab"},
141
+ "owl:oneOf": {"@container": "@list", "@type": "@vocab"},
142
+ "owl:imports": {"@type": "@id"},
143
+ "owl:versionInfo": {"@type": "@id"},
144
+ "owl:inverseOf": {"@type": "@vocab"},
145
+ "owl:unionOf": {"@type": "@vocab", "@container": "@list"},
146
+ "rdfs_classes": {"@reverse": "rdfs:isDefinedBy", "@type": "@id"},
147
+ "rdfs_properties": {"@reverse": "rdfs:isDefinedBy", "@type": "@id"},
148
+ "rdfs_datatypes": {"@reverse": "rdfs:isDefinedBy", "@type": "@id"},
149
+ "rdfs_instances": {"@reverse": "rdfs:isDefinedBy", "@type": "@id"}
150
+ })
151
+ rdfs_classes, rdfs_properties, rdfs_datatypes, rdfs_instances = [], [], [], [], []
152
+
153
+ ontology = {
154
+ "@context" => rdfs_context,
155
+ "@id" => to_uri.to_s
156
+ }
157
+
158
+ # Find namespaces used in the vocabulary
159
+ graph = RDF::Graph.new {|g| each_statement {|s| g << s}} if graph.nil? || graph.empty?
160
+
161
+ prefixes = vocab_prefixes(graph).merge(prefixes || {})
162
+ prefixes.each do |pfx, uri|
163
+ context[pfx.to_s] = uri.to_s unless pfx.to_s.empty?
164
+ end
165
+
166
+ # Determine the category for each subject in the vocabulary graph
167
+ cats = subject_categories(graph)
168
+
169
+ # Generate term definitions from graph subjects
170
+ cats.values.flatten.each do |term|
171
+ next unless Array(term.qname).length == 2
172
+ context[term.qname.last.to_s] = term.to_uri.to_s
173
+ end
174
+
175
+ # Parse the two contexts so we know what terms are in scope
176
+ jld_context = ::JSON::LD::Context.new.parse([context, rdfs_context])
177
+
178
+ {
179
+ ont: {
180
+ heading: "# #{__name__.split('::').last} Vocabulary definition\n",
181
+ bucket: ontology,
182
+ },
183
+ classes: {
184
+ heading: "# Class definitions\n",
185
+ bucket: rdfs_classes,
186
+ rev_prop: "rdfs_classes"
187
+ },
188
+ properties: {
189
+ heading: "# Property definitions\n",
190
+ bucket: rdfs_properties,
191
+ rev_prop: "rdfs_properties"
192
+ },
193
+ datatypes: {
194
+ heading: "# Datatype definitions\n",
195
+ bucket: rdfs_datatypes,
196
+ rev_prop: "rdfs_datatypes"
197
+ },
198
+ other: {
199
+ heading: "# Other definitions\n",
200
+ bucket: rdfs_instances,
201
+ rev_prop: "rdfs_instances"
202
+ }
203
+ }.each do |key, hash|
204
+ next unless cats[key]
205
+
206
+ cats[key].each do |subject|
207
+ node = {"@id" => subject.pname}
208
+ po = {}
209
+
210
+ # Group predicates with their values
211
+ graph.query(subject: subject) do |statement|
212
+ # Sanity check this, as these are set to an empty string if not defined.
213
+ next if [RDF::RDFS.label, RDF::RDFS.comment].include?(statement.predicate) && statement.object.to_s.empty?
214
+ po[statement.predicate] ||= []
215
+ po[statement.predicate] << statement.object
216
+ end
217
+
218
+ next if po.empty?
219
+
220
+ node['@type'] = po.delete(RDF.type).map {|t| jld_context.compact_iri(t, vocab: true)}
221
+
222
+ po.each do |predicate, objects|
223
+ term = jld_context.compact_iri(predicate, vocab: true)
224
+ node[term] = if jld_context.container(term) == '@language'
225
+ lang_map = objects.inject({}) do |memo, o|
226
+ raise "Language-mapped term #{term} with non plain-literal #{o.inspect}" unless o.literal? && o.plain?
227
+ memo.merge(o.language.to_s => o.value)
228
+ end
229
+ # Don't use language map if there's only one entry with no language
230
+ lang_map = lang_map[""] if lang_map.keys == [""]
231
+ [lang_map]
232
+ else
233
+ objects.map do |o|
234
+ expanded_value = jld_context.expand_value(term, o)
235
+ jld_context.compact_value(term, expanded_value)
236
+ end
237
+ end
238
+ end
239
+
240
+ node.each do |property, values|
241
+ case values.length
242
+ when 0 then node.delete(property)
243
+ when 1 then node[property] = values.first
244
+ end
245
+ end
246
+
247
+ # Either set bucket from node, or append node to bucket
248
+ if hash[:bucket].is_a?(Hash)
249
+ hash[:bucket].merge!(node)
250
+ else
251
+ ontology[hash[:rev_prop]] ||= hash[:bucket]
252
+ hash[:bucket] << node
253
+ end
254
+ end
255
+ end
256
+
257
+ # Serialize result
258
+ {
259
+ "@context" => context,
260
+ "@graph" => ontology
261
+ }.to_json(::JSON::LD::JSON_STATE)
262
+ end
263
+ rescue LoadError
264
+ # No JSON-LD serialization unless gem loaded
265
+ end
266
+
267
+ ##
268
+ # Generate HTML+RDFa representation, specific to vocabularies. This uses generated JSON-LD and a Haml template.
269
+ #
270
+ # @param [RDF::Queryable] :graph Optional graph, otherwise uses statements from vocabulary.
271
+ # @param [Hash{#to_sym => String}] Prefixes to add to output
272
+ # @param [String, Hash] jsonld
273
+ # If not provided, the `to_jsonld` method is used to generate it.
274
+ # @param [String] template The path to a Haml or ERB template used to generate the output using the JSON-LD serialization
275
+ # @return [String]
276
+ def to_html(graph: nil, prefixes: nil, jsonld: nil, template: nil)
277
+ # Find namespaces used in the vocabulary
278
+ graph = RDF::Graph.new {|g| each_statement {|s| g << s}} if graph.nil? || graph.empty?
279
+
280
+ # Get JSON as an object
281
+ json = case jsonld
282
+ when String then ::JSON.parse(File.read jsonld)
283
+ when Hash then jsonld
284
+ else
285
+ ::JSON.parse(to_jsonld(graph: graph, prefixes: prefixes))
286
+ end
287
+ raise "Expected JSON-LD data within the '@graph' key" unless json.has_key?('@graph')
288
+
289
+ template ||= File.expand_path("../../../../etc/template.erb", __FILE__)
290
+
291
+ prefixes = vocab_prefixes(graph).merge(prefixes || {})
292
+ prefixes[:owl] = RDF::OWL.to_uri.to_s
293
+
294
+ # Make sure ontology is typed
295
+ json['@graph']['@type'] ||= ['owl:Ontology']
296
+
297
+ jld_context = ::JSON::LD::Context.new.parse([json['@context'], json['@graph']['@context']])
298
+
299
+ # Expand the JSON-LD to normalize accesses
300
+ expanded = ::JSON::LD::API.expand(json).first
301
+ expanded.delete('@reverse')
302
+
303
+ # Re-compact keys
304
+ expanded = expanded.inject({}) do |memo, (k, v)|
305
+ term = RDF::Vocabulary.find_term(k)
306
+ k = term.pname if term
307
+ memo.merge(k => v)
308
+ end
309
+
310
+ # Normalize label accessors
311
+ expanded['rdfs:label'] ||= %w(dc:title dc11:title skos:prefLabel).inject(nil) do |memo, key|
312
+ memo || expanded[key]
313
+ end || [{'@value' => json['@graph']['@id']}]
314
+ %w(rdfs_classes rdfs_properties rdfs_datatypes rdfs_instances).each do |section|
315
+ next unless json['@graph'][section]
316
+ json['@graph'][section].each do |node|
317
+ node['rdfs:label'] ||= %w(dc:title dc11:title skos:prefLabel).inject do |memo, key|
318
+ memo || node[key]
319
+ end || [{'@value' => node['@id']}]
320
+ end
321
+ end
322
+
323
+ # Expand each part separately, as well.
324
+ %w(rdfs_classes rdfs_properties rdfs_datatypes rdfs_instances).each do |section|
325
+ next unless json['@graph'][section]
326
+ expanded_section = ::JSON::LD::API.expand(json['@graph'][section], expandContext: jld_context)
327
+ # Re-compact keys
328
+ expanded[section] = expanded_section.map do |node|
329
+ node.inject({}) do |memo, (k, v)|
330
+ term = RDF::Vocabulary.find_term(k)
331
+ k = term.pname if term
332
+ memo.merge(k => v)
333
+ end
334
+ end
335
+ end
336
+
337
+ # Template invoked with expanded JSON-LD with outer object including `rdfs_classes`, `rdfs_properties`, and `rdf_instances` sections.
338
+ case template
339
+ when /.haml$/
340
+ require 'haml'
341
+ haml = Haml::Engine.new(File.read(template))
342
+ haml.render(self, ont: expanded, context: json['@context'], prefixes: prefixes)
343
+ when /.erb$/
344
+ require 'erubis'
345
+ eruby = Erubis::FastEruby.new(File.read(template))
346
+ result = eruby.evaluate(binding: self, ont: expanded, context: json['@context'], prefixes: prefixes)
347
+ else
348
+ raise "Unknown template type #{template}. Should have '.erb' or '.haml' extension"
349
+ end
350
+ end
351
+
352
+ ##
353
+ # Create HTML for values (Helper method, needs to be public)
354
+ def value_to_html(property, value, tag)
355
+ value.map do |v|
356
+ %(<#{tag} property="#{property}") +
357
+ if v['@value']
358
+ (v['@language'] ? %( language="#{v['@language']}") : "") +
359
+ (v['@type'] ? %( datatype="#{RDF::URI(v['@type']).pname}") : "") +
360
+ %(>#{v['@value']})
361
+ elsif v['@id']
362
+ %( resource="#{RDF::URI(v['@id']).pname}">#{RDF::URI(v['@id']).pname})
363
+ else
364
+ raise "Unknown value type: #{v.inspect}, #{property}"
365
+ end +
366
+ %(</#{tag}>)
367
+ end.join("\n")
368
+ end
369
+ private
370
+
371
+ ##
372
+ # Prefixes used in this vocabulary
373
+ #
374
+ # @param [RDF::Graph] graph
375
+ # @return [Hash{Symbol => RDF::URI}]
376
+ def vocab_prefixes(graph)
377
+ vocabs = graph.
378
+ terms.
379
+ select(&:uri?).
380
+ map {|u| RDF::Vocabulary.find(u)}.
381
+ uniq.
382
+ compact.
383
+ sort_by(&:__prefix__)
384
+ vocabs << RDF::XSD # incase we need it for a literal
385
+
386
+ # Generate prefix definitions
387
+ vocabs.inject({}) do |memo, v|
388
+ memo.merge(v.__prefix__ => v.to_uri)
389
+ end
390
+ end
391
+
392
+ ##
393
+ # Categorize each subject in the graph
394
+ #
395
+ # @param [RDF::Graph] graph
396
+ # @return [Hash{RDF::URI => Symbol}]
397
+ def subject_categories(graph)
398
+ cats = {}
399
+ categorized = {}
400
+ uncategorized = {}
401
+ graph.query(predicate: RDF.type) do |statement|
402
+ # Only serialize statements that are in the defined vocabulary
403
+ next unless statement.subject.start_with?(self.to_uri)
404
+ case statement.object
405
+ when RDF.Property,
406
+ RDF::OWL.AnnotationProperty,
407
+ RDF::OWL.DatatypeProperty,
408
+ RDF::OWL.FunctionalProperty,
409
+ RDF::OWL.ObjectProperty,
410
+ RDF::OWL.OntologyProperty
411
+ (cats[:properties] ||= []) << statement.subject unless categorized[statement.subject]
412
+ categorized[statement.subject] = true
413
+ when RDF::RDFS.Class, RDF::OWL.Class
414
+ (cats[:classes] ||= []) << statement.subject unless categorized[statement.subject]
415
+ categorized[statement.subject] = true
416
+ when RDF::RDFS.Datatype, RDF::OWL.DataRange
417
+ (cats[:datatypes] ||= []) << statement.subject unless categorized[statement.subject]
418
+ categorized[statement.subject] = true
419
+ when RDF::OWL.Ontology
420
+ (cats[:ont] ||= []) << statement.subject unless categorized[statement.subject]
421
+ categorized[statement.subject] = true
422
+ else
423
+ if statement.subject == self.to_uri
424
+ (cats[:ont] ||= []) << statement.subject unless categorized[statement.subject]
425
+ categorized[statement.subject] = true
426
+ else
427
+ uncategorized[statement.subject] = true
428
+ end
429
+ end
430
+ end
431
+
432
+ # Add all uncategorized subjects as :other
433
+ uncat = (uncategorized.keys - categorized.keys)
434
+ cats[:other] = uncat unless uncat.empty?
435
+
436
+ cats
437
+ end
33
438
  end
439
+
440
+ module VocabFormatExtensions
441
+ ##
442
+ # Hash of CLI commands appropriate for this format
443
+ # @return [Hash{Symbol => Lambda(Array, Hash)}]
444
+ def cli_commands
445
+ super.merge({
446
+ :"gen-vocab" => {
447
+ description: "Generate a vocabulary using a special serialization. Accepts an input graph, or serializes built-in vocabulary",
448
+ parse: false, # Only parse if there are input files, otherwise, uses vocabulary
449
+ help: "gen-vocab --uri <vocabulary-URI> [--output format ttl|jsonld|html] [options] [files]\n",
450
+ lambda: ->(files, options) do
451
+ $stdout.puts "Generate Vocabulary"
452
+ raise ArgumentError, "Must specify vocabulary URI" unless options[:base_uri]
453
+
454
+ # Parse input graphs, if repository is not already created
455
+ if RDF::CLI.repository.empty? && !files.empty?
456
+ RDF::CLI.parse(files, options) do |reader|
457
+ RDF::CLI.repository << reader
458
+ end
459
+ end
460
+
461
+ # Lookup vocabulary, or generate a new vocabulary from this URI
462
+ vocab = RDF::Vocabulary.find(options[:base_uri]) || begin
463
+ raise ArgumentError, "Must specify vocabulary prefix if vocabulary not built-in" unless options[:prefix]
464
+ RDF::Vocabulary.from_graph(RDF::CLI.repository, url: options[:base_uri], class_name: options[:prefix].to_s.upcase)
465
+ end
466
+
467
+ prefixes = {}
468
+ prefixes[options[:prefix]] = options[:base_uri] if options[:prefix]
469
+ out = options[:output] || $stdout
470
+ case options[:output_format]
471
+ when :ttl, nil then out.write vocab.to_ttl(graph: RDF::CLI.repository, prefixes: prefixes)
472
+ when :jsonld then out.write vocab.to_jsonld(graph: RDF::CLI.repository, prefixes: prefixes)
473
+ when :html then out.write vocab.to_html(graph: RDF::CLI.repository, prefixes: prefixes, template: options[:template])
474
+ else
475
+ # Use whatever writer we find
476
+ writer = RDF::Writer.for(options[:output_format]) || RDF::NTriples::Writer
477
+ writer.new(out, options) do |w|
478
+ if RDF::CLI.repository.empty?
479
+ vocab.each_statement {|s| w << s}
480
+ else
481
+ w << RDF::CLI.repository
482
+ end
483
+ end
484
+ end
485
+ end,
486
+ options: [
487
+ RDF::CLI::Option.new(
488
+ symbol: :prefix,
489
+ datatype: String,
490
+ on: ["--prefix PREFIX"],
491
+ description: "Prefix associated with vocabulary, if not built-in."),
492
+ RDF::CLI::Option.new(
493
+ symbol: :template,
494
+ datatype: String,
495
+ on: ["--template TEMPLATE"],
496
+ description: "Path to local template for generating HTML, either Haml or ERB, depending on file extension.\n" +
497
+ "See https://github.com/ruby-rdf/rdf-vocab/tree/develop/etc for built-in templates."),
498
+ ]
499
+ }
500
+ })
501
+ end
502
+ end
503
+
504
+ # Add cli_commands as class method to RDF::Vocabulary::Format
505
+ # TODO: in Ruby 2.0, `prepend` seems to be a private method of the class singleton; works okay elsewhere.
506
+ Format.singleton_class.send(:prepend, VocabFormatExtensions)
34
507
  end
35
508
  end
@@ -0,0 +1,356 @@
1
+ # frozen_string_literal: true
2
+ require File.expand_path("../spec_helper", __FILE__)
3
+ require 'json-schema'
4
+
5
+ describe RDF::Vocabulary do
6
+ describe ".to_ttl" do
7
+ before(:all) do
8
+ @acl = RDF::Vocab::ACL.to_ttl
9
+ @bibo = RDF::Vocab::BIBO.to_ttl
10
+ @dc = RDF::Vocab::DC.to_ttl
11
+ @foaf = RDF::Vocab::FOAF.to_ttl
12
+ end
13
+
14
+ let(:acl) {@acl}
15
+ let(:bibo) {@bibo}
16
+ let(:dc) {@dc}
17
+ let(:foaf) {@foaf}
18
+
19
+ it "defines prefixes used in vocabulary" do
20
+ %w(dc dc11 foaf geo owl rdf rdfs skos vs).each do |pfx|
21
+ expect(foaf).to match(/@prefix\s+#{pfx}: /)
22
+ end
23
+ end
24
+
25
+ it "Does not generate an ontology if missing" do
26
+ expect(acl).not_to include "Vocabulary definition"
27
+ expect(foaf).to include "Vocabulary definition"
28
+ end
29
+
30
+ it "Creates Classes" do
31
+ expect(foaf).to include "Class definitions"
32
+ expect(foaf).to match /foaf:Agent a .*owl:Class/
33
+ end
34
+
35
+ it "Creates Properties" do
36
+ expect(foaf).to include "Property definitions"
37
+ expect(foaf).to match /foaf:account a .*rdf:Property/
38
+ expect(foaf).to match /foaf:account a .*owl:ObjectProperty/
39
+ end
40
+
41
+ it "Creates Datatypes" do
42
+ expect(dc).to include "Datatype definitions"
43
+ expect(dc).to include "dc:Box a rdfs:Datatype"
44
+ end
45
+
46
+ it "Creates Other definitions" do
47
+ expect(bibo).to include "Other definitions"
48
+ expect(bibo).to match /bdarcus a .*foaf:Person/
49
+ end
50
+
51
+ it "Serializes dates" do
52
+ expect(dc).to match %r("\d{4}-\d{2}-\d{2}"\^\^xsd:date)
53
+ end
54
+
55
+ it "Serializes long literals" do
56
+ expect(acl).to include '"""'
57
+ end
58
+
59
+ it "Serializes PNAME_NS" do
60
+ expect(foaf).to include "rdfs:isDefinedBy foaf:"
61
+ end
62
+
63
+ it "Serializes PNAME_LN" do
64
+ expect(foaf).to match /rdfs:subClassOf .*foaf:Document/
65
+ end
66
+
67
+ it "Serializes URIs" do
68
+ expect(foaf).to match %r(rdfs:subClassOf .*<http:)
69
+ end
70
+
71
+ context "smoke test", slow: true do
72
+ RDF::Vocabulary.each do |vocab|
73
+ it "serializes #{vocab.__name__} without raising exception" do
74
+ expect do
75
+ ttl = vocab.to_ttl
76
+ RDF::Turtle::Reader.new(ttl, validate: true).each_statement {}
77
+ end.not_to raise_error
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ describe ".to_jsonld" do
84
+ before(:all) do
85
+ @acl = RDF::Vocab::ACL.to_jsonld
86
+ @bibo = RDF::Vocab::BIBO.to_jsonld
87
+ @dc = RDF::Vocab::DC.to_jsonld
88
+ @foaf = RDF::Vocab::FOAF.to_jsonld
89
+ end
90
+
91
+ let(:acl) {JSON.parse @acl}
92
+ let(:bibo) {JSON.parse @bibo}
93
+ let(:dc) {JSON.parse @dc}
94
+ let(:foaf) {JSON.parse @foaf}
95
+
96
+ it "defines prefixes used in vocabulary" do
97
+ %w(dc dc11 foaf geo owl rdf rdfs skos vs).each do |pfx|
98
+ expect(foaf).to match_json_schema({
99
+ "$schema" => "http://json-schema.org/draft-04/schema#",
100
+ type: "object",
101
+ required: ["@context"],
102
+ properties: {
103
+ "@context" => {
104
+ type: "object",
105
+ required: [pfx]
106
+ }
107
+ }
108
+ })
109
+ end
110
+ end
111
+
112
+ it "Does not generate an ontology if missing" do
113
+ schema = {
114
+ "$schema" => "http://json-schema.org/draft-04/schema#",
115
+ type: "object",
116
+ required: ["@context", "@graph"],
117
+ properties: {
118
+ "@graph" => {
119
+ type: "object",
120
+ required: ["@type"]
121
+ }
122
+ }
123
+ }
124
+ expect(acl).not_to match_json_schema(schema)
125
+ expect(foaf).to match_json_schema(schema)
126
+ end
127
+
128
+ it "Creates Classes" do
129
+ schema = {
130
+ "$schema" => "http://json-schema.org/draft-04/schema#",
131
+ type: "object",
132
+ required: ["@context", "@graph"],
133
+ properties: {
134
+ "@graph" => {
135
+ type: "object",
136
+ required: ["rdfs_classes"],
137
+ properties: {
138
+ "rdfs_classes" => {
139
+ type: "array",
140
+ items: {
141
+ allOf: [{
142
+ type: "object",
143
+ required: ["@id", "@type"]
144
+ }]
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+ expect(foaf).to match_json_schema(schema)
152
+ #expect(foaf).to match_json_path "$..rdfs_classes[?(@.@id='foaf:Agent')]"
153
+ end
154
+
155
+ it "Creates Properties" do
156
+ schema = {
157
+ "$schema" => "http://json-schema.org/draft-04/schema#",
158
+ type: "object",
159
+ required: ["@context", "@graph"],
160
+ properties: {
161
+ "@graph" => {
162
+ type: "object",
163
+ required: ["rdfs_properties"],
164
+ properties: {
165
+ "rdfs_properties" => {
166
+ type: "array",
167
+ items: {
168
+ allOf: [{
169
+ type: "object",
170
+ required: ["@id", "@type"]
171
+ }]
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+ expect(foaf).to match_json_schema(schema)
179
+ #expect(foaf).to match_json_path "$..rdfs_properties[?(@.@id='foaf:account')]"
180
+ end
181
+
182
+ it "Creates Datatypes" do
183
+ schema = {
184
+ "$schema" => "http://json-schema.org/draft-04/schema#",
185
+ type: "object",
186
+ required: ["@context", "@graph"],
187
+ properties: {
188
+ "@graph" => {
189
+ type: "object",
190
+ required: ["rdfs_datatypes"],
191
+ properties: {
192
+ "rdfs_datatypes" => {
193
+ type: "array",
194
+ items: {
195
+ allOf: [{
196
+ type: "object",
197
+ required: ["@id", "@type"]
198
+ }]
199
+ }
200
+ }
201
+ }
202
+ }
203
+ }
204
+ }
205
+ expect(dc).to match_json_schema(schema)
206
+ #expect(dc).to match_json_path "$..rdfs_datatypes[?(@.@id='dc:Box')]"
207
+ end
208
+
209
+ it "Creates Other definitions" do
210
+ schema = {
211
+ "$schema" => "http://json-schema.org/draft-04/schema#",
212
+ type: "object",
213
+ required: ["@context", "@graph"],
214
+ properties: {
215
+ "@graph" => {
216
+ type: "object",
217
+ required: ["rdfs_instances"],
218
+ properties: {
219
+ "rdfs_instances" => {
220
+ type: "array",
221
+ items: {
222
+ allOf: [{
223
+ type: "object",
224
+ required: ["@id", "@type"]
225
+ }]
226
+ }
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+ expect(bibo).to match_json_schema(schema)
233
+ #expect(bibo).to match_json_path "$..rdfs_instances[?(@.@id='bdarcus')]"
234
+ end
235
+
236
+ context "smoke test", slow: true do
237
+ RDF::Vocabulary.each do |vocab|
238
+ it "serializes #{vocab.__name__} without raising exception" do
239
+ expect do
240
+ jsonld = vocab.to_jsonld
241
+ JSON::LD::Reader.new(jsonld, validate: true).each_statement {}
242
+ end.not_to raise_error
243
+ end
244
+ end
245
+ end
246
+ end
247
+
248
+ describe ".to_html" do
249
+ before(:all) do
250
+ @acl = RDF::Vocab::ACL.to_html
251
+ @bibo = RDF::Vocab::BIBO.to_html
252
+ @dc = RDF::Vocab::DC.to_html
253
+ @foaf = RDF::Vocab::FOAF.to_html
254
+ end
255
+
256
+ let(:acl) {Nokogiri::HTML.parse @acl}
257
+ let(:bibo) {Nokogiri::HTML.parse @bibo}
258
+ let(:dc) {Nokogiri::HTML.parse @dc}
259
+ let(:foaf) {Nokogiri::HTML.parse @foaf}
260
+
261
+ it "defines prefixes used in vocabulary" do
262
+ %w(dc dc11 foaf geo owl rdf rdfs skos vs).each do |pfx|
263
+ expect(foaf.at_xpath('/html/body/@prefix').to_s).to include("#{pfx}: ")
264
+ end
265
+ end
266
+
267
+ it "Creates Classes" do
268
+ expect(foaf.xpath('//section/h2').to_s).to include("Class Definitions")
269
+ expect(foaf.at_xpath('//td[@resource="foaf:Group"]')).not_to be_nil
270
+ end
271
+
272
+ it "Creates Properties" do
273
+ expect(foaf.xpath('//section/h2').to_s).to include("Property Definitions")
274
+ expect(foaf.at_xpath('//td[@resource="foaf:isPrimaryTopicOf"]')).not_to be_nil
275
+ end
276
+
277
+ it "Creates Datatypes" do
278
+ expect(dc.xpath('//section/h2').to_s).to include("Datatype Definitions")
279
+ expect(dc.at_xpath('//td[@resource="dc:RFC1766"]')).not_to be_nil
280
+ end
281
+
282
+ it "Creates Other definitions" do
283
+ expect(dc.xpath('//section/h2').to_s).to include("Instance Definitions")
284
+ expect(dc.at_xpath('//td[@resource="dc:NLM"]')).not_to be_nil
285
+ end
286
+
287
+ context "smoke test", slow: true do
288
+ skips = [
289
+ RDF::Vocab::Bibframe,
290
+ RDF::Vocab::EBUCore,
291
+ RDF::Vocab::GEONAMES,
292
+ RDF::Vocab::IIIF,
293
+ RDF::Vocab::MO,
294
+ RDF::Vocab::PREMIS,
295
+ RDF::Vocab::SIOC,
296
+ ]
297
+ RDF::Vocabulary.each do |vocab|
298
+ it "serializes #{vocab.__name__} without raising exception", skip: (skips.include?(vocab)) do
299
+ expect do
300
+ rdfa = vocab.to_html
301
+ RDF::RDFa::Reader.new(rdfa, validate: true, base_uri: vocab.to_uri).each_statement {}
302
+ end.not_to raise_error
303
+ end
304
+ end
305
+ end
306
+ end
307
+
308
+ describe RDF::Vocabulary::Format do
309
+ describe ".cli_commands", skip: ("Rubinius issues in RDF.rb" if RUBY_ENGINE == "rbx") do
310
+ require 'rdf/cli'
311
+ describe "gen-vocab" do
312
+ let(:vocab) {RDF::Vocab::IANA}
313
+
314
+ it "generates Turtle by default" do
315
+ stringio = StringIO.new
316
+ RDF::CLI.exec(["gen-vocab"], base_uri: vocab.to_uri, output: stringio)
317
+ expect(stringio.string).not_to be_empty
318
+ graph = RDF::Graph.new
319
+ RDF::Turtle::Reader.new(stringio.string, validate: true) {|r| graph << r}
320
+ expect(graph).not_to be_empty
321
+ expect(graph).to be_valid
322
+ end
323
+
324
+ it "generates Turtle explictly" do
325
+ stringio = StringIO.new
326
+ RDF::CLI.exec(["gen-vocab"], base_uri: vocab.to_uri, output_format: :ttl, output: stringio)
327
+ expect(stringio.string).not_to be_empty
328
+ graph = RDF::Graph.new
329
+ RDF::Turtle::Reader.new(stringio.string, validate: true) {|r| graph << r}
330
+ expect(graph).not_to be_empty
331
+ expect(graph).to be_valid
332
+ end
333
+
334
+ it "generates JSON-LD" do
335
+ stringio = StringIO.new
336
+ RDF::CLI.exec(["gen-vocab"], base_uri: vocab.to_uri, output_format: :jsonld, output: stringio)
337
+ expect(stringio.string).not_to be_empty
338
+ graph = RDF::Graph.new
339
+ JSON::LD::Reader.new(stringio.string, validate: true) {|r| graph << r}
340
+ expect(graph).not_to be_empty
341
+ expect(graph).to be_valid
342
+ end
343
+
344
+ it "generates HTML" do
345
+ stringio = StringIO.new
346
+ RDF::CLI.exec(["gen-vocab"], base_uri: vocab.to_uri, output_format: :html, output: stringio)
347
+ expect(stringio.string).not_to be_empty
348
+ graph = RDF::Graph.new
349
+ RDF::RDFa::Reader.new(stringio.string, validate: true, base_uri: vocab.to_uri) {|r| graph << r}
350
+ expect(graph).not_to be_empty
351
+ expect(graph).to be_valid
352
+ end
353
+ end
354
+ end
355
+ end
356
+ end
@@ -0,0 +1,28 @@
1
+ require 'rspec/matchers' # @see http://rubygems.org/gems/rspec
2
+ require 'json-schema'
3
+ require 'jsonpath'
4
+
5
+ RSpec::Matchers.define :match_json_schema do |schema|
6
+ match do |actual|
7
+ @error_message = JSON::Validator.fully_validate(schema, actual, validate_schema: true).join("")
8
+ @error_message.empty?
9
+ end
10
+
11
+ failure_message do |actual|
12
+ @error_message +
13
+ "\nActual: #{actual.to_json(JSON::LD::JSON_STATE)}" +
14
+ "\nSchema: #{schema.to_json(JSON::LD::JSON_STATE)}"
15
+ end
16
+ end
17
+
18
+ RSpec::Matchers.define :match_json_path do |path|
19
+ match do |actual|
20
+ matched = JsonPath.new(path).on(actual)
21
+ !matched.empty?
22
+ end
23
+
24
+ failure_message do |actual|
25
+ "Path #{path} not found in data" +
26
+ "\nActual: #{actual.to_json(JSON::LD::JSON_STATE)}"
27
+ end
28
+ end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
  require "bundler/setup"
3
3
  require 'rdf/vocab'
4
+ require 'matchers'
4
5
 
5
6
  RSpec.configure do |config|
6
- config.filter_run :focus => true
7
+ config.filter_run focus: true
8
+ config.filter_run_excluding slow: true
7
9
  config.run_all_when_everything_filtered = true
8
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdf-vocab
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Chandek-Stark
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-04-10 00:00:00.000000000 Z
13
+ date: 2016-05-02 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rdf
@@ -27,33 +27,33 @@ dependencies:
27
27
  - !ruby/object:Gem::Version
28
28
  version: '2.0'
29
29
  - !ruby/object:Gem::Dependency
30
- name: ld-patch
30
+ name: haml
31
31
  requirement: !ruby/object:Gem::Requirement
32
32
  requirements:
33
33
  - - "~>"
34
34
  - !ruby/object:Gem::Version
35
- version: '0.3'
35
+ version: '4.0'
36
36
  type: :development
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
39
39
  requirements:
40
40
  - - "~>"
41
41
  - !ruby/object:Gem::Version
42
- version: '0.3'
42
+ version: '4.0'
43
43
  - !ruby/object:Gem::Dependency
44
- name: rdf-reasoner
44
+ name: erubis
45
45
  requirement: !ruby/object:Gem::Requirement
46
46
  requirements:
47
47
  - - "~>"
48
48
  - !ruby/object:Gem::Version
49
- version: '0.4'
49
+ version: '2.7'
50
50
  type: :development
51
51
  prerelease: false
52
52
  version_requirements: !ruby/object:Gem::Requirement
53
53
  requirements:
54
54
  - - "~>"
55
55
  - !ruby/object:Gem::Version
56
- version: '0.4'
56
+ version: '2.7'
57
57
  - !ruby/object:Gem::Dependency
58
58
  name: bundler
59
59
  requirement: !ruby/object:Gem::Requirement
@@ -68,6 +68,76 @@ dependencies:
68
68
  - - "~>"
69
69
  - !ruby/object:Gem::Version
70
70
  version: '1.7'
71
+ - !ruby/object:Gem::Dependency
72
+ name: json-ld
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - "~>"
76
+ - !ruby/object:Gem::Version
77
+ version: '2.0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - "~>"
83
+ - !ruby/object:Gem::Version
84
+ version: '2.0'
85
+ - !ruby/object:Gem::Dependency
86
+ name: json-schema
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: '2.0'
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - "~>"
97
+ - !ruby/object:Gem::Version
98
+ version: '2.0'
99
+ - !ruby/object:Gem::Dependency
100
+ name: jsonpath
101
+ requirement: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - "~>"
104
+ - !ruby/object:Gem::Version
105
+ version: '0.5'
106
+ type: :development
107
+ prerelease: false
108
+ version_requirements: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - "~>"
111
+ - !ruby/object:Gem::Version
112
+ version: '0.5'
113
+ - !ruby/object:Gem::Dependency
114
+ name: ld-patch
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - "~>"
118
+ - !ruby/object:Gem::Version
119
+ version: '0.3'
120
+ type: :development
121
+ prerelease: false
122
+ version_requirements: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - "~>"
125
+ - !ruby/object:Gem::Version
126
+ version: '0.3'
127
+ - !ruby/object:Gem::Dependency
128
+ name: nokogiri
129
+ requirement: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - "~>"
132
+ - !ruby/object:Gem::Version
133
+ version: '1.6'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - "~>"
139
+ - !ruby/object:Gem::Version
140
+ version: '1.6'
71
141
  - !ruby/object:Gem::Dependency
72
142
  name: rake
73
143
  requirement: !ruby/object:Gem::Requirement
@@ -82,6 +152,48 @@ dependencies:
82
152
  - - "~>"
83
153
  - !ruby/object:Gem::Version
84
154
  version: '10.0'
155
+ - !ruby/object:Gem::Dependency
156
+ name: rdf-rdfa
157
+ requirement: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - "~>"
160
+ - !ruby/object:Gem::Version
161
+ version: '2.0'
162
+ type: :development
163
+ prerelease: false
164
+ version_requirements: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - "~>"
167
+ - !ruby/object:Gem::Version
168
+ version: '2.0'
169
+ - !ruby/object:Gem::Dependency
170
+ name: rdf-reasoner
171
+ requirement: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - "~>"
174
+ - !ruby/object:Gem::Version
175
+ version: '0.4'
176
+ type: :development
177
+ prerelease: false
178
+ version_requirements: !ruby/object:Gem::Requirement
179
+ requirements:
180
+ - - "~>"
181
+ - !ruby/object:Gem::Version
182
+ version: '0.4'
183
+ - !ruby/object:Gem::Dependency
184
+ name: rdf-turtle
185
+ requirement: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - "~>"
188
+ - !ruby/object:Gem::Version
189
+ version: '2.0'
190
+ type: :development
191
+ prerelease: false
192
+ version_requirements: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - "~>"
195
+ - !ruby/object:Gem::Version
196
+ version: '2.0'
85
197
  - !ruby/object:Gem::Dependency
86
198
  name: rspec
87
199
  requirement: !ruby/object:Gem::Requirement
@@ -201,6 +313,8 @@ files:
201
313
  - lib/rdf/vocab/wot.rb
202
314
  - lib/rdf/vocab/xhtml.rb
203
315
  - lib/rdf/vocab/xhv.rb
316
+ - spec/extensions_spec.rb
317
+ - spec/matchers.rb
204
318
  - spec/spec_helper.rb
205
319
  - spec/vocab_spec.rb
206
320
  homepage: http://ruby-rdf.github.com/rdf-vocab
@@ -223,11 +337,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
223
337
  version: '0'
224
338
  requirements: []
225
339
  rubyforge_project:
226
- rubygems_version: 2.4.8
340
+ rubygems_version: 2.5.1
227
341
  signing_key:
228
342
  specification_version: 4
229
343
  summary: A library of RDF vocabularies
230
344
  test_files:
345
+ - spec/extensions_spec.rb
346
+ - spec/matchers.rb
231
347
  - spec/spec_helper.rb
232
348
  - spec/vocab_spec.rb
233
349
  has_rdoc: false