Skip to content

Commit 498a1ef

Browse files
committed
Support :unique_bnodes option to make sure that randomized BNode lables are generated when flattening or writing.
1 parent 0eb5d51 commit 498a1ef

File tree

6 files changed

+60
-23
lines changed

6 files changed

+60
-23
lines changed

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ end
1212

1313
group :debug do
1414
gem "wirble"
15-
gem "byebug", :platforms => :mri_20
15+
gem "byebug", :platforms => [:mri_20, :mri_21]
1616
end
1717

1818
platforms :rbx do

lib/json/ld/api.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,16 @@ class API
7272
# If set to `true`, the JSON-LD processor will treat `rdf:type` like a normal property instead of using `@type`.
7373
# @option options [Boolean] :rename_bnodes (true)
7474
# Rename bnodes as part of expansion, or keep them the same.
75+
# @option options [Boolean] :unique_bnodes (false)
76+
# Use unique bnode identifiers, defaults to using the identifier which the node was originall initialized with (if any).
7577
# @yield [api]
7678
# @yieldparam [API]
7779
def initialize(input, context, options = {}, &block)
7880
@options = {:compactArrays => true}.merge(options)
7981
@options[:validate] = true if @options[:processingMode] == "json-ld-1.0"
8082
@options[:documentLoader] ||= self.class.method(:documentLoader)
8183
options[:rename_bnodes] ||= true
82-
@namer = options[:rename_bnodes] ? BlankNodeNamer.new("b") : BlankNodeMapper.new
84+
@namer = options[:unique_bnodes] ? BlankNodeUniqer.new : (options[:rename_bnodes] ? BlankNodeNamer.new("b") : BlankNodeMapper.new)
8385
@value = case input
8486
when Array, Hash then input.dup
8587
when IO, StringIO

lib/json/ld/utils.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,23 @@ def get_name(old = "")
163163
end
164164
end
165165

166+
class BlankNodeUniqer < BlankNodeMapper
167+
##
168+
# Use the uniquely generated bnodes, rather than a sequence
169+
# @param [String] old ("")
170+
# @return [String]
171+
def get_sym(old = "")
172+
old = old.to_s.sub(/_:/, '')
173+
if old && self.has_key?(old)
174+
self[old]
175+
elsif !old.empty?
176+
self[old] = RDF::Node.new.to_unique_base[2..-1]
177+
else
178+
RDF::Node.new.to_unique_base[2..-1]
179+
end
180+
end
181+
end
182+
166183
class BlankNodeNamer < BlankNodeMapper
167184
# @param [String] prefix
168185
def initialize(prefix)

lib/json/ld/writer.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ def self.to_sym
8585
# Add standard prefixes to @prefixes, if necessary.
8686
# @option options [IO, Array, Hash, String, Context] :context ({})
8787
# context to use when serializing. Constructed context for native serialization.
88+
# @option options [Boolean] :unique_bnodes (false)
89+
# Use unique bnode identifiers, defaults to using the identifier which the node was originall initialized with (if any).
8890
# @yield [writer] `self`
8991
# @yieldparam [RDF::Writer] writer
9092
# @yieldreturn [void]
@@ -160,7 +162,12 @@ def write_epilogue
160162
end if @options[:prefixes]
161163
ctx
162164
end
163-
165+
166+
# Rename BNodes to uniquify them, if necessary
167+
if options[:unique_bnodes]
168+
result = API.flatten(result, context, @options)
169+
end
170+
164171
# Perform compaction, if we have a context
165172
if context
166173
debug("writer") { "compact result"}

spec/to_rdf_spec.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@
2525
}),
2626
%q([ <http://example.com/foo> "bar"^^xsd:string] .)
2727
],
28+
"@id with _:a and reference" => [
29+
%q({
30+
"@id": "_:a",
31+
"http://example.com/foo": {"@id": "_:a"}
32+
}),
33+
%q(_:a <http://example.com/foo> _:a] .)
34+
],
2835
}.each do |title, (js, ttl)|
2936
it title do
3037
ttl = "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . #{ttl}"
@@ -660,7 +667,7 @@
660667
def parse(input, options = {})
661668
@debug = []
662669
graph = options[:graph] || RDF::Graph.new
663-
options = {:debug => @debug, :validate => true, :canonicalize => false}.merge(options)
670+
options = {debug: @debug, validate: true, canonicalize: false}.merge(options)
664671
JSON::LD::API.toRdf(StringIO.new(input), options) {|st| graph << st}
665672
graph
666673
end

spec/writer_spec.rb

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,23 @@
2020
{:content_type => 'application/x-ld+json'},
2121
].each do |arg|
2222
it "discovers with #{arg.inspect}" do
23-
RDF::Reader.for(arg).should == JSON::LD::Reader
23+
expect(RDF::Reader.for(arg)).to eql JSON::LD::Reader
2424
end
2525
end
2626
end
2727

