rdf-vocab 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  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
OSZAR »