json-ld 3.1.0 → 3.1.5
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.
- checksums.yaml +4 -4
- data/README.md +66 -46
- data/VERSION +1 -1
- data/bin/jsonld +27 -30
- data/lib/json/ld.rb +12 -8
- data/lib/json/ld/api.rb +51 -43
- data/lib/json/ld/compact.rb +82 -68
- data/lib/json/ld/conneg.rb +1 -1
- data/lib/json/ld/context.rb +650 -542
- data/lib/json/ld/expand.rb +154 -87
- data/lib/json/ld/flatten.rb +1 -1
- data/lib/json/ld/format.rb +10 -6
- data/lib/json/ld/frame.rb +1 -2
- data/lib/json/ld/from_rdf.rb +7 -8
- data/lib/json/ld/html/nokogiri.rb +2 -1
- data/lib/json/ld/html/rexml.rb +2 -1
- data/lib/json/ld/reader.rb +20 -11
- data/lib/json/ld/streaming_reader.rb +578 -0
- data/lib/json/ld/to_rdf.rb +9 -3
- data/lib/json/ld/writer.rb +12 -5
- data/spec/compact_spec.rb +1 -0
- data/spec/context_spec.rb +63 -116
- data/spec/expand_spec.rb +29 -9
- data/spec/frame_spec.rb +44 -0
- data/spec/matchers.rb +1 -1
- data/spec/reader_spec.rb +33 -34
- data/spec/streaming_reader_spec.rb +237 -0
- data/spec/suite_expand_spec.rb +4 -2
- data/spec/suite_frame_spec.rb +0 -1
- data/spec/suite_helper.rb +23 -8
- data/spec/suite_to_rdf_spec.rb +1 -1
- data/spec/to_rdf_spec.rb +3 -3
- metadata +11 -8
data/spec/expand_spec.rb
CHANGED
@@ -2909,6 +2909,26 @@ describe JSON::LD::API do
|
|
2909
2909
|
validate: true,
|
2910
2910
|
exception: JSON::LD::JsonLdError::InvalidTermDefinition
|
2911
2911
|
},
|
2912
|
+
"Applies property scoped contexts which are aliases of @nest": {
|
2913
|
+
input: %({
|
2914
|
+
"@context": {
|
2915
|
+
"@version": 1.1,
|
2916
|
+
"@vocab": "http://example.org/",
|
2917
|
+
"nest": {
|
2918
|
+
"@id": "@nest",
|
2919
|
+
"@context": {
|
2920
|
+
"@vocab": "http://example.org/nest/"
|
2921
|
+
}
|
2922
|
+
}
|
2923
|
+
},
|
2924
|
+
"nest": {
|
2925
|
+
"property": "should be in /nest"
|
2926
|
+
}
|
2927
|
+
}),
|
2928
|
+
output: %([{
|
2929
|
+
"http://example.org/nest/property": [{"@value": "should be in /nest"}]
|
2930
|
+
}])
|
2931
|
+
}
|
2912
2932
|
}.each do |title, params|
|
2913
2933
|
it(title) {run_expand({processingMode: "json-ld-1.1"}.merge(params))}
|
2914
2934
|
end
|
@@ -3366,7 +3386,7 @@ describe JSON::LD::API do
|
|
3366
3386
|
%w(Nokogiri REXML).each do |impl|
|
3367
3387
|
next unless Module.constants.map(&:to_s).include?(impl)
|
3368
3388
|
context impl do
|
3369
|
-
|
3389
|
+
let(:library) {impl.downcase.to_s.to_sym}
|
3370
3390
|
|
3371
3391
|
{
|
3372
3392
|
"Expands embedded JSON-LD script element": {
|
@@ -3505,9 +3525,9 @@ describe JSON::LD::API do
|
|
3505
3525
|
]),
|
3506
3526
|
extractAllScripts: true
|
3507
3527
|
},
|
3508
|
-
"
|
3528
|
+
"Errors no script element": {
|
3509
3529
|
input: %(<html><head></head></html>),
|
3510
|
-
|
3530
|
+
exception: JSON::LD::JsonLdError::LoadingDocumentFailed
|
3511
3531
|
},
|
3512
3532
|
"Expands as empty with no script element and extractAllScripts": {
|
3513
3533
|
input: %(<html><head></head></html>),
|
@@ -3619,7 +3639,7 @@ describe JSON::LD::API do
|
|
3619
3639
|
</head>
|
3620
3640
|
</html>),
|
3621
3641
|
base: "http://example.org/doc#third",
|
3622
|
-
exception: JSON::LD::JsonLdError::
|
3642
|
+
exception: JSON::LD::JsonLdError::LoadingDocumentFailed
|
3623
3643
|
},
|
3624
3644
|
"Errors if targeted element is not a script element": {
|
3625
3645
|
input: %(
|
@@ -3636,7 +3656,7 @@ describe JSON::LD::API do
|
|
3636
3656
|
</head>
|
3637
3657
|
</html>),
|
3638
3658
|
base: "http://example.org/doc#first",
|
3639
|
-
exception: JSON::LD::JsonLdError::
|
3659
|
+
exception: JSON::LD::JsonLdError::LoadingDocumentFailed
|
3640
3660
|
},
|
3641
3661
|
"Errors if targeted element does not have type application/ld+json": {
|
3642
3662
|
input: %(
|
@@ -3653,7 +3673,7 @@ describe JSON::LD::API do
|
|
3653
3673
|
</head>
|
3654
3674
|
</html>),
|
3655
3675
|
base: "http://example.org/doc#first",
|
3656
|
-
exception: JSON::LD::JsonLdError::
|
3676
|
+
exception: JSON::LD::JsonLdError::LoadingDocumentFailed
|
3657
3677
|
},
|
3658
3678
|
"Errors if uncommented script text contains comment": {
|
3659
3679
|
input: %(
|
@@ -3722,10 +3742,10 @@ describe JSON::LD::API do
|
|
3722
3742
|
},
|
3723
3743
|
}.each do |title, params|
|
3724
3744
|
it(title) do
|
3725
|
-
skip "rexml" if params[:not] ==
|
3726
|
-
params
|
3745
|
+
skip "rexml" if params[:not] == library
|
3746
|
+
params = params.merge(input: StringIO.new(params[:input]))
|
3727
3747
|
params[:input].send(:define_singleton_method, :content_type) {"text/html"}
|
3728
|
-
run_expand params.merge(validate: true, library:
|
3748
|
+
run_expand params.merge(validate: true, library: library)
|
3729
3749
|
end
|
3730
3750
|
end
|
3731
3751
|
end
|
data/spec/frame_spec.rb
CHANGED
@@ -2305,6 +2305,50 @@ describe JSON::LD::API do
|
|
2305
2305
|
}
|
2306
2306
|
}),
|
2307
2307
|
processingMode: "json-ld-1.1"
|
2308
|
+
},
|
2309
|
+
"missing types": {
|
2310
|
+
input: %({
|
2311
|
+
"@context": {
|
2312
|
+
"ex": "http://example.com#",
|
2313
|
+
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
2314
|
+
},
|
2315
|
+
"@graph": [{
|
2316
|
+
"@id": "ex:graph1",
|
2317
|
+
"@graph": [{
|
2318
|
+
"@id": "ex:entity1",
|
2319
|
+
"@type": ["ex:Type1","ex:Type2"],
|
2320
|
+
"ex:title": "some title",
|
2321
|
+
"ex:multipleValues": "ex:One"
|
2322
|
+
}]
|
2323
|
+
}, {
|
2324
|
+
"@id": "ex:graph2",
|
2325
|
+
"@graph": [{
|
2326
|
+
"@id": "ex:entity1",
|
2327
|
+
"@type": "ex:Type3",
|
2328
|
+
"ex:tags": "tag1 tag2",
|
2329
|
+
"ex:multipleValues": ["ex:Two","ex:Three"]
|
2330
|
+
}]
|
2331
|
+
}]
|
2332
|
+
}),
|
2333
|
+
output: %({
|
2334
|
+
"@context": {
|
2335
|
+
"ex": "http://example.com#",
|
2336
|
+
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
2337
|
+
},
|
2338
|
+
"@id": "ex:entity1",
|
2339
|
+
"@type": ["ex:Type1", "ex:Type2", "ex:Type3"],
|
2340
|
+
"ex:multipleValues": ["ex:One", "ex:Two","ex:Three"],
|
2341
|
+
"ex:tags": "tag1 tag2",
|
2342
|
+
"ex:title": "some title"
|
2343
|
+
}),
|
2344
|
+
frame: %({
|
2345
|
+
"@context": {
|
2346
|
+
"ex": "http://example.com#",
|
2347
|
+
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
2348
|
+
},
|
2349
|
+
"@id": "ex:entity1"
|
2350
|
+
}),
|
2351
|
+
processingMode: "json-ld-1.1"
|
2308
2352
|
}
|
2309
2353
|
}.each do |title, params|
|
2310
2354
|
it title do
|
data/spec/matchers.rb
CHANGED
data/spec/reader_spec.rb
CHANGED
@@ -43,40 +43,39 @@ describe JSON::LD::Reader do
|
|
43
43
|
{
|
44
44
|
plain: %q({
|
45
45
|
"@context": {"foaf": "http://xmlns.com/foaf/0.1/"},
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
</script>),
|
46
|
+
"@id": "_:bnode1",
|
47
|
+
"@type": "foaf:Person",
|
48
|
+
"foaf:homepage": "http://example.com/bob/",
|
49
|
+
"foaf:name": "Bob"
|
50
|
+
}),
|
51
|
+
leading_comment: %q(
|
52
|
+
// A comment before content
|
53
|
+
{
|
54
|
+
"@context": {"foaf": "http://xmlns.com/foaf/0.1/"},
|
55
|
+
"@id": "_:bnode1",
|
56
|
+
"@type": "foaf:Person",
|
57
|
+
"foaf:homepage": "http://example.com/bob/",
|
58
|
+
"foaf:name": "Bob"
|
59
|
+
}),
|
60
|
+
script: %q(<script type="application/ld+json">
|
61
|
+
{
|
62
|
+
"@context": {"foaf": "http://xmlns.com/foaf/0.1/"},
|
63
|
+
"@id": "_:bnode1",
|
64
|
+
"@type": "foaf:Person",
|
65
|
+
"foaf:homepage": "http://example.com/bob/",
|
66
|
+
"foaf:name": "Bob"
|
67
|
+
}
|
68
|
+
</script>),
|
69
|
+
script_comments: %q(<script type="application/ld+json">
|
70
|
+
// A comment before content
|
71
|
+
{
|
72
|
+
"@context": {"foaf": "http://xmlns.com/foaf/0.1/"},
|
73
|
+
"@id": "_:bnode1",
|
74
|
+
"@type": "foaf:Person",
|
75
|
+
"foaf:homepage": "http://example.com/bob/",
|
76
|
+
"foaf:name": "Bob"
|
77
|
+
}
|
78
|
+
</script>),
|
80
79
|
}.each do |variant, src|
|
81
80
|
context variant do
|
82
81
|
subject {src}
|
@@ -0,0 +1,237 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require_relative 'spec_helper'
|
3
|
+
require 'rdf/spec/reader'
|
4
|
+
|
5
|
+
describe JSON::LD::Reader do
|
6
|
+
let!(:doap) {File.expand_path("../../etc/doap.jsonld", __FILE__)}
|
7
|
+
let!(:doap_nt) {File.expand_path("../../etc/doap.nt", __FILE__)}
|
8
|
+
let!(:doap_count) {File.open(doap_nt).each_line.to_a.length}
|
9
|
+
let(:logger) {RDF::Spec.logger}
|
10
|
+
|
11
|
+
after(:each) {|example| puts logger.to_s if example.exception}
|
12
|
+
|
13
|
+
it_behaves_like 'an RDF::Reader' do
|
14
|
+
let(:reader_input) {File.read(doap)}
|
15
|
+
let(:reader) {JSON::LD::Reader.new(reader_input, stream: true)}
|
16
|
+
let(:reader_count) {doap_count}
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when validating", pending: ("JRuby support for jsonlint" if RUBY_ENGINE == "jruby") do
|
20
|
+
it "detects invalid JSON" do
|
21
|
+
expect do |b|
|
22
|
+
described_class.new(StringIO.new(%({"a": "b", "a": "c"})), validate: true, logger: false).each_statement(&b)
|
23
|
+
end.to raise_error(RDF::ReaderError)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context :interface do
|
28
|
+
{
|
29
|
+
plain: %q({
|
30
|
+
"@context": {"foaf": "http://xmlns.com/foaf/0.1/"},
|
31
|
+
"@type": "foaf:Person",
|
32
|
+
"@id": "_:bnode1",
|
33
|
+
"foaf:homepage": "http://example.com/bob/",
|
34
|
+
"foaf:name": "Bob"
|
35
|
+
}),
|
36
|
+
leading_comment: %q(
|
37
|
+
// A comment before content
|
38
|
+
{
|
39
|
+
"@context": {"foaf": "http://xmlns.com/foaf/0.1/"},
|
40
|
+
"@type": "foaf:Person",
|
41
|
+
"@id": "_:bnode1",
|
42
|
+
"foaf:homepage": "http://example.com/bob/",
|
43
|
+
"foaf:name": "Bob"
|
44
|
+
}),
|
45
|
+
script: %q(<script type="application/ld+json">
|
46
|
+
{
|
47
|
+
"@context": {"foaf": "http://xmlns.com/foaf/0.1/"},
|
48
|
+
"@type": "foaf:Person",
|
49
|
+
"@id": "_:bnode1",
|
50
|
+
"foaf:homepage": "http://example.com/bob/",
|
51
|
+
"foaf:name": "Bob"
|
52
|
+
}
|
53
|
+
</script>),
|
54
|
+
script_comments: %q(<script type="application/ld+json">
|
55
|
+
// A comment before content
|
56
|
+
{
|
57
|
+
"@context": {"foaf": "http://xmlns.com/foaf/0.1/"},
|
58
|
+
"@type": "foaf:Person",
|
59
|
+
"@id": "_:bnode1",
|
60
|
+
"foaf:homepage": "http://example.com/bob/",
|
61
|
+
"foaf:name": "Bob"
|
62
|
+
}
|
63
|
+
</script>),
|
64
|
+
}.each do |variant, src|
|
65
|
+
context variant do
|
66
|
+
subject {src}
|
67
|
+
|
68
|
+
describe "#initialize" do
|
69
|
+
it "yields reader given string" do
|
70
|
+
inner = double("inner")
|
71
|
+
expect(inner).to receive(:called).with(JSON::LD::Reader)
|
72
|
+
JSON::LD::Reader.new(subject, stream: true) do |reader|
|
73
|
+
inner.called(reader.class)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it "yields reader given IO" do
|
78
|
+
inner = double("inner")
|
79
|
+
expect(inner).to receive(:called).with(JSON::LD::Reader)
|
80
|
+
JSON::LD::Reader.new(StringIO.new(subject), stream: true) do |reader|
|
81
|
+
inner.called(reader.class)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it "returns reader" do
|
86
|
+
expect(JSON::LD::Reader.new(subject, stream: true)).to be_a(JSON::LD::Reader)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "#each_statement" do
|
91
|
+
it "yields statements" do
|
92
|
+
inner = double("inner")
|
93
|
+
expect(inner).to receive(:called).with(RDF::Statement).exactly(3)
|
94
|
+
JSON::LD::Reader.new(subject, stream: true).each_statement do |statement|
|
95
|
+
inner.called(statement.class)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "#each_triple" do
|
101
|
+
it "yields statements" do
|
102
|
+
inner = double("inner")
|
103
|
+
expect(inner).to receive(:called).exactly(3)
|
104
|
+
JSON::LD::Reader.new(subject, stream: true).each_triple do |subject, predicate, object|
|
105
|
+
inner.called(subject.class, predicate.class, object.class)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "Selected toRdf tests" do
|
114
|
+
{
|
115
|
+
"e004": {
|
116
|
+
input: %({
|
117
|
+
"@context": {
|
118
|
+
"mylist1": {"@id": "http://example.com/mylist1", "@container": "@list"}
|
119
|
+
},
|
120
|
+
"@id": "http://example.org/id",
|
121
|
+
"mylist1": { "@list": [ ] },
|
122
|
+
"http://example.org/list1": { "@list": [ null ] },
|
123
|
+
"http://example.org/list2": { "@list": [ {"@value": null} ] }
|
124
|
+
}),
|
125
|
+
expect: %(
|
126
|
+
<http://example.org/id> <http://example.com/mylist1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
|
127
|
+
<http://example.org/id> <http://example.org/list1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
|
128
|
+
<http://example.org/id> <http://example.org/list2> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
|
129
|
+
)
|
130
|
+
},
|
131
|
+
"e015": {
|
132
|
+
input: %({
|
133
|
+
"@context": {
|
134
|
+
"myset2": {"@id": "http://example.com/myset2", "@container": "@set" }
|
135
|
+
},
|
136
|
+
"@id": "http://example.org/id",
|
137
|
+
"myset2": [ [], { "@set": [ null ] }, [ null ] ]
|
138
|
+
}),
|
139
|
+
expect: %(
|
140
|
+
)
|
141
|
+
},
|
142
|
+
"in06": {
|
143
|
+
input: %({
|
144
|
+
"@context": {
|
145
|
+
"@version": 1.1,
|
146
|
+
"@vocab": "http://example.org/vocab#",
|
147
|
+
"@base": "http://example.org/base/",
|
148
|
+
"id": "@id",
|
149
|
+
"type": "@type",
|
150
|
+
"data": "@nest",
|
151
|
+
"links": "@nest",
|
152
|
+
"relationships": "@nest",
|
153
|
+
"self": {"@type": "@id"},
|
154
|
+
"related": {"@type": "@id"}
|
155
|
+
},
|
156
|
+
"data": [{
|
157
|
+
"type": "articles",
|
158
|
+
"id": "1",
|
159
|
+
"author": {
|
160
|
+
"data": { "type": "people", "id": "9" }
|
161
|
+
}
|
162
|
+
}]
|
163
|
+
}),
|
164
|
+
expect: %(
|
165
|
+
<http://example.org/base/1> <http://example.org/vocab#author> <http://example.org/base/9> .
|
166
|
+
<http://example.org/base/1> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/vocab#articles> .
|
167
|
+
<http://example.org/base/9> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/vocab#people> .
|
168
|
+
),
|
169
|
+
pending: "@nest defining @id"
|
170
|
+
}
|
171
|
+
}.each do |name, params|
|
172
|
+
it name do
|
173
|
+
run_to_rdf params
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe "test suite" do
|
179
|
+
require_relative 'suite_helper'
|
180
|
+
m = Fixtures::SuiteTest::Manifest.open("#{Fixtures::SuiteTest::STREAM_SUITE}stream-toRdf-manifest.jsonld")
|
181
|
+
describe m.name do
|
182
|
+
m.entries.each do |t|
|
183
|
+
specify "#{t.property('@id')}: #{t.name}#{' (negative test)' unless t.positiveTest?}" do
|
184
|
+
pending "Generalized RDF" if t.options[:produceGeneralizedRdf]
|
185
|
+
pending "@nest defining @id" if %w(#tin06).include?(t.property('@id'))
|
186
|
+
pending "double @reverse" if %w(#te043).include?(t.property('@id'))
|
187
|
+
pending "graph map containing named graph" if %w(#te084 #te087 #te098 #te101 #te105 #te106).include?(t.property('@id'))
|
188
|
+
pending "named graphs" if %w(#t0029 #te021).include?(t.property('@id'))
|
189
|
+
|
190
|
+
if %w(#t0118).include?(t.property('@id'))
|
191
|
+
expect {t.run self}.to write(/Statement .* is invalid/).to(:error)
|
192
|
+
elsif %w(#twf07).include?(t.property('@id'))
|
193
|
+
expect {t.run self}.to write(/skipping graph statement within invalid graph name/).to(:error)
|
194
|
+
elsif %w(#te075).include?(t.property('@id'))
|
195
|
+
expect {t.run self}.to write(/is invalid/).to(:error)
|
196
|
+
elsif %w(#te005 #tpr34 #tpr35 #tpr36 #tpr37 #tpr38 #tpr39 #te119 #te120).include?(t.property('@id'))
|
197
|
+
expect {t.run self}.to write("beginning with '@' are reserved for future use").to(:error)
|
198
|
+
elsif %w(#te068).include?(t.property('@id'))
|
199
|
+
expect {t.run self}.to write("[DEPRECATION]").to(:error)
|
200
|
+
elsif %w(#twf05).include?(t.property('@id'))
|
201
|
+
expect {t.run self}.to write("@language must be valid BCP47").to(:error)
|
202
|
+
else
|
203
|
+
expect {t.run self}.not_to write.to(:error)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end unless ENV['CI']
|
209
|
+
|
210
|
+
def run_to_rdf(params)
|
211
|
+
input = params[:input]
|
212
|
+
logger.info("input: #{input}")
|
213
|
+
output = RDF::Repository.new
|
214
|
+
if params[:expect]
|
215
|
+
RDF::NQuads::Reader.new(params[:expect], validate: false) {|r| output << r}
|
216
|
+
logger.info("expect (quads): #{output.dump(:nquads, validate: false)}")
|
217
|
+
else
|
218
|
+
logger.info("expect: #{Regexp.new params[:exception]}")
|
219
|
+
end
|
220
|
+
|
221
|
+
graph = params[:graph] || RDF::Repository.new
|
222
|
+
pending params.fetch(:pending, "test implementation") if !input || params[:pending]
|
223
|
+
if params[:exception]
|
224
|
+
expect do |b|
|
225
|
+
JSON::LD::Reader.new(input, stream: true, validate: true, logger: false, **params).each_statement(&b)
|
226
|
+
end.to raise_error {|er| expect(er.message).to include params[:exception]}
|
227
|
+
else
|
228
|
+
if params[:write]
|
229
|
+
expect{JSON::LD::Reader.new(input, stream: true, logger: logger, **params) {|st| graph << st}}.to write(params[:write]).to(:error)
|
230
|
+
else
|
231
|
+
expect{JSON::LD::Reader.new(input, stream: true, logger: logger, **params) {|st| graph << st}}.not_to write.to(:error)
|
232
|
+
end
|
233
|
+
logger.info("results (quads): #{graph.dump(:nquads, validate: false)}")
|
234
|
+
expect(graph).to be_equivalent_graph(output, logger: logger, inputDocument: input)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|