-
Notifications
You must be signed in to change notification settings - Fork 98
/
vocabulary.rb
744 lines (681 loc) · 26 KB
/
vocabulary.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
module RDF
##
# A {Vocabulary} represents an RDFS or OWL vocabulary.
#
# A {Vocabulary} can also serve as a Domain Specific Language (DSL) for generating an RDF Graph definition for the vocabulary (see {RDF::Vocabulary#to_enum}).
#
# ### Defining a vocabulary using the DSL
# Vocabularies can be defined based on {RDF::Vocabulary} or {RDF::StrictVocabulary} using a simple Domain Specific Language (DSL). Terms of the vocabulary are specified using either `property` or `term` (alias), with the attributes of the term listed in a hash. See {property} for description of the hash.
#
# ### Vocabularies:
#
# The following vocabularies are pre-defined for your convenience:
#
# * {RDF} - Resource Description Framework (RDF)
# * {RDF::OWL} - Web Ontology Language (OWL)
# * {RDF::RDFS} - RDF Schema (RDFS)
# * {RDF::XSD} - XML Schema (XSD)
#
# Other vocabularies are defined in the [rdf-vocab](http://rubygems.org/gems/rdf-vocab) gem
#
# @example Using pre-defined RDF vocabularies
# include RDF
#
# RDF.type #=> RDF::URI("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")
# RDFS.seeAlso #=> RDF::URI("http://www.w3.org/2000/01/rdf-schema#seeAlso")
# OWL.sameAs #=> RDF::URI("http://www.w3.org/2002/07/owl#sameAs")
# XSD.dateTime #=> RDF::URI("http://www.w3.org/2001/XMLSchema#dateTime")
#
# @example Using ad-hoc RDF vocabularies
# foaf = RDF::Vocabulary.new("http://xmlns.com/foaf/0.1/")
# foaf.knows #=> RDF::URI("http://xmlns.com/foaf/0.1/knows")
# foaf[:name] #=> RDF::URI("http://xmlns.com/foaf/0.1/name")
# foaf['mbox'] #=> RDF::URI("http://xmlns.com/foaf/0.1/mbox")
#
# @example Method calls are converted to the typical RDF camelcase convention
# foaf = RDF::Vocabulary.new("http://xmlns.com/foaf/0.1/")
# foaf.family_name #=> RDF::URI("http://xmlns.com/foaf/0.1/familyName")
# owl.same_as #=> RDF::URI("http://www.w3.org/2002/07/owl#sameAs")
# # []-style access is left as is
# foaf['family_name'] #=> RDF::URI("http://xmlns.com/foaf/0.1/family_name")
# foaf[:family_name] #=> RDF::URI("http://xmlns.com/foaf/0.1/family_name")
#
#
#
# @example Generating RDF from a vocabulary definition
# graph = RDF::Graph.new << RDF::RDFS.to_enum
# graph.dump(:ntriples)
#
# @example Defining a simple vocabulary
# class EX < RDF::StrictVocabulay("http://example/ns#")
# term :Class,
# label: "My Class",
# comment: "Good to use as an example",
# "rdf:type" => "rdfs:Class",
# "rdfs:subClassOf" => "http://example/SuperClass"
# end
#
# @see http://www.w3.org/TR/curie/
# @see http://en.wikipedia.org/wiki/QName
class Vocabulary
extend ::Enumerable
class << self
##
# Enumerates known RDF vocabulary classes.
#
# @yield [klass]
# @yieldparam [Class] klass
# @return [Enumerator]
def each(&block)
if self.equal?(Vocabulary)
# This is needed since all vocabulary classes are defined using
# Ruby's autoloading facility, meaning that `@@subclasses` will be
# empty until each subclass has been touched or require'd.
RDF::VOCABS.each { |v| require "rdf/vocab/#{v}" unless v == :rdf }
@@subclasses.select(&:name).each(&block)
else
__properties__.each(&block)
end
end
##
# Is this a strict vocabulary, or a liberal vocabulary allowing arbitrary properties?
def strict?; false; end
##
# @overload property
# Returns `property` in the current vocabulary
# @return [RDF::Vocabulary::Term]
#
# @overload property(name, options)
# Defines a new property or class in the vocabulary.
#
# @param [String, #to_s] name
# @param [Hash{Symbol => Object}] options
# Any other values are expected to be String which expands to a {URI} using built-in vocabulary prefixes. The value is a `String` or `Array<String>` which is interpreted according to the `range` of the associated property.
# @option options [String, Array<String>] :label
# Shortcut for `rdfs:label`, values are String interpreted as a {Literal}.
# @option options [String, Array<String>] :comment
# Shortcut for `rdfs:comment`, values are String interpreted as a {Literal}.
# @option options [String, Array<String>] :subClassOf
# Shortcut for `rdfs:subClassOf`, values are String interpreted as a {URI}.
# @option options [String, Array<String>] :subPropertyOf
# Shortcut for `rdfs:subPropertyOf`, values are String interpreted as a {URI}.
# @option options [String, Array<String>] :domain
# Shortcut for `rdfs:domain`, values are String interpreted as a {URI}.
# @option options [String, Array<String>] :range
# Shortcut for `rdfs:range`, values are String interpreted as a {URI}.
# @option options [String, Array<String>] :type
# Shortcut for `rdf:type`, values are String interpreted as a {URI}.
# @return [RDF::Vocabulary::Term]
def property(*args)
case args.length
when 0
Term.intern("#{self}property", attributes: {label: "property", vocab: self})
else
name, options = args
options = {label: name.to_s, vocab: self}.merge(options || {})
uri_str = [to_s, name.to_s].join('')
Term.cache.delete(uri_str.to_sym) # Clear any previous entry
prop = Term.intern(uri_str, attributes: options)
props[name.to_sym] = prop
# Define an accessor, except for problematic properties
(class << self; self; end).send(:define_method, name) { prop } unless %w(property hash).include?(name.to_s)
prop
end
end
# Alternate use for vocabulary terms, functionally equivalent to {#property}.
alias_method :term, :property
alias_method :__property__, :property
##
# @return [Array<RDF::URI>] a list of properties in the current vocabulary
def properties
props.values
end
alias_method :__properties__, :properties
##
# Attempt to expand a Compact IRI/PName/QName using loaded vocabularies
#
# @param [String, #to_s] pname
# @return [RDF::URI]
# @raise [KeyError] if pname suffix not found in identified vocabulary
def expand_pname(pname)
prefix, suffix = pname.to_s.split(":", 2)
if prefix == "rdf"
RDF[suffix]
elsif vocab = RDF::Vocabulary.each.detect {|v| v.__name__ && v.__prefix__ == prefix.to_sym}
suffix.to_s.empty? ? vocab.to_uri : vocab[suffix]
else
RDF::Vocabulary.find_term(pname) || RDF::URI(pname)
end
end
##
# Return the Vocabulary associated with a URI. Allows the trailing '/' or '#' to be excluded
#
# @param [RDF::URI] uri
# @return [Vocabulary]
def find(uri)
RDF::Vocabulary.detect do |v|
if uri.length >= v.to_uri.length
RDF::URI(uri).start_with?(v.to_uri)
else
v.to_uri.to_s.sub(%r([/#]$), '') == uri.to_s
end
end
end
##
# Return the Vocabulary term associated with a URI
#
# @param [RDF::URI] uri
# @return [Vocabulary::Term]
def find_term(uri)
uri = RDF::URI(uri)
return uri if uri.is_a?(Vocabulary::Term)
vocab = RDF::Vocabulary.detect {|v| uri.start_with?(v.to_uri)}
vocab[uri.to_s[vocab.to_uri.to_s.length..-1]] if vocab
end
##
# Returns the URI for the term `property` in this vocabulary.
#
# @param [#to_s] property
# @return [RDF::URI]
def [](property)
if props.has_key?(property.to_sym)
props[property.to_sym]
else
Term.intern([to_s, property.to_s].join(''), attributes: {vocab: self})
end
end
##
# List of vocabularies this vocabulary `owl:imports`
#
# @note the alias {__imports__} guards against `RDF::OWL.imports` returning a term, rather than an array of vocabularies
# @return [Array<RDF::Vocabulary>]
def imports
@imports ||= begin
Array(self[""].attributes[:"owl:imports"]).map {|pn|find(expand_pname(pn)) rescue nil}.compact
rescue KeyError
[]
end
end
alias_method :__imports__, :imports
##
# List of vocabularies which import this vocabulary
# @return [Array<RDF::Vocabulary>]
def imported_from
@imported_from ||= begin
RDF::Vocabulary.select {|v| v.__imports__.include?(self)}
end
end
##
# Returns the base URI for this vocabulary class.
#
# @return [RDF::URI]
def to_uri
RDF::URI.intern(to_s)
end
# For IRI compatibility
alias_method :to_iri, :to_uri
##
# Return an enumerator over {RDF::Statement} defined for this vocabulary.
# @return [RDF::Enumerable::Enumerator]
# @see Object#enum_for
def enum_for(method = :each_statement, *args)
# Ensure that enumerators are, themselves, queryable
this = self
Enumerable::Enumerator.new do |yielder|
this.send(method, *args) {|*y| yielder << (y.length > 1 ? y : y.first)}
end
end
alias_method :to_enum, :enum_for
##
# Enumerate each statement constructed from the defined vocabulary terms
#
# If a property value is known to be a {URI}, or expands to a {URI}, the `object` is a URI, otherwise, it will be a {Literal}.
#
# @yield statement
# @yieldparam [RDF::Statement]
def each_statement(&block)
props.each do |name, subject|
subject.each_statement(&block)
end
end
##
# Returns a string representation of this vocabulary class.
#
# @return [String]
def to_s
@@uris.has_key?(self) ? @@uris[self].to_s : super
end
##
# Create a vocabulary from a graph or enumerable
#
# @param [RDF::Enumerable] graph
# @param [URI, #to_s] url
# @param [String] class_name
# 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`.
# @param [Array<Symbol>, Hash{Symbol => Hash}] extra
# 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}.
# @return [RDF::Vocabulary] the loaded vocabulary
def from_graph(graph, url: nil, class_name: nil, extra: nil)
vocab = if class_name
Object.const_set(class_name, Class.new(self.create(url)))
else
Class.new(self.create(url))
end
term_defs = {}
graph.each do |statement|
next unless statement.subject.uri? && statement.subject.start_with?(url)
name = statement.subject.to_s[url.to_s.length..-1]
term = (term_defs[name.to_sym] ||= {})
key = case statement.predicate
when RDF.type then :type
when RDF::RDFS.subClassOf then :subClassOf
when RDF::RDFS.subPropertyOf then :subPropertyOf
when RDF::RDFS.range then :range
when RDF::RDFS.domain then :domain
when RDF::RDFS.comment then :comment
when RDF::RDFS.label then :label
when RDF::URI("http://schema.org/inverseOf") then :inverseOf
when RDF::URI("http://schema.org/domainIncludes") then :domainIncludes
when RDF::URI("http://schema.org/rangeIncludes") then :rangeIncludes
else statement.predicate.pname.to_sym
end
value = if statement.object.uri?
statement.object.pname
elsif statement.object.literal? && (statement.object.language || :en).to_s =~ /^en-?/
statement.object.to_s
end
(term[key] ||= []) << value if value
end
# Create extra terms
term_defs = case extra
when Array
extra.inject({}) {|memo, s| memo[s.to_sym] = {label: s.to_s}; memo}.merge(term_defs)
when Hash
extra.merge(term_defs)
else
term_defs
end
term_defs.each do |term, attributes|
vocab.__property__ term, attributes
end
vocab
end
##
# Returns a developer-friendly representation of this vocabulary class.
#
# @return [String]
def inspect
if self == Vocabulary
self.to_s
else
sprintf("%s(%s)", superclass.to_s, to_s)
end
end
# Preserve the class name so that it can be obtained even for
# vocabularies that define a `name` property:
alias_method :__name__, :name
##
# Returns a suggested CURIE/PName prefix for this vocabulary class.
#
# @return [Symbol]
# @since 0.3.0
def __prefix__
__name__.split('::').last.downcase.to_sym
end
protected
def inherited(subclass) # @private
unless @@uri.nil?
@@subclasses << subclass unless %w(http://www.w3.org/1999/02/22-rdf-syntax-ns#).include?(@@uri)
subclass.send(:private_class_method, :new)
@@uris[subclass] = @@uri
@@uri = nil
end
super
end
def method_missing(property, *args, &block)
property = RDF::Vocabulary.camelize(property.to_s)
if %w(to_ary).include?(property.to_s)
super
elsif args.empty? && !to_s.empty?
Term.intern([to_s, property.to_s].join(''), attributes: {vocab: self})
else
super
end
end
private
def props; @properties ||= {}; end
end
# Undefine all superfluous instance methods:
undef_method(*instance_methods.
map(&:to_s).
select {|m| m =~ /^\w+$/}.
reject {|m| %w(object_id dup instance_eval inspect to_s class).include?(m) || m[0,2] == '__'}.
map(&:to_sym))
##
# @param [RDF::URI, String, #to_s] uri
def initialize(uri)
@uri = case uri
when RDF::URI then uri.to_s
else RDF::URI.parse(uri.to_s) ? uri.to_s : nil
end
end
##
# Returns the URI for the term `property` in this vocabulary.
#
# @param [#to_s] property
# @return [URI]
def [](property)
Term.intern([to_s, property.to_s].join(''), attributes: {vocab: self.class})
end
##
# Returns the base URI for this vocabulary.
#
# @return [URI]
def to_uri
RDF::URI.intern(to_s)
end
# For IRI compatibility
alias_method :to_iri, :to_uri
##
# Returns a string representation of this vocabulary.
#
# @return [String]
def to_s
@uri.to_s
end
##
# Returns a developer-friendly representation of this vocabulary.
#
# @return [String]
def inspect
sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, to_s)
end
protected
def self.create(uri) # @private
@@uri = uri
self
end
def method_missing(property, *args, &block)
property = self.class.camelize(property.to_s)
if %w(to_ary).include?(property.to_s)
super
elsif args.empty?
self[property]
else
super
end
end
def self.camelize(str)
str.split('_').inject([]) do |buffer, e|
buffer.push(buffer.empty? ? e : e.capitalize)
end.join
end
private
@@subclasses = [::RDF] # @private
@@uris = {} # @private
@@uri = nil # @private
# A Vocabulary Term is a URI that can also act as an {Enumerable} to generate the RDF definition of vocabulary terms as defined within the vocabulary definition.
class Term < RDF::URI
# @!method comment
# `rdfs:comment` accessor
# @return [String]
# @!method label
# `rdfs:label` accessor
# @return [String]
# @!method type
# `rdf:type` accessor
# @return [RDF::URI]
# @!method subClassOf
# `rdfs:subClassOf` accessor
# @return [RDF::URI]
# @!method subPropertyOf
# `rdfs:subPropertyOf` accessor
# @return [RDF::URI]
# @!method domain
# `rdfs:domain` accessor
# @return [RDF::URI]
# @!method range
# `rdfs:range` accessor
# @return [RDF::URI]
# @!method inverseOf
# `owl:inverseOf` accessor
# @return [RDF::URI]
# @!method domainIncludes
# `schema:domainIncludes` accessor
# @return [RDF::URI]
# @!method rangeIncludes
# `schema:rangeIncludes` accoessor
# @return [RDF::URI]
# @!attribute [rw] attributes
# Attributes of this vocabulary term, used for finding `label` and `comment` and to serialize the term back to RDF.
# @return [Hash{Symbol,Resource => Term, #to_s}]
attr_accessor :attributes
##
# @overload URI(uri, options = {})
# @param [URI, String, #to_s] uri
# @param [Hash{Symbol => Object}] options
# @option options [Boolean] :validate (false)
# @option options [Boolean] :canonicalize (false)
# @option options [Hash{Symbol,Resource => Term, #to_s}] :attributes
# Attributes of this vocabulary term, used for finding `label` and `comment` and to serialize the term back to RDF
#
# @overload URI(options = {})
# @param [Hash{Symbol => Object}] options
# @option options options [Boolean] :validate (false)
# @option options options [Boolean] :canonicalize (false)
# @option options [Vocabulary] :vocab The {Vocabulary} associated with this term.
# @option options [String, #to_s] :scheme The scheme component.
# @option options [String, #to_s] :user The user component.
# @option options [String, #to_s] :password The password component.
# @option options [String, #to_s] :userinfo
# The u optionsserinfo component. If this is supplied, the user and password
# compo optionsnents must be omitted.
# @option options [String, #to_s] :host The host component.
# @option options [String, #to_s] :port The port component.
# @option options [String, #to_s] :authority
# The a optionsuthority component. If this is supplied, the user, password,
# useri optionsnfo, host, and port components must be omitted.
# @option options [String, #to_s] :path The path component.
# @option options [String, #to_s] :query The query component.
# @option options [String, #to_s] :fragment The fragment component.
# @option options [Hash{Symbol,Resource => Term, #to_s}] :attributes
# Attributes of this vocabulary term, used for finding `label` and `comment` and to serialize the term back to RDF
def initialize(*args, **options)
@attributes = options.fetch(:attributes)
if RUBY_ENGINE == "rbx"
# FIXME: Somehow, this gets messed up in Rubinius
args << options
super(*args)
else
super
end
end
##
# Vocabulary of this term.
#
# @return [RDF::Vocabulary]
def vocab; @attributes.fetch(:vocab); end
##
# Returns a duplicate copy of `self`.
#
# @return [RDF::URI]
def dup
self.class.new((@value || @object).dup, attributes: @attributes)
end
##
# Determine if the URI is a valid according to RFC3987
#
# @return [Boolean] `true` or `false`
# @since 0.3.9
def valid?
# Validate relative to RFC3987
to_s.match(RDF::URI::IRI) || false
end
##
# Is this a class term?
# @return [Boolean]
def class?
!!(self.type.to_s =~ /Class/)
end
##
# Is this a class term?
# @return [Boolean]
def property?
!!(self.type.to_s =~ /Property/)
end
##
# Is this a class term?
# @return [Boolean]
def datatype?
!!(self.type.to_s =~ /Datatype/)
end
##
# Is this neither a class, property or datatype term?
# @return [Boolean]
def other?
!!(self.type.to_s !~ /(Class|Property|Datatype)/)
end
##
# Enumerate each statement constructed from the defined vocabulary terms
#
# If a property value is known to be a {URI}, or expands to a {URI}, the `object` is a URI, otherwise, it will be a {Literal}.
#
# @yield statement
# @yieldparam [RDF::Statement]
def each_statement
attributes.reject {|p| p == :vocab}.each do |prop, values|
Array(values).each do |value|
begin
case prop
when :type
prop = RDF.type
value = RDF::Vocabulary.expand_pname(value)
when :subClassOf
prop = RDFS.subClassOf
value = RDF::Vocabulary.expand_pname(value)
when :subPropertyOf
prop = RDFS.subPropertyOf
value = RDF::Vocabulary.expand_pname(value)
when :domain
prop = RDFS.domain
value = RDF::Vocabulary.expand_pname(value)
when :range
prop = RDFS.range
value = RDF::Vocabulary.expand_pname(value)
when :inverseOf
prop = RDF::URI("http://schema.org/inverseOf")
value = RDF::Vocabulary.expand_pname(value)
when :domainIncludes
prop = RDF::URI("http://schema.org/domainIncludes")
value = RDF::Vocabulary.expand_pname(value)
when :rangeIncludes
prop = RDF::URI("http://schema.org/rangeIncludes")
value = RDF::Vocabulary.expand_pname(value)
when :label
prop = RDF::RDFS.label
when :comment
prop = RDF::RDFS.comment
else
prop = RDF::Vocabulary.expand_pname(prop.to_s)
next unless prop
v = value.to_s
value = RDF::Vocabulary.expand_pname(v)
unless value && value.valid?
# Use as most appropriate literal
value = [
RDF::Literal::Date,
RDF::Literal::DateTime,
RDF::Literal::Integer,
RDF::Literal::Decimal,
RDF::Literal::Double,
RDF::Literal::Boolean,
RDF::Literal
].inject(nil) do |memo, klass|
l = klass.new(v)
memo || (l if l.valid?)
end
end
end
yield RDF::Statement(self, prop, value)
rescue KeyError
# Skip things eroneously defined in the vocabulary
end
end
end
end
##
# Return an enumerator over {RDF::Statement} defined for this vocabulary.
# @return [RDF::Enumerable::Enumerator]
# @see Object#enum_for
def enum_for(method = :each_statement, *args)
# Ensure that enumerators are, themselves, queryable
this = self
Enumerable::Enumerator.new do |yielder|
this.send(method, *args) {|*y| yielder << (y.length > 1 ? y : y.first)}
end
end
alias_method :to_enum, :enum_for
##
# Returns a <code>String</code> representation of the URI object's state.
#
# @return [String] The URI object's state, as a <code>String</code>.
def inspect
sprintf("#<%s:%#0x URI:%s>", Term.to_s, self.object_id, self.to_s)
end
# Implement accessor to symbol attributes
def respond_to?(method, include_all = false)
@attributes.has_key?(method) || super
end
# Accessor for `schema:domainIncludes`
# @return [RDF::URI]
def domain_includes
Array(@attributes[:domainIncludes]).map {|v| RDF::Vocabulary.expand_pname(v)}
end
# Accessor for `schema:rangeIncludes`
# @return [RDF::URI]
def range_includes
Array(@attributes[:rangeIncludes]).map {|v| RDF::Vocabulary.expand_pname(v)}
end
protected
# Implement accessor to symbol attributes
def method_missing(method, *args, &block)
case method
when :comment
@attributes.fetch(method, "")
when :label
@attributes.fetch(method, to_s.split(/[\/\#]/).last)
when :type, :subClassOf, :subPropertyOf, :domain, :range, :inverseOf, :domainIncludes, :rangeIncludes
Array(@attributes[method]).map {|v| RDF::Vocabulary.expand_pname(v)}
else
super
end
end
end # Term
end # Vocabulary
# Represents an RDF Vocabulary. The difference from {RDF::Vocabulary} is that
# that every concept in the vocabulary is required to be declared. To assist
# in this, an existing RDF representation of the vocabulary can be loaded as
# the basis for concepts being available
class StrictVocabulary < Vocabulary
class << self
# Redefines method_missing to the original definition
# By remaining a subclass of Vocabulary, we remain available to
# Vocabulary::each etc.
define_method(:method_missing, BasicObject.instance_method(:method_missing))
##
# Is this a strict vocabulary, or a liberal vocabulary allowing arbitrary properties?
def strict?; true; end
##
# Returns the URI for the term `property` in this vocabulary.
#
# @param [#to_s] name
# @return [RDF::URI]
# @raise [KeyError] if property not defined in vocabulary
def [](name)
props.fetch(name.to_sym)
rescue KeyError
raise KeyError, "#{name} not found in vocabulary #{self.__name__}"
end
end
end # StrictVocabulary
end # RDF