Skip to content

Commit b76c51f

Browse files
committed
Added pruneBlankNodeIdentifiers options for framing.
For json-ld/json-ld.org#293.
1 parent 1faa478 commit b76c51f

File tree

3 files changed

+134
-36
lines changed

3 files changed

+134
-36
lines changed

lib/json/ld/api.rb

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ def self.flatten(input, context, options = {})
302302
# @option options [Boolean] :omitDefault (false)
303303
# a flag specifying that properties that are missing from the JSON-LD input should be omitted from the output.
304304
# @option options [Boolean] :expanded Input is already expanded
305+
# @option options [Boolean] :pruneBlankNodeIdentifiers (true) removes blank node identifiers that are only used once.
305306
# @yield jsonld
306307
# @yieldparam [Hash] jsonld
307308
# The framed JSON-LD document
@@ -313,13 +314,14 @@ def self.flatten(input, context, options = {})
313314
def self.frame(input, frame, options = {})
314315
result = nil
315316
options = {
316-
base: (input if input.is_a?(String)),
317-
compactArrays: true,
318-
embed: '@last',
319-
explicit: false,
320-
requireAll: true,
321-
omitDefault: false,
322-
documentLoader: method(:documentLoader)
317+
base: (input if input.is_a?(String)),
318+
compactArrays: true,
319+
embed: '@last',
320+
explicit: false,
321+
requireAll: true,
322+
omitDefault: false,
323+
pruneBlankNodeIdentifiers: true,
324+
documentLoader: method(:documentLoader)
323325
}.merge!(options)
324326

