json-ld 3.0.2 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -6,60 +6,60 @@ describe JSON::LD::API do
6
6
 
7
7
  describe ".flatten" do
8
8
  {
9
- "single object" => {
10
- input: {"@id" => "http://example.com", "@type" => RDF::RDFS.Resource.to_s},
11
- output: [
12
- {"@id" => "http://example.com", "@type" => [RDF::RDFS.Resource.to_s]}
13
- ]
9
+ "single object": {
10
+ input: %({"@id": "http://example.com", "@type": "http://www.w3.org/2000/01/rdf-schema#Resource"}),
11
+ output: %([
12
+ {"@id": "http://example.com", "@type": ["http://www.w3.org/2000/01/rdf-schema#Resource"]}
13
+ ])
14
14
  },
15
- "embedded object" => {
16
- input: {
17
- "@context" => {
18
- "foaf" => RDF::Vocab::FOAF.to_s
15
+ "embedded object": {
16
+ input: %({
17
+ "@context": {
18
+ "foaf": "http://xmlns.com/foaf/0.1/"
19
19
  },
20
- "@id" => "http://greggkellogg.net/foaf",
21
- "@type" => "http://xmlns.com/foaf/0.1/PersonalProfileDocument",
22
- "foaf:primaryTopic" => [{
23
- "@id" => "http://greggkellogg.net/foaf#me",
24
- "@type" => "http://xmlns.com/foaf/0.1/Person"
20
+ "@id": "http://greggkellogg.net/foaf",
21
+ "@type": "http://xmlns.com/foaf/0.1/PersonalProfileDocument",
22
+ "foaf:primaryTopic": [{
23
+ "@id": "http://greggkellogg.net/foaf#me",
24
+ "@type": "http://xmlns.com/foaf/0.1/Person"
25
25
  }]
26
- },
27
- output: [
26
+ }),
27
+ output: %([
28
28
  {
29
- "@id" => "http://greggkellogg.net/foaf",
30
- "@type" => ["http://xmlns.com/foaf/0.1/PersonalProfileDocument"],
31
- "http://xmlns.com/foaf/0.1/primaryTopic" => [{"@id" => "http://greggkellogg.net/foaf#me"}]
29
+ "@id": "http://greggkellogg.net/foaf",
30
+ "@type": ["http://xmlns.com/foaf/0.1/PersonalProfileDocument"],
31
+ "http://xmlns.com/foaf/0.1/primaryTopic": [{"@id": "http://greggkellogg.net/foaf#me"}]
32
32
  },
33
33
  {
34
- "@id" => "http://greggkellogg.net/foaf#me",
35
- "@type" => ["http://xmlns.com/foaf/0.1/Person"]
34
+ "@id": "http://greggkellogg.net/foaf#me",
35
+ "@type": ["http://xmlns.com/foaf/0.1/Person"]
36
36
  }
37
- ]
37
+ ])
38
38
  },
39
- "embedded anon" => {
40
- input: {
41
- "@context" => {
42
- "foaf" => RDF::Vocab::FOAF.to_s
39
+ "embedded anon": {
40
+ input: %({
41
+ "@context": {
42
+ "foaf": "http://xmlns.com/foaf/0.1/"
43
43
  },
44
- "@id" => "http://greggkellogg.net/foaf",
45
- "@type" => "foaf:PersonalProfileDocument",
46
- "foaf:primaryTopic" => {
47
- "@type" => "foaf:Person"
44
+ "@id": "http://greggkellogg.net/foaf",
45
+ "@type": "foaf:PersonalProfileDocument",
46
+ "foaf:primaryTopic": {
47
+ "@type": "foaf:Person"
48
48
  }
49
- },
50
- output: [
49
+ }),
50
+ output: %([
51
51
  {
52
- "@id" => "_:b0",
53
- "@type" => [RDF::Vocab::FOAF.Person.to_s]
52
+ "@id": "_:b0",
53
+ "@type": ["http://xmlns.com/foaf/0.1/Person"]
54
54
  },
55
55
  {
56
- "@id" => "http://greggkellogg.net/foaf",
57
- "@type" => [RDF::Vocab::FOAF.PersonalProfileDocument.to_s],
58
- RDF::Vocab::FOAF.primaryTopic.to_s => [{"@id" => "_:b0"}]
56
+ "@id": "http://greggkellogg.net/foaf",
57
+ "@type": ["http://xmlns.com/foaf/0.1/PersonalProfileDocument"],
58
+ "http://xmlns.com/foaf/0.1/primaryTopic": [{"@id": "_:b0"}]
59
59
  }
60
- ]
60
+ ])
61
61
  },
62
- "reverse properties" => {
62
+ "reverse properties": {
63
63
  input: %([
64
64
  {
65
65
  "@id": "http://example.com/people/markus",
@@ -103,7 +103,7 @@ describe JSON::LD::API do
103
103
  }
104
104
  ])
105
105
  },
106
- "Simple named graph (Wikidata)" => {
106
+ "Simple named graph (Wikidata)": {
107
107
  input: %q({
108
108
  "@context": {
109
109
  "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
@@ -156,7 +156,7 @@ describe JSON::LD::API do
156
156
  }]
157
157
  }]),
158
158
  },
159
- "Test Manifest (shortened)" => {
159
+ "Test Manifest (shortened)": {
160
160
  input: %q{
161
161
  {
162
162
  "@id": "",
@@ -180,7 +180,7 @@ describe JSON::LD::API do
180
180
  }]
181
181
  },
182
182
  },
183
- "@reverse bnode issue (0045)" => {
183
+ "@reverse bnode issue (0045)": {
184
184
  input: %q{
185
185
  {
186
186
  "@context": {
@@ -234,7 +234,7 @@ describe JSON::LD::API do
234
234
  }
235
235
  ])
236
236
  },
237
- "coerced @list containing an deep list" => {
237
+ "coerced @list containing an deep list": {
238
238
  input: %([{
239
239
  "http://example.com/foo": [{"@list": [{"@list": [{"@list": [{"@value": "baz"}]}]}]}]
240
240
  }]),
@@ -243,7 +243,7 @@ describe JSON::LD::API do
243
243
  "http://example.com/foo": [{"@list": [{"@list": [{"@list": [{"@value": "baz"}]}]}]}]
244
244
  }]),
245
245
  },
246
- "@list containing empty @list" => {
246
+ "@list containing empty @list": {
247
247
  input: %({
248
248
  "http://example.com/foo": {"@list": [{"@list": []}]}
249
249
  }),
@@ -252,7 +252,7 @@ describe JSON::LD::API do
252
252
  "http://example.com/foo": [{"@list": [{"@list": []}]}]
253
253
  }])
254
254
  },
255
- "coerced @list containing mixed list values" => {
255
+ "coerced @list containing mixed list values": {
256
256
  input: %({
257
257
  "@context": {"foo": {"@id": "http://example.com/foo", "@container": "@list"}},
258
258
  "foo": [
@@ -282,6 +282,387 @@ describe JSON::LD::API do
282
282
  }.each do |title, params|
283
283
  it(title) {run_flatten(params)}
284
284
  end
285
+
286
+ context "@included" do
287
+ {
288
+ "Basic Included array": {
289
+ input: %({
290
+ "@context": {
291
+ "@version": 1.1,
292
+ "@vocab": "http://example.org/"
293
+ },
294
+ "prop": "value",
295
+ "@included": [{
296
+ "prop": "value2"
297
+ }]
298
+ }),
299
+ output: %([{
300
+ "@id": "_:b0",
301
+ "http://example.org/prop": [{"@value": "value"}]
302
+ }, {
303
+ "@id": "_:b1",
304
+ "http://example.org/prop": [{"@value": "value2"}]
305
+ }])
306
+ },
307
+ "Basic Included object": {
308
+ input: %({
309
+ "@context": {
310
+ "@version": 1.1,
311
+ "@vocab": "http://example.org/"
312
+ },
313
+ "prop": "value",
314
+ "@included": {
315
+ "prop": "value2"
316
+ }
317
+ }),
318
+ output: %([{
319
+ "@id": "_:b0",
320
+ "http://example.org/prop": [{"@value": "value"}]
321
+ }, {
322
+ "@id": "_:b1",
323
+ "http://example.org/prop": [{"@value": "value2"}]
324
+ }])
325
+ },
326
+ "Multiple properties mapping to @included are folded together": {
327
+ input: %({
328
+ "@context": {
329
+ "@version": 1.1,
330
+ "@vocab": "http://example.org/",
331
+ "included1": "@included",
332
+ "included2": "@included"
333
+ },
334
+ "included1": {"prop": "value1"},
335
+ "included2": {"prop": "value2"}
336
+ }),
337
+ output: %([{
338
+ "@id": "_:b1",
339
+ "http://example.org/prop": [{"@value": "value1"}]
340
+ }, {
341
+ "@id": "_:b2",
342
+ "http://example.org/prop": [{"@value": "value2"}]
343
+ }])
344
+ },
345
+ "Included containing @included": {
346
+ input: %({
347
+ "@context": {
348
+ "@version": 1.1,
349
+ "@vocab": "http://example.org/"
350
+ },
351
+ "prop": "value",
352
+ "@included": {
353
+ "prop": "value2",
354
+ "@included": {
355
+ "prop": "value3"
356
+ }
357
+ }
358
+ }),
359
+ output: %([{
360
+ "@id": "_:b0",
361
+ "http://example.org/prop": [{"@value": "value"}]
362
+ }, {
363
+ "@id": "_:b1",
364
+ "http://example.org/prop": [{"@value": "value2"}]
365
+ }, {
366
+ "@id": "_:b2",
367
+ "http://example.org/prop": [{"@value": "value3"}]
368
+ }])
369
+ },
370
+ "Property value with @included": {
371
+ input: %({
372
+ "@context": {
373
+ "@version": 1.1,
374
+ "@vocab": "http://example.org/"
375
+ },
376
+ "prop": {
377
+ "@type": "Foo",
378
+ "@included": {
379
+ "@type": "Bar"
380
+ }
381
+ }
382
+ }),
383
+ output: %([{
384
+ "@id": "_:b0",
385
+ "http://example.org/prop": [
386
+ {"@id": "_:b1"}
387
+ ]
388
+ }, {
389
+ "@id": "_:b1",
390
+ "@type": ["http://example.org/Foo"]
391
+ }, {
392
+ "@id": "_:b2",
393
+ "@type": ["http://example.org/Bar"]
394
+ }])
395
+ },
396
+ "json.api example": {
397
+ input: %({
398
+ "@context": {
399
+ "@version": 1.1,
400
+ "@vocab": "http://example.org/vocab#",
401
+ "@base": "http://example.org/base/",
402
+ "id": "@id",
403
+ "type": "@type",
404
+ "data": "@nest",
405
+ "attributes": "@nest",
406
+ "links": "@nest",
407
+ "relationships": "@nest",
408
+ "included": "@included",
409
+ "self": {"@type": "@id"},
410
+ "related": {"@type": "@id"},
411
+ "comments": {
412
+ "@context": {
413
+ "data": null
414
+ }
415
+ }
416
+ },
417
+ "data": [{
418
+ "type": "articles",
419
+ "id": "1",
420
+ "attributes": {
421
+ "title": "JSON:API paints my bikeshed!"
422
+ },
423
+ "links": {
424
+ "self": "http://example.com/articles/1"
425
+ },
426
+ "relationships": {
427
+ "author": {
428
+ "links": {
429
+ "self": "http://example.com/articles/1/relationships/author",
430
+ "related": "http://example.com/articles/1/author"
431
+ },
432
+ "data": { "type": "people", "id": "9" }
433
+ },
434
+ "comments": {
435
+ "links": {
436
+ "self": "http://example.com/articles/1/relationships/comments",
437
+ "related": "http://example.com/articles/1/comments"
438
+ },
439
+ "data": [
440
+ { "type": "comments", "id": "5" },
441
+ { "type": "comments", "id": "12" }
442
+ ]
443
+ }
444
+ }
445
+ }],
446
+ "included": [{
447
+ "type": "people",
448
+ "id": "9",
449
+ "attributes": {
450
+ "first-name": "Dan",
451
+ "last-name": "Gebhardt",
452
+ "twitter": "dgeb"
453
+ },
454
+ "links": {
455
+ "self": "http://example.com/people/9"
456
+ }
457
+ }, {
458
+ "type": "comments",
459
+ "id": "5",
460
+ "attributes": {
461
+ "body": "First!"
462
+ },
463
+ "relationships": {
464
+ "author": {
465
+ "data": { "type": "people", "id": "2" }
466
+ }
467
+ },
468
+ "links": {
469
+ "self": "http://example.com/comments/5"
470
+ }
471
+ }, {
472
+ "type": "comments",
473
+ "id": "12",
474
+ "attributes": {
475
+ "body": "I like XML better"
476
+ },
477
+ "relationships": {
478
+ "author": {
479
+ "data": { "type": "people", "id": "9" }
480
+ }
481
+ },
482
+ "links": {
483
+ "self": "http://example.com/comments/12"
484
+ }
485
+ }]
486
+ }),
487
+ output: %([{
488
+ "@id": "_:b0",
489
+ "http://example.org/vocab#self": [{"@id": "http://example.com/articles/1/relationships/comments"}
490
+ ],
491
+ "http://example.org/vocab#related": [{"@id": "http://example.com/articles/1/comments"}]
492
+ }, {
493
+ "@id": "http://example.org/base/1",
494
+ "@type": ["http://example.org/vocab#articles"],
495
+ "http://example.org/vocab#title": [{"@value": "JSON:API paints my bikeshed!"}],
496
+ "http://example.org/vocab#self": [{"@id": "http://example.com/articles/1"}],
497
+ "http://example.org/vocab#author": [{"@id": "http://example.org/base/9"}],
498
+ "http://example.org/vocab#comments": [{"@id": "_:b0"}]
499
+ }, {
500
+ "@id": "http://example.org/base/12",
501
+ "@type": ["http://example.org/vocab#comments"],
502
+ "http://example.org/vocab#body": [{"@value": "I like XML better"}],
503
+ "http://example.org/vocab#author": [{"@id": "http://example.org/base/9"}],
504
+ "http://example.org/vocab#self": [{"@id": "http://example.com/comments/12"}]
505
+ }, {
506
+ "@id": "http://example.org/base/2",
507
+ "@type": ["http://example.org/vocab#people"]
508
+ }, {
509
+ "@id": "http://example.org/base/5",
510
+ "@type": ["http://example.org/vocab#comments"],
511
+ "http://example.org/vocab#body": [{"@value": "First!"}
512
+ ],
513
+ "http://example.org/vocab#author": [{"@id": "http://example.org/base/2"}],
514
+ "http://example.org/vocab#self": [{"@id": "http://example.com/comments/5"}]
515
+ }, {
516
+ "@id": "http://example.org/base/9",
517
+ "@type": ["http://example.org/vocab#people"],
518
+ "http://example.org/vocab#first-name": [{"@value": "Dan"}],
519
+ "http://example.org/vocab#last-name": [{"@value": "Gebhardt"}],
520
+ "http://example.org/vocab#twitter": [{"@value": "dgeb"}],
521
+ "http://example.org/vocab#self": [
522
+ {"@id": "http://example.com/people/9"},
523
+ {"@id": "http://example.com/articles/1/relationships/author"}
524
+ ],
525
+ "http://example.org/vocab#related": [{"@id": "http://example.com/articles/1/author"}]
526
+ }])
527
+ },
528
+ }.each do |title, params|
529
+ it(title) {run_flatten(params)}
530
+ end
531
+ end
532
+ end
533
+
534
+ context "html" do
535
+ {
536
+ "Flattens embedded JSON-LD script element": {
537
+ input: %(
538
+ <html>
539
+ <head>
540
+ <script type="application/ld+json">
541
+ {
542
+ "@context": {
543
+ "foo": {"@id": "http://example.com/foo", "@container": "@list"}
544
+ },
545
+ "foo": [{"@value": "bar"}]
546
+ }
547
+ </script>
548
+ </head>
549
+ </html>),
550
+ context: %({"foo": {"@id": "http://example.com/foo", "@container": "@list"}}),
551
+ output: %({
552
+ "@context": {
553
+ "foo": {"@id": "http://example.com/foo", "@container": "@list"}
554
+ },
555
+ "@graph": [{"@id": "_:b0","foo": ["bar"]}]
556
+ })
557
+ },
558
+ "Flattens first script element with extractAllScripts: false": {
559
+ input: %(
560
+ <html>
561
+ <head>
562
+ <script type="application/ld+json">
563
+ {
564
+ "@context": {
565
+ "foo": {"@id": "http://example.com/foo", "@container": "@list"}
566
+ },
567
+ "foo": [{"@value": "bar"}]
568
+ }
569
+ </script>
570
+ <script type="application/ld+json">
571
+ {
572
+ "@context": {"ex": "http://example.com/"},
573
+ "@graph": [
574
+ {"ex:foo": {"@value": "foo"}},
575
+ {"ex:bar": {"@value": "bar"}}
576
+ ]
577
+ }
578
+ </script>
579
+ </head>
580
+ </html>),
581
+ context: %({"foo": {"@id": "http://example.com/foo", "@container": "@list"}}),
582
+ output: %({
583
+ "@context": {
584
+ "foo": {"@id": "http://example.com/foo", "@container": "@list"}
585
+ },
586
+ "@graph": [{"@id": "_:b0","foo": ["bar"]}]
587
+ }),
588
+ extractAllScripts: false
589
+ },
590
+ "Flattens targeted script element": {
591
+ input: %(
592
+ <html>
593
+ <head>
594
+ <script id="first" type="application/ld+json">
595
+ {
596
+ "@context": {
597
+ "foo": {"@id": "http://example.com/foo", "@container": "@list"}
598
+ },
599
+ "foo": [{"@value": "bar"}]
600
+ }
601
+ </script>
602
+ <script id="second" type="application/ld+json">
603
+ {
604
+ "@context": {"ex": "http://example.com/"},
605
+ "@graph": [
606
+ {"ex:foo": {"@value": "foo"}},
607
+ {"ex:bar": {"@value": "bar"}}
608
+ ]
609
+ }
610
+ </script>
611
+ </head>
612
+ </html>),
613
+ context: %({"ex": "http://example.com/"}),
614
+ output: %({
615
+ "@context": {"ex": "http://example.com/"},
616
+ "@graph": [
617
+ {"@id": "_:b0", "ex:foo": "foo"},
618
+ {"@id": "_:b1", "ex:bar": "bar"}
619
+ ]
620
+ }),
621
+ base: "http://example.org/doc#second"
622
+ },
623
+ "Flattens all script elements by default": {
624
+ input: %(
625
+ <html>
626
+ <head>
627
+ <script type="application/ld+json">
628
+ {
629
+ "@context": {
630
+ "foo": {"@id": "http://example.com/foo", "@container": "@list"}
631
+ },
632
+ "foo": [{"@value": "bar"}]
633
+ }
634
+ </script>
635
+ <script type="application/ld+json">
636
+ [
637
+ {"http://example.com/foo": {"@value": "foo"}},
638
+ {"http://example.com/bar": {"@value": "bar"}}
639
+ ]
640
+ </script>
641
+ </head>
642
+ </html>),
643
+ context: %({
644
+ "ex": "http://example.com/",
645
+ "foo": {"@id": "http://example.com/foo", "@container": "@list"}
646
+ }),
647
+ output: %({
648
+ "@context": {
649
+ "ex": "http://example.com/",
650
+ "foo": {"@id": "http://example.com/foo", "@container": "@list"}
651
+ },
652
+ "@graph": [
653
+ {"@id": "_:b0", "foo": ["bar"]},
654
+ {"@id": "_:b1", "ex:foo": "foo"},
655
+ {"@id": "_:b2", "ex:bar": "bar"}
656
+ ]
657
+ })
658
+ },
659
+ }.each do |title, params|
660
+ it(title) do
661
+ params[:input] = StringIO.new(params[:input])
662
+ params[:input].send(:define_singleton_method, :content_type) {"text/html"}
663
+ run_flatten params.merge(validate: true)
664
+ end
665
+ end
285
666
  end
286
667
 
287
668
  def run_flatten(params)
@@ -289,11 +670,17 @@ describe JSON::LD::API do
289
670
  input = ::JSON.parse(input) if input.is_a?(String)
290
671
  output = ::JSON.parse(output) if output.is_a?(String)
291
672
  context = ::JSON.parse(context) if context.is_a?(String)
673
+ params[:base] ||= nil
292
674
  pending params.fetch(:pending, "test implementation") unless input
293
675
  if params[:exception]
294
- expect {JSON::LD::API.flatten(input, context, params.merge(logger: logger))}.to raise_error(params[:exception])
676
+ expect {JSON::LD::API.flatten(input, context, logger: logger, **params)}.to raise_error(params[:exception])
295
677
  else
296
- jld = JSON::LD::API.flatten(input, context, params.merge(logger: logger))
678
+ jld = nil
679
+ if params[:write]
680
+ expect{jld = JSON::LD::API.flatten(input, context, logger: logger, **params)}.to write(params[:write]).to(:error)
681
+ else
682
+ expect{jld = JSON::LD::API.flatten(input, context, logger: logger, **params)}.not_to write.to(:error)
683
+ end
297
684
  expect(jld).to produce_jsonld(output, logger)
298
685
  end
299
686
  end