json-ld 3.0.2 → 3.1.0

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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHORS +1 -1
  3. data/README.md +90 -53
  4. data/UNLICENSE +1 -1
  5. data/VERSION +1 -1
  6. data/bin/jsonld +4 -4
  7. data/lib/json/ld.rb +27 -10
  8. data/lib/json/ld/api.rb +325 -96
  9. data/lib/json/ld/compact.rb +75 -27
  10. data/lib/json/ld/conneg.rb +188 -0
  11. data/lib/json/ld/context.rb +677 -292
  12. data/lib/json/ld/expand.rb +240 -75
  13. data/lib/json/ld/flatten.rb +5 -3
  14. data/lib/json/ld/format.rb +19 -19
  15. data/lib/json/ld/frame.rb +135 -85
  16. data/lib/json/ld/from_rdf.rb +44 -17
  17. data/lib/json/ld/html/nokogiri.rb +151 -0
  18. data/lib/json/ld/html/rexml.rb +186 -0
  19. data/lib/json/ld/reader.rb +25 -5
  20. data/lib/json/ld/resource.rb +2 -2
  21. data/lib/json/ld/streaming_writer.rb +3 -1
  22. data/lib/json/ld/to_rdf.rb +47 -17
  23. data/lib/json/ld/utils.rb +4 -2
  24. data/lib/json/ld/writer.rb +75 -14
  25. data/spec/api_spec.rb +13 -34
  26. data/spec/compact_spec.rb +968 -9
  27. data/spec/conneg_spec.rb +373 -0
  28. data/spec/context_spec.rb +447 -53
  29. data/spec/expand_spec.rb +1872 -416
  30. data/spec/flatten_spec.rb +434 -47
  31. data/spec/frame_spec.rb +979 -344
  32. data/spec/from_rdf_spec.rb +305 -5
  33. data/spec/spec_helper.rb +177 -0
  34. data/spec/streaming_writer_spec.rb +4 -4
  35. data/spec/suite_compact_spec.rb +2 -2
  36. data/spec/suite_expand_spec.rb +14 -2
  37. data/spec/suite_flatten_spec.rb +10 -2
  38. data/spec/suite_frame_spec.rb +3 -2
  39. data/spec/suite_from_rdf_spec.rb +2 -2
  40. data/spec/suite_helper.rb +55 -20
  41. data/spec/suite_html_spec.rb +22 -0
  42. data/spec/suite_http_spec.rb +35 -0
  43. data/spec/suite_remote_doc_spec.rb +2 -2
  44. data/spec/suite_to_rdf_spec.rb +14 -3
  45. data/spec/support/extensions.rb +5 -1
  46. data/spec/test-files/test-4-input.json +3 -3
  47. data/spec/test-files/test-5-input.json +2 -2
  48. data/spec/test-files/test-8-framed.json +14 -18
  49. data/spec/to_rdf_spec.rb +606 -16
  50. data/spec/writer_spec.rb +5 -5
  51. metadata +144 -88
@@ -111,13 +111,13 @@ describe JSON::LD::StreamingWriter do
111
111
  t.logger.info "source: #{t.input}"
112
112
  specify "#{t.property('@id')}: #{t.name}" do
113
113
  repo = RDF::Repository.load(t.input_loc, format: :nquads)
114
- jsonld = JSON::LD::Writer.buffer(stream: true, context: ctx, logger: t.logger) do |writer|
114
+ jsonld = JSON::LD::Writer.buffer(stream: true, context: ctx, logger: t.logger, **t.options) do |writer|
115
115
  writer << repo
116
116
  end
117
117
  t.logger.info "Generated: #{jsonld}"
118
118
 
119
119
  # And then, re-generate jsonld as RDF
120
- expect(parse(jsonld, format: :jsonld)).to be_equivalent_graph(repo, t)
120
+ expect(parse(jsonld, format: :jsonld, **t.options)).to be_equivalent_graph(repo, t)
121
121
  end
122
122
  end
123
123
  end
@@ -127,12 +127,12 @@ describe JSON::LD::StreamingWriter do
127
127
 
128
128
  def parse(input, format: :trig, **options)
129
129
  reader = RDF::Reader.for(format)
130
- RDF::Repository.new << reader.new(input, options)
130
+ RDF::Repository.new << reader.new(input, **options)
131
131
  end
132
132
 
133
133
  # Serialize ntstr to a string and compare against regexps