2828
context "simple tests" do
2929
it "should use full URIs without base" do
3030
input = %(<http://a/b> <http://a/c> <http://a/d> .)
31-
serialize(input).should produce([{
31+
expect(serialize(input)).to produce([{
3232
'@id' => "http://a/b",
3333
"http://a/c" => [{"@id" => "http://a/d"}]
3434
}], @debug)
3535
end
3636

3737
it "should use qname URIs with standard prefix" do
3838
input = %(<http://xmlns.com/foaf/0.1/b> <http://xmlns.com/foaf/0.1/c> <http://xmlns.com/foaf/0.1/d> .)
39-
serialize(input, :standard_prefixes => true).
40-
should produce({
39+
expect(serialize(input, :standard_prefixes => true)).to produce({
4140
'@context' => {
4241
"foaf" => "http://xmlns.com/foaf/0.1/",
4342
},
@@ -53,12 +52,11 @@
5352
<https://senet.org/gm> <https://senet.org/ns#unofficialTitle> "Rhythm Tengoku"@en .
5453
<https://senet.org/gm> <https://senet.org/ns#urlkey> "rhythm-tengoku" .
5554
)
56-
serialize(input, :prefixes => {
55+
expect(serialize(input, :prefixes => {
5756
:dc => "http://purl.org/dc/terms/",
5857
:frbr => "http://vocab.org/frbr/core#",
5958
:senet => "https://senet.org/ns#",
60-
}).
61-
should produce({
59+
})).to produce({
6260
'@context' => {
6361
"dc" => "http://purl.org/dc/terms/",
6462
"frbr" => "http://vocab.org/frbr/core#",
@@ -75,8 +73,8 @@
7573
it "should use CURIEs with empty prefix" do
7674
input = %(<http://xmlns.com/foaf/0.1/b> <http://xmlns.com/foaf/0.1/c> <http://xmlns.com/foaf/0.1/d> .)
7775
begin
78-
serialize(input, :prefixes => { "" => RDF::FOAF}).
79-
should produce({
76+
expect(serialize(input, :prefixes => { "" => RDF::FOAF})).
77+
to produce({
8078
"@context" => {
8179
"" => "http://xmlns.com/foaf/0.1/"
8280
},
@@ -92,8 +90,8 @@
9290

9391
it "should not use terms if no suffix" do
9492
input = %(<http://xmlns.com/foaf/0.1/> <http://xmlns.com/foaf/0.1/> <http://xmlns.com/foaf/0.1/> .)
95-
serialize(input, :standard_prefixes => true).
96-
should_not produce({
93+
expect(serialize(input, :standard_prefixes => true)).
94+
not_to produce({
9795
"@context" => {"foaf" => "http://xmlns.com/foaf/0.1/"},
9896
'@id' => "foaf",
9997
"foaf" => {"@id" => "foaf"}
@@ -107,10 +105,10 @@
107105
db:Michael_Jackson dbo:artistOf <http://dbpedia.org/resource/%28I_Can%27t_Make_It%29_Another_Day> .
108106
)
109107

110-
serialize(input, :prefixes => {
108+
expect(serialize(input, :prefixes => {
111109
"db" => RDF::URI("http://dbpedia.org/resource/"),
112-
"dbo" => RDF::URI("http://dbpedia.org/ontology/")}).
113-
should produce({
110+
"dbo" => RDF::URI("http://dbpedia.org/ontology/")})).
111+
to produce({
114112
"@context" => {
115113
"db" => "http://dbpedia.org/resource/",
116114
"dbo" => "http://dbpedia.org/ontology/"
@@ -120,15 +118,21 @@
120118
}, @debug)
121119
end
122120

121+
it "should not use provided node identifiers if :unique_bnodes set" do
122+
input = %(_:a <http://example.com/foo> _:b \.)
123+
result = serialize(input, unique_bnodes: true, context: {})
124+
expect(result.to_json).to match(%r(_:g\w+))
125+
end
126+
123127
it "serializes multiple subjects" do
124128
input = %q(
125129
@prefix : <http://www.w3.org/2006/03/test-description#> .
126130
@prefix dc: <http://purl.org/dc/terms/> .
127131
<http://example.com/test-cases/0001> a :TestCase .
128132
<http://example.com/test-cases/0002> a :TestCase .
129133
)
130-
serialize(input, :prefixes => {"" => "http://www.w3.org/2006/03/test-description#"}).
131-
should produce({
134+
expect(serialize(input, :prefixes => {"" => "http://www.w3.org/2006/03/test-description#"})).
135+
to produce({
132136
'@context' => {
133137
"" => "http://www.w3.org/2006/03/test-description#",
134138
"dc" => RDF::DC.to_s
@@ -154,12 +158,12 @@
154158
owl:onClass <http://data.wikia.com/terms#Element>;
155159
owl:onProperty <http://data.wikia.com/terms#characterIn> .
156160
)
157-
serialize(input, :rename_bnodes => false, :prefixes => {
161+
expect(serialize(input, :rename_bnodes => false, :prefixes => {
158162
:owl => "http://www.w3.org/2002/07/owl#",
159163
:rdfs => "http://www.w3.org/2000/01/rdf-schema#",
160164
:xsd => "http://www.w3.org/2001/XMLSchema#"
161-
}).
162-
should produce({
165+
})).
166+
to produce({
163167
'@context' => {
164168
"owl" => "http://www.w3.org/2002/07/owl#",
165169
"rdf" => "http://www.w3.org/1999/02/22-rdf-syntax-ns#",

0 commit comments

Comments
 (0)