325327
framing_state = {
@@ -369,7 +371,12 @@ def self.frame(input, frame, options = {})
369371

370372
result = []
371373
frame(framing_state, framing_state[:subjects].keys.sort, (expanded_frame.first || {}), options.merge(parent: result))
372-
374+
375+
# Count blank node identifiers used in the document, if pruning
376+
bnodes_to_clear = if options[:pruneBlankNodeIdentifiers]
377+
count_blank_node_identifiers(result).collect {|k, v| k if v == 1}.compact
378+
end
379+
373380
# Initalize context from frame
374381
@context = @context.parse(frame['@context'])
375382
# Compact result
@@ -380,7 +387,7 @@ def self.frame(input, frame, options = {})
380387
kwgraph = context.compact_iri('@graph', quiet: true)
381388
result = context.serialize.merge({kwgraph => compacted})
382389
log_debug(".frame") {"after compact: #{result.to_json(JSON_STATE) rescue 'malformed json'}"}
383-
result = cleanup_preserve(result)
390+
result = cleanup_preserve(result, bnodes_to_clear || [])
384391
end
385392

386393
block_given? ? yield(result) : result

lib/json/ld/frame.rb

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -184,23 +184,48 @@ def frame(state, subjects, frame, **options)
184184
end
185185

186186
##
187-
# Replace @preserve keys with the values, also replace @null with null
187+
# Recursively find and count blankNode identifiers.
188+
# @return [Hash{String => Integer}]
189+
def count_blank_node_identifiers(input, results = {})
190+
case input
191+
when Array
192+
input.map {|o| count_blank_node_identifiers(o, results)}
193+
when Hash
194+
input.each do |k, v|
195+
count_blank_node_identifiers(v, results)
196+
end
197+
when String
198+
if input.start_with?('_:')
199+
results[input] ||= 0
200+
results[input] += 1
201+
end
202+
end
203+
results
204+
end
205+
206+
##
207+
# Replace @preserve keys with the values, also replace @null with null.
208+
#
209+
# Optionally, remove BNode identifiers only used once.
188210
#
189211
# @param [Array, Hash] input
212+
# @param [Array<String>] bnodes_to_clear
190213
# @return [Array, Hash]
191-
def cleanup_preserve(input)
214+
def cleanup_preserve(input, bnodes_to_clear)
192215
result = case input
193216
when Array
194217
# If, after replacement, an array contains only the value null remove the value, leaving an empty array.
195-
input.map {|o| cleanup_preserve(o)}.compact
218+
input.map {|o| cleanup_preserve(o, bnodes_to_clear)}.compact
196219
when Hash
197220
output = Hash.new
198221
input.each do |key, value|
199222
if key == '@preserve'
200223
# replace all key-value pairs where the key is @preserve with the value from the key-pair
201-
output = cleanup_preserve(value)
224+
output = cleanup_preserve(value, bnodes_to_clear)
225+
elsif context.expand_iri(key) == '@id' && bnodes_to_clear.include?(value)
226+
# Don't add this to output, as it is pruned as being superfluous
202227
else
203-
v = cleanup_preserve(value)
228+
v = cleanup_preserve(value, bnodes_to_clear)
204229

205230
# Because we may have added a null value to an array, we need to clean that up, if we possible
206231
v = v.first if v.is_a?(Array) && v.length == 1 &&

spec/frame_spec.rb

Lines changed: 88 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -654,14 +654,11 @@
654654
"result": {"@id": "mf:result", "@type": "xsd:boolean"}
655655
},
656656
"@graph": [{
657-
"@id": "_:b0",
658657
"@type": "mf:Manifest",
659658
"comment": "Positive processor tests",
660659
"entries": [{
661-
"@id": "_:b1",
662660
"@type": "mf:ManifestEntry",
663661
"action": {
664-
"@id": "_:b2",
665662
"@type": "mq:QueryTest",
666663
"data": "http://www.w3.org/TR/microdata-rdf/tests/0001.html",
667664
"query": "http://www.w3.org/TR/microdata-rdf/tests/0001.ttl"
@@ -1135,9 +1132,7 @@
11351132
"@graph": [{
11361133
"@id": "urn:id-1",
11371134
"@type": "Class",
1138-
"preserve": {
1139-
"@id": "_:b0"
1140-
}
1135+
"preserve": {}
11411136
}]
11421137
})
11431138
},
@@ -1260,9 +1255,7 @@
12601255
"term": "foo"
12611256
},
12621257
"preserve": {
1263-
"@id": "_:b0",
12641258
"deep": {
1265-
"@id": "_:b1",
12661259
"@graph": [{
12671260
"@id": "urn:id-3",
12681261
"term": "bar"
@@ -1338,23 +1331,96 @@
13381331
end
13391332
end
13401333

1334+
describe "pruneBlankNodeIdentifiers" do
1335+
it "preserves single-use bnode identifiers if option set to false" do
1336+
do_frame(
1337+
input: %({
1338+
"@context": {
1339+
"dc0": "http://purl.org/dc/terms/",
1340+
"dc:creator": {
1341+
"@type": "@id"
1342+
},
1343+
"foaf": "http://xmlns.com/foaf/0.1/",
1344+
"ps": "http://purl.org/payswarm#"
1345+
},
1346+
"@id": "http://example.com/asset",
1347+
"@type": "ps:Asset",
1348+
"dc:creator": {
1349+
"foaf:name": "John Doe"
1350+
}
1351+
}),
1352+
frame: %({
1353+
"@context": {
1354+
"dc": "http://purl.org/dc/terms/",
1355+
"dc:creator": {
1356+
"@type": "@id"
1357+
},
1358+
"foaf": "http://xmlns.com/foaf/0.1/",
1359+
"ps": "http://purl.org/payswarm#"
1360+
},
1361+
"@id": "http://example.com/asset",
1362+
"@type": "ps:Asset",
1363+
"dc:creator": {}
1364+
}),
1365+
output: %({
1366+
"@context": {
1367+
"dc": "http://purl.org/dc/terms/",
1368+
"dc:creator": {
1369+
"@type": "@id"
1370+
},
1371+
"foaf": "http://xmlns.com/foaf/0.1/",
1372+
"ps": "http://purl.org/payswarm#"
1373+
},
1374+
"@graph": [
1375+
{
1376+
"@id": "http://example.com/asset",
1377+
"@type": "ps:Asset",
1378+
"dc:creator": {
1379+
"@id": "_:b0",
1380+
"foaf:name": "John Doe"
1381+
}
1382+
}
1383+
]
1384+
}),
1385+
prune: false
1386+
)
1387+
end
1388+
end
1389+
13411390
context "problem cases" do
13421391
it "pr #20" do
1343-
expanded = [
1392+
expanded = %([
13441393
{
1345-
"@id"=>"_:gregg",
1346-
"@type"=>"http://xmlns.com/foaf/0.1/Person",
1347-
"http://xmlns.com/foaf/0.1/name" => "Gregg Kellogg"
1394+
"@id": "_:gregg",
1395+
"@type": "http://xmlns.com/foaf/0.1/Person",
1396+
"http://xmlns.com/foaf/0.1/name": "Gregg Kellogg"
13481397
}, {
1349-
"@id"=>"http://manu.sporny.org/#me",
1350-
"@type"=> "http://xmlns.com/foaf/0.1/Person",
1351-
"http://xmlns.com/foaf/0.1/knows"=> {"@id"=>"_:gregg"},
1352-
"http://xmlns.com/foaf/0.1/name"=>"Manu Sporny"
1398+
"@id": "http://manu.sporny.org/#me",
1399+
"@type": "http://xmlns.com/foaf/0.1/Person",
1400+
"http://xmlns.com/foaf/0.1/knows": {"@id": "_:gregg"},
1401+
"http://xmlns.com/foaf/0.1/name": "Manu Sporny"
13531402
}
1354-
]
1355-
framed = JSON::LD::API.frame(expanded, {})
1356-
data = framed["@graph"].first
1357-
expect(data["mising_value"]).to be_nil
1403+
])
1404+
expected = %({
1405+
"@graph": [
1406+
{
1407+
"@id": "_:b0",
1408+
"@type": "http://xmlns.com/foaf/0.1/Person",
1409+
"http://xmlns.com/foaf/0.1/name": "Gregg Kellogg"
1410+
},
1411+
{
1412+
"@id": "http://manu.sporny.org/#me",
1413+
"@type": "http://xmlns.com/foaf/0.1/Person",
1414+
"http://xmlns.com/foaf/0.1/knows": {
1415+
"@id": "_:b0",
1416+
"@type": "http://xmlns.com/foaf/0.1/Person",
1417+
"http://xmlns.com/foaf/0.1/name": "Gregg Kellogg"
1418+
},
1419+
"http://xmlns.com/foaf/0.1/name": "Manu Sporny"
1420+
}
1421+
]
1422+
})
1423+
do_frame(input: expanded, frame: {}, output: expected)
13581424
end
13591425

13601426
it "issue #28" do
@@ -1417,11 +1483,11 @@
14171483

14181484
def do_frame(params)
14191485
begin
1420-
input, frame, output = params[:input], params[:frame], params[:output]
1486+
input, frame, output, prune = params[:input], params[:frame], params[:output], params.fetch(:prune, true)
14211487
input = ::JSON.parse(input) if input.is_a?(String)
14221488
frame = ::JSON.parse(frame) if frame.is_a?(String)
14231489
output = ::JSON.parse(output) if output.is_a?(String)
1424-
jld = JSON::LD::API.frame(input, frame, logger: logger)
1490+
jld = JSON::LD::API.frame(input, frame, logger: logger, pruneBlankNodeIdentifiers: prune)
14251491
expect(jld).to produce(output, logger)
14261492
rescue JSON::LD::JsonLdError => e
14271493
fail("#{e.class}: #{e.message}\n" +

0 commit comments

Comments
 (0)