134
134
  def serialize(ntstr, **options)
135
- g = ntstr.is_a?(String) ? parse(ntstr, options) : ntstr
135
+ g = ntstr.is_a?(String) ? parse(ntstr, **options) : ntstr
136
136
  logger = RDF::Spec.logger
137
137
  logger.info(g.dump(:ttl))
138
138
  result = JSON::LD::Writer.buffer(logger: logger, stream: true, **options) do |writer|
@@ -9,12 +9,12 @@ describe JSON::LD do
9
9
  m.entries.each do |t|
10
10
  specify "#{t.property('@id')}: #{t.name} unordered#{' (negative test)' unless t.positiveTest?}" do
11
11
  t.options[:ordered] = false
12
- t.run self
12
+ expect{t.run self}.not_to write.to(:error)
13
13
  end
14
14
 
15
15
  specify "#{t.property('@id')}: #{t.name} ordered#{' (negative test)' unless t.positiveTest?}" do
16
16
  t.options[:ordered] = true
17
- t.run self
17
+ expect {t.run self}.not_to write.to(:error)
18
18
  end
19
19
  end
20
20
  end
@@ -9,12 +9,24 @@ describe JSON::LD do
9
9
  m.entries.each do |t|
10
10
  specify "#{t.property('@id')}: #{t.name} unordered#{' (negative test)' unless t.positiveTest?}" do
11
11
  t.options[:ordered] = false
