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
@@ -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