Skip to content

Commit ad71b66

Browse files
committed
Finish 3.2.3
2 parents 78a0dbc + 8981cdd commit ad71b66

File tree

6 files changed

+171
-61
lines changed

6 files changed

+171
-61
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
ruby:
2929
- 2.6
3030
- 2.7
31-
- 3.0
31+
- "3.0"
3232
- 3.1
3333
- ruby-head
3434
- jruby

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.2.2
1+
3.2.3

lib/rdf/model/uri.rb

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ class URI
111111
tag tel turn turns tv urn javascript
112112
).freeze
113113

114+
# Characters in a PName which must be escaped
115+
PN_ESCAPE_CHARS = /[~\.\-!\$&'\(\)\*\+,;=\/\?\#@%_]/.freeze
116+
PN_ESCAPES = /\\#{PN_ESCAPE_CHARS}/.freeze
117+
114118
##
115119
# Cache size may be set through {RDF.config} using `uri_cache_size`.
116120
#
@@ -627,10 +631,14 @@ def parent
627631
# RDF::URI('http://www.w3.org/2000/01/rdf-schema#').qname #=> [:rdfs, nil]
628632
# RDF::URI('http://www.w3.org/2000/01/rdf-schema#label').qname #=> [:rdfs, :label]
629633
# RDF::RDFS.label.qname #=> [:rdfs, :label]
634+
# RDF::Vocab::DC.title.qname(
635+
# prefixes: {dcterms: 'http://purl.org/dc/terms/'}) #=> [:dcterms, :title]
636+
#
637+
# @note within this software, the term QName is used to describe the tuple of prefix and suffix for a given IRI, where the prefix identifies some defined vocabulary. This somewhat contrasts with the notion of a [Qualified Name](https://www.w3.org/TR/2006/REC-xml-names11-20060816/#ns-qualnames) from XML, which are a subset of Prefixed Names.
630638
#
631639
# @param [Hash{Symbol => String}] prefixes
632640
# Explicit set of prefixes to look for matches, defaults to loaded vocabularies.
633-
# @return [Array(Symbol, Symbol)] or `nil` if no QName found
641+
# @return [Array(Symbol, Symbol)] or `nil` if no QName found. The suffix component will not have [reserved characters](https://www.w3.org/TR/turtle/#reserved) escaped.
634642
def qname(prefixes: nil)
635643
if prefixes
636644
prefixes.each do |prefix, uri|
@@ -659,13 +667,25 @@ def qname(prefixes: nil)
659667
end
660668

661669
##
662-
# Returns a string version of the QName or the full IRI
670+
# Returns a Prefixed Name (PName) or the full IRI with any [reserved characters](https://www.w3.org/TR/turtle/#reserved) in the suffix escaped.
671+
#
672+
# @example Using a custom prefix for creating a PNname.
673+
# RDF::URI('http://purl.org/dc/terms/creator').
674+
# pname(prefixes: {dcterms: 'http://purl.org/dc/terms/'})
675+
# #=> "dcterms:creator"
663676
#
664677
# @param [Hash{Symbol => String}] prefixes
665678
# Explicit set of prefixes to look for matches, defaults to loaded vocabularies.
666679
# @return [String] or `nil`
680+
# @see #qname
681+
# @see https://www.w3.org/TR/rdf-sparql-query/#prefNames
667682
def pname(prefixes: nil)
668-
(q = self.qname(prefixes: prefixes)) ? q.join(":") : to_s
683+
q = self.qname(prefixes: prefixes)
684+
return self.to_s unless q
685+
prefix, suffix = q
686+
suffix = suffix.to_s.gsub(PN_ESCAPE_CHARS) {|c| "\\#{c}"} if
687+
suffix.to_s.match?(PN_ESCAPE_CHARS)
688+
[prefix, suffix].join(":")
669689
end
670690

671691
##

lib/rdf/vocabulary.rb

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@ module RDF
5353
# "rdfs:subClassOf" => "http://example/SuperClass"
5454
# end
5555
#
56-
# @see http://www.w3.org/TR/curie/
57-
# @see http://en.wikipedia.org/wiki/QName
56+
# @see https://www.w3.org/TR/rdf-sparql-query/#prefNames
5857
class Vocabulary
5958
extend ::Enumerable
6059

@@ -379,15 +378,20 @@ def properties
379378
alias_method :__properties__, :properties
380379

381380
##
382-
# Attempt to expand a Compact IRI/PName/QName using loaded vocabularies
381+
# Attempt to expand a Compact IRI/PName using loaded vocabularies
383382
#
384383
# @param [String, #to_s] pname
384+
# The local-part of the PName will will have [reserved character escapes](https://www.w3.org/TR/turtle/#reserved) unescaped.
385385
# @return [Term]
386-
# @raise [KeyError] if pname suffix not found in identified vocabulary
386+
# @raise [KeyError] if pname suffix not found in identified vocabulary.
387387
# @raise [ArgumentError] if resulting URI is not valid
388388
def expand_pname(pname)
389389
return pname unless pname.is_a?(String) || pname.is_a?(Symbol)
390390
prefix, suffix = pname.to_s.split(":", 2)
391+
# Unescape escaped PN_ESCAPE_CHARS
392+
if suffix.match?(/\\#{RDF::URI::PN_ESCAPE_CHARS}/)
393+
suffix = suffix.gsub(RDF::URI::PN_ESCAPES) {|matched| matched[1..-1]}
394+
end
391395
if prefix == "rdf"
392396
RDF[suffix]
393397
elsif vocab_detail = RDF::Vocabulary.vocab_map[prefix.to_sym]
@@ -417,9 +421,10 @@ def find(uri)
417421
end
418422

419423
##
420-
# Return the Vocabulary term associated with a URI
424+
# Return the Vocabulary term associated with a URI
421425
#
422-
# @param [RDF::URI] uri
426+
# @param [RDF::URI, String] uri
427+
# If `uri` has is a pname in a locded vocabulary, the suffix portion of the PName will have escape characters unescaped before resolving against the vocabulary.
423428
# @return [Vocabulary::Term]
424429
def find_term(uri)
425430
uri = RDF::URI(uri)
@@ -428,7 +433,8 @@ def find_term(uri)
428433
if vocab.ontology == uri
429434
vocab.ontology
430435
else
431-
vocab[uri.to_s[vocab.to_uri.to_s.length..-1].to_s]
436+
suffix = uri.to_s[vocab.to_uri.to_s.length..-1].to_s
437+
vocab[suffix]
432438
end
433439
end
434440
end
@@ -574,7 +580,6 @@ def from_graph(graph, url: nil, class_name: nil, extra: nil)
574580
term_defs
575581
end
576582

577-
#require 'byebug'; byebug
578583
# Pass over embedded_defs with anonymous references, once
579584
embedded_defs.each do |term, attributes|
580585
attributes.each do |ak, avs|
@@ -643,12 +648,31 @@ def inspect
643648
alias_method :__name__, :name
644649

645650
##
646-
# Returns a suggested CURIE/PName prefix for this vocabulary class.
651+
# Returns a suggested vocabulary prefix for this vocabulary class.
647652
#
648653
# @return [Symbol]
649654
# @since 0.3.0
650655
def __prefix__
651-
__name__.split('::').last.downcase.to_sym
656+
instance_variable_defined?(:@__prefix__) ?
657+
@__prefix__ :
658+
__name__.split('::').last.downcase.to_sym
659+
end
660+
661+
##
662+
# Sets the vocabulary prefix to use for this vocabulary..
663+
#
664+
# @example Overriding a standard vocabulary prefix.
665+
# RDF::Vocab::DC.__prefix__ = :dcterms
666+
# RDF::Vocab::DC.title.pname #=> 'dcterms:title'
667+
#
668+
# @param [Symbol] prefix
669+
# @return [Symbol]
670+
# @since 3.2.3
671+
def __prefix__=(prefix)
672+
params = RDF::Vocabulary.vocab_map[__prefix__]
673+
@__prefix__ = prefix.to_sym
674+
RDF::Vocabulary.register(@__prefix__, self, **params)
675+
@__prefix__
652676
end
653677

654678
protected

spec/model_uri_spec.rb

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,70 @@
849849
end
850850
end
851851

852+
describe "#qname" do
853+
it "#qname" do
854+
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#').qname).to eql [:rdfs, nil]
855+
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#label').qname).to eql [:rdfs, :label]
856+
expect(RDF::RDFS.label.qname).to eql [:rdfs, :label]
857+
end
858+
859+
it "#qname with empty prefixes" do
860+
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#').qname(prefixes: {})).to be_nil
861+
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#label').qname(prefixes: {})).to be_nil
862+
expect(RDF::RDFS.label.qname(prefixes: {})).to be_nil
863+
end
864+
865+
it "#qname with explicit prefixes" do
866+
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#').qname(prefixes: {rdfs: 'http://www.w3.org/2000/01/rdf-schema#'})).to eql [:rdfs, :""]
867+
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#label').qname(prefixes: {rdfs: 'http://www.w3.org/2000/01/rdf-schema#'})).to eql [:rdfs, :label]
868+
expect(RDF::RDFS.label.qname(prefixes: {rdfs: 'http://www.w3.org/2000/01/rdf-schema#'})).to eql [:rdfs, :label]
869+
end
870+
end
871+
872+
describe "#pname" do
873+
it "#pname" do
874+
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#').pname).to eql 'rdfs:'
875+
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#label').pname).to eql 'rdfs:label'
876+
expect(RDF::RDFS.label.pname).to eql 'rdfs:label'
877+
end
878+
879+
it "#pname with empty prefixes" do
880+
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#').pname(prefixes: {})).to eq 'http://www.w3.org/2000/01/rdf-schema#'
881+
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#label').pname(prefixes: {})).to eq 'http://www.w3.org/2000/01/rdf-schema#label'
882+
expect(RDF::RDFS.label.pname(prefixes: {})).to eq 'http://www.w3.org/2000/01/rdf-schema#label'
883+
end
884+
885+
it "#pname with explicit prefixes" do
886+
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#').pname(prefixes: {rdfs: 'http://www.w3.org/2000/01/rdf-schema#'})).to eql 'rdfs:'
887+
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#label').pname(prefixes: {rdfs: 'http://www.w3.org/2000/01/rdf-schema#'})).to eql 'rdfs:label'
888+
expect(RDF::RDFS.label.pname(prefixes: {rdfs: 'http://www.w3.org/2000/01/rdf-schema#'})).to eql 'rdfs:label'
889+
end
890+
891+
context "escapes" do
892+
{
893+
"http://example.org/c-" => 'ex:c\-',
894+
"http://example.org/c!" => 'ex:c\!',
895+
"http://example.org/c$" => 'ex:c\$',
896+
"http://example.org/c&" => 'ex:c\&',
897+
"http://example.org/c'" => "ex:c\\'",
898+
"http://example.org/c()" => 'ex:c\(\)',
899+
"http://example.org/c*+" => 'ex:c\*\+',
900+
"http://example.org/c;=" => 'ex:c\;\=',
901+
"http://example.org/c/#" => 'ex:c\/\#',
902+
"http://example.org/c@_" => 'ex:c\@\_',
903+
"http://example.org/c:d?" => 'ex:c:d\?',
904+
"http://example.org/c~z." => 'ex:c\~z\.',
905+
}.each do |orig, result|
906+
it "#{orig} => #{result}" do
907+
uri = RDF::URI(orig)
908+
pname = uri.pname(prefixes: {ex: "http://example.org/"})
909+
expect(uri).to be_valid
910+
expect(pname).to eql result
911+
end
912+
end
913+
end
914+
end
915+
852916
context "Examples" do
853917
it "Creating a URI reference (1)" do
854918
expect(RDF::URI.new("https://rubygems.org/gems/rdf")).to be_a_uri
@@ -956,42 +1020,6 @@
9561020
expect(RDF::URI('http://example.org/path/').parent).to eql RDF::URI('http://example.org/')
9571021
end
9581022

959-
it "#qname" do
960-
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#').qname).to eql [:rdfs, nil]
961-
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#label').qname).to eql [:rdfs, :label]
962-
expect(RDF::RDFS.label.qname).to eql [:rdfs, :label]
963-
end
964-
965-
it "#qname with empty prefixes" do
966-
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#').qname(prefixes: {})).to be_nil
967-
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#label').qname(prefixes: {})).to be_nil
968-
expect(RDF::RDFS.label.qname(prefixes: {})).to be_nil
969-
end
970-
971-
it "#qname with explicit prefixes" do
972-
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#').qname(prefixes: {rdfs: 'http://www.w3.org/2000/01/rdf-schema#'})).to eql [:rdfs, :""]
973-
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#label').qname(prefixes: {rdfs: 'http://www.w3.org/2000/01/rdf-schema#'})).to eql [:rdfs, :label]
974-
expect(RDF::RDFS.label.qname(prefixes: {rdfs: 'http://www.w3.org/2000/01/rdf-schema#'})).to eql [:rdfs, :label]
975-
end
976-
977-
it "#pname" do
978-
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#').pname).to eql 'rdfs:'
979-
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#label').pname).to eql 'rdfs:label'
980-
expect(RDF::RDFS.label.pname).to eql 'rdfs:label'
981-
end
982-
983-
it "#pname with empty prefixes" do
984-
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#').pname(prefixes: {})).to eq 'http://www.w3.org/2000/01/rdf-schema#'
985-
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#label').pname(prefixes: {})).to eq 'http://www.w3.org/2000/01/rdf-schema#label'
986-
expect(RDF::RDFS.label.pname(prefixes: {})).to eq 'http://www.w3.org/2000/01/rdf-schema#label'
987-
end
988-
989-
it "#pname with explicit prefixes" do
990-
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#').pname(prefixes: {rdfs: 'http://www.w3.org/2000/01/rdf-schema#'})).to eql 'rdfs:'
991-
expect(RDF::URI('http://www.w3.org/2000/01/rdf-schema#label').pname(prefixes: {rdfs: 'http://www.w3.org/2000/01/rdf-schema#'})).to eql 'rdfs:label'
992-
expect(RDF::RDFS.label.pname(prefixes: {rdfs: 'http://www.w3.org/2000/01/rdf-schema#'})).to eql 'rdfs:label'
993-
end
994-
9951023
it "#start_with?" do
9961024
expect(RDF::URI('http://example.org/')).to be_start_with('http')
9971025
expect(RDF::URI('http://example.org/')).not_to be_start_with('ftp')

spec/vocabulary_spec.rb

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,39 @@
8888
end
8989
end
9090

91+
describe ".expand_pname" do
92+
{
93+
"rdfs:" => RDF::RDFS.to_uri,
94+
"rdfs:label" => RDF::RDFS.label,
95+
RDF::Value => RDF::Value,
96+
}.each do |orig, result|
97+
it "#{orig} => #{result}" do
98+
expect(RDF::Vocabulary.expand_pname(orig)).to eql result
99+
end
100+
end
101+
102+
context "unescapes" do
103+
{
104+
'xsd:c\-' => "http://www.w3.org/2001/XMLSchema#c-",
105+
'xsd:c\!' => "http://www.w3.org/2001/XMLSchema#c!",
106+
'xsd:c\$' => "http://www.w3.org/2001/XMLSchema#c$",
107+
'xsd:c\&' => "http://www.w3.org/2001/XMLSchema#c&",
108+
"xsd:c\\'" => "http://www.w3.org/2001/XMLSchema#c'",
109+
'xsd:c\(\)' => "http://www.w3.org/2001/XMLSchema#c()",
110+
'xsd:c\*\+' => "http://www.w3.org/2001/XMLSchema#c*+",
111+
'xsd:c\;\=' => "http://www.w3.org/2001/XMLSchema#c;=",
112+
'xsd:c\/\#' => "http://www.w3.org/2001/XMLSchema#c/#",
113+
'xsd:c\@\_' => "http://www.w3.org/2001/XMLSchema#c@_",
114+
'xsd:c:d\?' => "http://www.w3.org/2001/XMLSchema#c:d?",
115+
'xsd:c\~z\.' => "http://www.w3.org/2001/XMLSchema#c~z.",
116+
}.each do |pname, result|
117+
it "#{pname} => #{result}" do
118+
expect(RDF::Vocabulary.expand_pname(pname).to_s).to eql result
119+
end
120+
end
121+
end
122+
end
123+
91124
describe ".limit_vocabs" do
92125
before { RDF::Vocabulary.limit_vocabs(:rdf, :rdfs)}
93126
after { RDF::Vocabulary.limit_vocabs()}
@@ -115,7 +148,7 @@
115148
if cls
116149
expect(RDF::Vocabulary.find(term)).to eql cls
117150
else
118-
expect(RDF::Vocabulary.find_term(term)).to be_nil
151+
expect(RDF::Vocabulary.find(term)).to be_nil
119152
end
120153
end
121154
end
@@ -276,12 +309,6 @@
276309
end
277310
end
278311

279-
it "should expand PName for vocabulary" do
280-
expect(RDF::Vocabulary.expand_pname("rdfs:")).to eql RDF::RDFS.to_uri
281-
expect(RDF::Vocabulary.expand_pname("rdfs:label")).to eql RDF::RDFS.label
282-
expect(RDF::Vocabulary.expand_pname(RDF::Value)).to eql RDF::Value
283-
end
284-
285312
it "should support Web Ontology Language (OWL)" do
286313
expect(RDF::OWL).to be_a_vocabulary("http://www.w3.org/2002/07/owl#")
287314
expect(RDF::OWL).to have_properties("http://www.w3.org/2002/07/owl#", %w(allValuesFrom annotatedProperty annotatedSource annotatedTarget assertionProperty backwardCompatibleWith bottomDataProperty bottomObjectProperty cardinality complementOf datatypeComplementOf deprecated differentFrom disjointUnionOf disjointWith distinctMembers equivalentClass equivalentProperty hasKey hasSelf hasValue imports incompatibleWith intersectionOf inverseOf maxCardinality maxQualifiedCardinality members minCardinality minQualifiedCardinality onClass onDataRange onDatatype onProperties onProperty oneOf priorVersion propertyChainAxiom propertyDisjointWith qualifiedCardinality sameAs someValuesFrom sourceIndividual targetIndividual targetValue topDataProperty topObjectProperty unionOf versionIRI versionInfo withRestrictions))
@@ -909,11 +936,22 @@
909936
end
910937

911938
context 'Vocabs outside of the RDF::Vocab namespace' do
912-
uri = 'urn:x-bogus:test-vocab:'
913-
TestVocab = Class.new RDF::Vocabulary(uri)
914-
RDF::Vocabulary.register :testvocab, TestVocab
939+
let(:uri) {'urn:x-bogus:test-vocab:'}
940+
before(:all) {
941+
TestVocab = Class.new RDF::Vocabulary('urn:x-bogus:test-vocab:')
942+
RDF::Vocabulary.register :testvocab, TestVocab
943+
}
944+
915945
it 'correctly expands the pname of an arbitrary class' do
916946
expect(RDF::Vocabulary.expand_pname('testvocab:test')).to eq(TestVocab.test)
917947
end
948+
949+
it 'uses custom prefix' do
950+
TestVocab.__prefix__ = :tv
951+
term = RDF::Vocabulary.expand_pname('tv:test')
952+
expect(term).to eq(TestVocab.test)
953+
expect(term.pname).to eq 'tv:test'
954+
expect(RDF::Vocabulary.find_term(term.to_s)).to eq TestVocab.test
955+
end
918956
end
919957
end

0 commit comments

Comments
 (0)