12
- t.run self
12
+ if %w(#t0068).include?(t.property('@id'))
13
+ expect{t.run self}.to write("[DEPRECATION]").to(:error)
14
+ elsif %w(#t0005 #tpr34 #tpr35 #tpr36 #tpr37 #t0119 #t0120).include?(t.property('@id'))
15
+ expect{t.run self}.to write("beginning with '@' are reserved for future use").to(:error)
16
+ else
17
+ expect {t.run self}.not_to write.to(:error)
18
+ end
13
19
  end
14
20
 
15
21
  specify "#{t.property('@id')}: #{t.name} ordered#{' (negative test)' unless t.positiveTest?}" do
16
22
  t.options[:ordered] = true
17
- t.run self
23
+ if %w(#t0068).include?(t.property('@id'))
24
+ expect{t.run self}.to write("[DEPRECATION]").to(:error)
25
+ elsif %w(#t0005 #tpr34 #tpr35 #tpr36 #tpr37 #t0119 #t0120).include?(t.property('@id'))
26
+ expect{t.run self}.to write("beginning with '@' are reserved for future use").to(:error)
27
+ else
28
+ expect {t.run self}.not_to write.to(:error)
29
+ end
18
30
  end
19
31
  end
20
32
  end
@@ -9,12 +9,20 @@ describe JSON::LD do
9
9
  m.entries.each do |t|
10
10
  specify "#{t.property('@id')}: #{t.name} unordered#{' (negative test)' unless t.positiveTest?}" do
11
11
  t.options[:ordered] = false
12
- t.run self
12
+ if %w(#t0005).include?(t.property('@id'))
13
+ expect{t.run self}.to write("Terms beginning with '@' are reserved for future use").to(:error)
14
+ else
15
+ expect {t.run self}.not_to write.to(:error)
16
+ end
13
17
  end
14
18
 
15
19
  specify "#{t.property('@id')}: #{t.name} ordered#{' (negative test)' unless t.positiveTest?}" do
16
20
  t.options[:ordered] = true
17
- t.run self
21
+ if %w(#t0005).include?(t.property('@id'))
22
+ expect{t.run self}.to write("Terms beginning with '@' are reserved for future use").to(:error)
23
+ else
24
+ expect {t.run self}.not_to write.to(:error)
25
+ end
18
26
  end
19
27
  end
20
28
  end
@@ -9,12 +9,13 @@ describe JSON::LD do
9
9
  m.entries.each do |t|
10
10
  specify "#{t.property('@id')}: #{t.name} unordered#{' (negative test)' unless t.positiveTest?}" do
11
11
  t.options[:ordered] = false
12
- t.run self
12
+ expect {t.run self}.not_to write.to(:error)
13
13
  end
14
14
 
15
15
  specify "#{t.property('@id')}: #{t.name} ordered#{' (negative test)' unless t.positiveTest?}" do
16
+ pending "Ordered version of in03" if %w(#tin03).include?(t.property('@id'))
16
17
  t.options[:ordered] = true
17
- t.run self
18
+ expect {t.run self}.not_to write.to(:error)
18
19
  end
19
20
  end
20
21
  end
@@ -9,12 +9,12 @@ describe JSON::LD do
9
9
  m.entries.each do |t|
10
10
  specify "#{t.property('@id')}: #{t.name} unordered#{' (negative test)' unless t.positiveTest?}" do
11
11
  t.options[:ordered] = false
12
- t.run self
12
+ expect {t.run self}.not_to write.to(:error)
13
13
  end
14
14
 
15
15
  specify "#{t.property('@id')}: #{t.name} ordered#{' (negative test)' unless t.positiveTest?}" do
16
16
  t.options[:ordered] = true
17
- t.run self
17
+ expect {t.run self}.not_to write.to(:error)
18
18
  end
19
19
  end
20
20
  end
@@ -26,7 +26,10 @@ module RDF::Util
26
26
  LOCAL_PATHS.each do |r, l|
27
27
  next unless Dir.exist?(l) && filename_or_url.start_with?(r)
28
28
  #puts "attempt to open #{filename_or_url} locally"
29
- localpath = filename_or_url.to_s.sub(r, l)
29
+ url_no_frag_or_query = RDF::URI(filename_or_url).dup
30
+ url_no_frag_or_query.query = nil
31
+ url_no_frag_or_query.fragment = nil
32
+ localpath = url_no_frag_or_query.to_s.sub(r, l)
30
33
  response = begin
31
34
  ::File.open(localpath)
32
35
  rescue Errno::ENOENT => e
@@ -40,9 +43,11 @@ module RDF::Util
40
43
  headers: options.fetch(:headers, {})
41
44
  }
42
45
  #puts "use #{filename_or_url} locally"
43
- document_options[:headers][:content_type] = case filename_or_url.to_s
46
+ document_options[:headers][:content_type] = case localpath
44
47
  when /\.ttl$/ then 'text/turtle'
48
+ when /\.nq$/ then 'application/n-quads'
45
49
  when /\.nt$/ then 'application/n-triples'
50
+ when /\.html$/ then 'text/html'
46
51
  when /\.jsonld$/ then 'application/ld+json'
47
52
  when /\.json$/ then 'application/json'
48
53
  else 'unknown'
@@ -52,7 +57,7 @@ module RDF::Util
52
57
  # For overriding content type from test data
53
58
  document_options[:headers][:content_type] = options[:contentType] if options[:contentType]
54
59
 
55
- remote_document = RDF::Util::File::RemoteDocument.new(response.read, document_options)
60
+ remote_document = RDF::Util::File::RemoteDocument.new(response.read, **document_options)
56
61
  if block_given?
57
62
  return yield remote_document
58
63
  else
@@ -60,7 +65,7 @@ module RDF::Util
60
65
  end
61
66
  end
62
67
 
63
- original_open_file(filename_or_url, options, &block)
68
+ original_open_file(filename_or_url, **options, &block)
64
69
  end
65
70
  end
66
71
  end
@@ -112,15 +117,18 @@ module Fixtures
112
117
  super
113
118
  end
114
119
 
115
- # Base is expanded input file
120
+ # Base is expanded input if not specified
116
121
  def base
117
122
  options.fetch('base', manifest_url.join(property('input')).to_s)
118
123
  end
119
124
 
120
125
  def options
121
126
  @options ||= begin
122
- opts = {documentLoader: Fixtures::SuiteTest.method(:documentLoader)}
123
- opts = {}
127
+ opts = {
128
+ documentLoader: Fixtures::SuiteTest.method(:documentLoader),
129
+ validate: true,
130
+ lowercaseLanguage: true,
131
+ }
124
132
  {'specVersion' => "json-ld-1.1"}.merge(property('option') || {}).each do |k, v|
125
133
  opts[k.to_sym] = v
126
134
  end
@@ -136,7 +144,7 @@ module Fixtures
136
144
  file = self.send("#{m}_loc".to_sym)
137
145
 
138
146
  dl_opts = {safe: true}
139
- RDF::Util::File.open_file(file, dl_opts) do |remote_doc|
147
+ RDF::Util::File.open_file(file, **dl_opts) do |remote_doc|
140
148
  res = remote_doc.read
141
149
  end
142
150
  res
@@ -181,14 +189,15 @@ module Fixtures
181
189
  logger.info "options: #{options.inspect}" unless options.empty?
182
190
  logger.info "frame: #{frame}" if frame_loc
183
191
 
184
- options = self.options.merge(documentLoader: Fixtures::SuiteTest.method(:documentLoader))
185
- options = {validate: true}.merge(options)
186
-
192
+ options = self.options
187
193
  unless options[:specVersion] == "json-ld-1.1"
188
194
  skip "not a 1.1 test"
189
195
  return
190
196
  end
191
197
 
198
+ # Because we're doing exact comparisons when ordered.
199
+ options[:lowercaseLanguage] = true if options[:ordered]
200
+
192
201
  if positiveTest?
193
202
  logger.info "expected: #{expect rescue nil}" if expect_loc
194
203
  begin
@@ -205,16 +214,32 @@ module Fixtures
205
214
  # Use an array, to preserve input order
206
215
  repo = RDF::NQuads::Reader.open(input_loc) do |reader|
207
216
  reader.each_statement.to_a
208
- end.uniq.extend(RDF::Enumerable)
209
- logger.info "repo: #{repo.dump(id == '#t0012' ? :nquads : :trig)}"
217
+ end.to_a.uniq.extend(RDF::Enumerable)
218
+ logger.info "repo: #{repo.dump(self.id == '#t0012' ? :nquads : :trig)}"
210
219
  JSON::LD::API.fromRdf(repo, logger: logger, **options)
211
220
  when "jld:ToRDFTest"
212
221
  repo = RDF::Repository.new
213
222
  JSON::LD::API.toRdf(input_loc, logger: logger, **options) do |statement|
223
+ # To properly compare values of rdf:language and i18n datatypes, normalize to lower case
224
+ if statement.predicate == RDF.to_uri + 'language'
225
+ statement.object = RDF::Literal(statement.object.to_s.downcase) if statement.object.literal?
226
+ elsif statement.object.literal? && statement.object.datatype.to_s.start_with?('https://www.w3.org/ns/i18n#')
227
+ statement.object.datatype = RDF::URI(statement.object.datatype.to_s.downcase)
228
+ end
214
229
  repo << statement
215
230
  end
216
- logger.info "nq: #{repo.to_nquads}"
231
+ logger.info "nq: #{repo.map(&:to_nquads)}"
217
232
  repo
233
+ when "jld:HttpTest"
234
+ res = input_json
235
+ rspec_example.instance_eval do
236
+ # use the parsed input file as @result for Rack Test application
237
+ @results = res
238
+ get "/", {}, "HTTP_ACCEPT" => options.fetch(:httpAccept, ""), "HTTP_LINK" => options.fetch(:httpLink, nil)
239
+ expect(last_response.status).to eq 200
240
+ expect(last_response.content_type).to eq options.fetch(:contentType, "")
241
+ JSON.parse(last_response.body)
242
+ end
218
243
  else
219
244
  fail("Unknown test type: #{testType}")
220
245
  end
@@ -273,14 +298,24 @@ module Fixtures
273
298
  JSON::LD::API.frame(t.input_loc, t.frame_loc, logger: logger, **options)
274
299
  when "jld:FromRDFTest"
275
300
  repo = RDF::Repository.load(t.input_loc)
276
- logger.info "repo: #{repo.dump(id == '#t0012' ? :nquads : :trig)}"
301
+ logger.info "repo: #{repo.dump(t.id == '#t0012' ? :nquads : :trig)}"
277
302
  JSON::LD::API.fromRdf(repo, logger: logger, **options)
303
+ when "jld:HttpTest"
304
+ rspec_example.instance_eval do
305
+ # use the parsed input file as @result for Rack Test application
306
+ @results = t.input_json
307
+ get "/", {}, "HTTP_ACCEPT" => options.fetch(:httpAccept, "")
308
+ expect(last_response.status).to eq t.property('expect')
309
+ expect(last_response.content_type).to eq options.fetch(:contentType, "")
310
+ raise "406" if t.property('expect') == 406
311
+ raise "Expected status #{t.property('expectErrorCode')}, not #{last_response.status}"
312
+ end
278
313
  when "jld:ToRDFTest"
279
314
  JSON::LD::API.toRdf(t.input_loc, logger: logger, **options) {}
280
315
  else
281
316
  success("Unknown test type: #{testType}")
282
317
  end
283
- end.to raise_error(/#{t.property('expect')}/)
318
+ end.to raise_error(/#{t.property('expectErrorCode')}/)
284
319
  else
285
320
  fail("No support for NegativeSyntaxTest")
286
321
  end
@@ -335,16 +370,16 @@ module Fixtures
335
370
  # @param [Hash<Symbol => Object>] options
336
371
  # @option options [Boolean] :validate
337
372
  # Allow only appropriate content types
338
- # @return [RemoteDocument] retrieved remote document and context information unless block given
373
+ # @return [RDF::Util::File::RemoteDocument] retrieved remote document and context information unless block given
339
374
  # @yield remote_document
340
- # @yieldparam [RemoteDocument] remote_document
375
+ # @yieldparam [RDF::Util::File::RemoteDocument] remote_document
341
376
  # @raise [JsonLdError]
342
377
  def documentLoader(url, **options, &block)
343
- options[:headers] ||= JSON::LD::API::OPEN_OPTS[:headers]
378
+ options[:headers] ||= JSON::LD::API::OPEN_OPTS[:headers].dup
344
379
  options[:headers][:link] = Array(options[:httpLink]).join(',') if options[:httpLink]
345
380
 
346
381
  url = url.to_s[5..-1] if url.to_s.start_with?("file:")
347
- JSON::LD::API.documentLoader(url, options, &block)
382
+ JSON::LD::API.documentLoader(url, **options, &block)
348
383
  rescue JSON::LD::JsonLdError::LoadingDocumentFailed, JSON::LD::JsonLdError::MultipleContextLinkHeaders
349
384
  raise unless options[:safe]
350
385
  "don't raise error"
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ require_relative 'spec_helper'
3
+
4
+ describe JSON::LD do
5
+ describe "test suite" do
6
+ require_relative 'suite_helper'
7
+ m = Fixtures::SuiteTest::Manifest.open("#{Fixtures::SuiteTest::SUITE}html-manifest.jsonld")
8
+ describe m.name do
9
+ m.entries.each do |t|
10
+ specify "#{t.property('@id')}: #{t.name} unordered#{' (negative test)' unless t.positiveTest?}" do
11
+ t.options[:ordered] = false
12
+ expect {t.run self}.not_to write.to(:error)
13
+ end
14
+
15
+ specify "#{t.property('@id')}: #{t.name} ordered#{' (negative test)' unless t.positiveTest?}" do
16
+ t.options[:ordered] = true
17
+ expect {t.run self}.not_to write.to(:error)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end unless ENV['CI']
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ require_relative 'spec_helper'
3
+ require 'rack/linkeddata'
4
+ require 'rack/test'
5
+
6
+ begin
7
+ describe JSON::LD do
8
+ describe "test suite" do
9
+ require_relative 'suite_helper'
10
+ m = Fixtures::SuiteTest::Manifest.open("#{Fixtures::SuiteTest::SUITE}http-manifest.jsonld")
11
+ describe m.name do
12
+ include ::Rack::Test::Methods
13
+ before(:all) {JSON::LD::Writer.default_context = "#{Fixtures::SuiteTest::SUITE}http/default-context.jsonld"}
14
+ after(:all) {JSON::LD::Writer.default_context = nil}
15
+ let(:app) do
16
+ JSON::LD::ContentNegotiation.new(
17
+ Rack::LinkedData::ContentNegotiation.new(
18
+ double("Target Rack Application", :call => [200, {}, @results]),
19
+ {}
20
+ )
21
+ )
22
+ end
23
+
24
+ m.entries.each do |t|
25
+ specify "#{t.property('@id')}: #{t.name} unordered#{' (negative test)' unless t.positiveTest?}" do
26
+ t.options[:ordered] = false
27
+ expect {t.run self}.not_to write.to(:error)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end unless ENV['CI']
33
+ rescue IOError
34
+ # Skip this until such a test suite is re-added
35
+ end
@@ -9,12 +9,12 @@ describe JSON::LD do
9
9
  m.entries.each do |t|
10
10
  specify "#{t.property('@id')}: #{t.name} unordered#{' (negative test)' unless t.positiveTest?}" do
11
11
  t.options[:ordered] = false
12
- t.run self
12
+ expect {t.run self}.not_to write.to(:error)
13
13
  end
14
14
 
15
15
  specify "#{t.property('@id')}: #{t.name} ordered#{' (negative test)' unless t.positiveTest?}" do
16
16
  t.options[:ordered] = true
17
- t.run self
17
+ expect {t.run self}.not_to write.to(:error)
18
18
  end
19
19
  end
20
20
  end
@@ -8,9 +8,20 @@ describe JSON::LD do
8
8
  describe m.name do
9
9
  m.entries.each do |t|
10
10
  specify "#{t.property('@id')}: #{t.name}#{' (negative test)' unless t.positiveTest?}" do
11
- skip "Native value fidelity" if %w(toRdf/0035-in.jsonld).include?(t.property('input'))
12
- pending "Generalized RDF" if %w(toRdf/0118-in.jsonld).include?(t.property('input'))
13
- t.run self
11
+ pending "Generalized RDF" if t.options[:produceGeneralizedRdf]
12
+ if %w(#t0118).include?(t.property('@id'))
13
+ expect {t.run self}.to write(/Statement .* is invalid/).to(:error)
14
+ elsif %w(#te075).include?(t.property('@id'))
15
+ expect {t.run self}.to write(/is invalid/).to(:error)
16
+ elsif %w(#te005 #tpr34 #tpr35 #tpr36 #tpr37 #te119 #te120).include?(t.property('@id'))
17
+ expect {t.run self}.to write("beginning with '@' are reserved for future use").to(:error)
18
+ elsif %w(#te068).include?(t.property('@id'))
19
+ expect {t.run self}.to write("[DEPRECATION]").to(:error)
20
+ elsif %w(#twf05).include?(t.property('@id'))
21
+ expect {t.run self}.to write("@language must be valid BCP47").to(:error)
22
+ else
23
+ expect {t.run self}.not_to write.to(:error)
24
+ end
14
25
  end
15
26
  end
16
27
  end
@@ -9,7 +9,11 @@ class Hash
9
9
  return false unless other.is_a?(Hash) && other.length == length
10
10
  all? do |key, value|
11
11
  # List values are still ordered
12
- value.equivalent_jsonld?(other[key], ordered: key == '@list')
12
+ if key == '@language' && value.is_a?(String)
13
+ value.downcase.equivalent_jsonld?(other[key].to_s.downcase, ordered: key == '@list')
14
+ else
15
+ value.equivalent_jsonld?(other[key], ordered: key == '@list')
16
+ end
13
17
  end
14
18
  end
15
19
 
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "@context": {
3
- "": "http://manu.sporny.org/",
3
+ "foo": "http://manu.sporny.org/",
4
4
  "foaf": "http://xmlns.com/foaf/0.1/"
5
5
  },
6
- "@id": ":#me",
6
+ "@id": "foo:#me",
7
7
  "@type": "foaf:Person",
8
8
  "foaf:name": "Manu Sporny",
9
- "foaf:homepage": { "@id": ":" }
9
+ "foaf:homepage": { "@id": "foo:" }
10
10
  }
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "@context": {
3
- "": "http://manu.sporny.org/",
3
+ "foo": "http://manu.sporny.org/",
4
4
  "foaf": "http://xmlns.com/foaf/0.1/"
5
5
  },
6
- "@id": ":#me",
6
+ "@id": "foo:#me",
7
7
  "@type": "foaf:Person",
8
8
  "foaf:name": "Manu Sporny",
9
9
  "foaf:knows": {
@@ -8,22 +8,18 @@
8
8
  "Library": "http://example.org/vocab#Library",
9
9
  "title": "http://purl.org/dc/terms/title"
10
10
  },
11
- "@graph": [
12
- {
13
- "@id": "http://example.com/library",
14
- "@type": "Library",
15
- "contains": {
16
- "@id": "http://example.org/library/the-republic",
17
- "@type": "Book",
18
- "contains": {
19
- "@id": "http://example.org/library/the-republic#introduction",
20
- "@type": "Chapter",
21
- "description": "An introductory chapter on The Republic.",
22
- "title": "The Introduction"
23
- },
24
- "creator": "Plato",
25
- "title": "The Republic"
26
- }
27
- }
28
- ]
11
+ "@id": "http://example.com/library",
12
+ "@type": "Library",
13
+ "contains": {
14
+ "@id": "http://example.org/library/the-republic",
15
+ "@type": "Book",
16
+ "contains": {
17
+ "@id": "http://example.org/library/the-republic#introduction",
18
+ "@type": "Chapter",
19
+ "description": "An introductory chapter on The Republic.",
20
+ "title": "The Introduction"
21
+ },
22
+ "creator": "Plato",
23
+ "title": "The Republic"
24
+ }
29
25
  }