json-ld 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.markdown +15 -0
- data/README.markdown +199 -3
- data/VERSION +1 -1
- data/lib/json/ld.rb +44 -4
- data/lib/json/ld/api.rb +220 -224
- data/lib/json/ld/compact.rb +126 -0
- data/lib/json/ld/evaluation_context.rb +428 -204
- data/lib/json/ld/expand.rb +185 -0
- data/lib/json/ld/extensions.rb +34 -7
- data/lib/json/ld/format.rb +2 -17
- data/lib/json/ld/frame.rb +452 -0
- data/lib/json/ld/from_rdf.rb +166 -0
- data/lib/json/ld/reader.rb +7 -231
- data/lib/json/ld/to_rdf.rb +181 -0
- data/lib/json/ld/utils.rb +97 -0
- data/lib/json/ld/writer.rb +33 -471
- metadata +51 -34
- data/lib/json/ld/normalize.rb +0 -120
data/History.markdown
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
=== 0.1.1
|
2
|
+
* Changed @literal to @value.
|
3
|
+
* Change expanded double format to %1.6e
|
4
|
+
* Only recognize application/ld+json and :jsonld.
|
5
|
+
|
6
|
+
=== 0.1.0
|
7
|
+
* New @context processing rules.
|
8
|
+
* @iri and @subject changed to @id.
|
9
|
+
* @datatype changed to @type.
|
10
|
+
* @coerce keys can be CURIEs or IRIs (not spec'd).
|
11
|
+
* @language in @context.
|
12
|
+
* Implemented JSON::LD::API for .compact and .expand.
|
13
|
+
* Support relative IRI expansion based on document location.
|
14
|
+
* Make sure that keyword aliases are fully considered on both input and output and used when compacting.
|
15
|
+
|
1
16
|
=== 0.0.8
|
2
17
|
* RDF.rb 0.3.4 compatibility.
|
3
18
|
* Format detection.
|
data/README.markdown
CHANGED
@@ -4,7 +4,8 @@
|
|
4
4
|
|
5
5
|
## Features
|
6
6
|
|
7
|
-
JSON::LD parses and serializes [JSON-LD][] into
|
7
|
+
JSON::LD parses and serializes [JSON-LD][] into [RDF][] and implements
|
8
|
+
JSON::LD expansion, compaction and framing API interfaces.
|
8
9
|
|
9
10
|
Install with `gem install json-ld`
|
10
11
|
|
@@ -13,13 +14,208 @@ Install with `gem install json-ld`
|
|
13
14
|
require 'rubygems'
|
14
15
|
require 'json/ld'
|
15
16
|
|
17
|
+
### Expand a Document
|
18
|
+
|
19
|
+
input = {
|
20
|
+
"@context": {
|
21
|
+
"name": "http://xmlns.com/foaf/0.1/name",
|
22
|
+
"homepage": "http://xmlns.com/foaf/0.1/homepage",
|
23
|
+
"avatar": "http://xmlns.com/foaf/0.1/avatar"
|
24
|
+
},
|
25
|
+
"name": "Manu Sporny",
|
26
|
+
"homepage": "http://manu.sporny.org/",
|
27
|
+
"avatar": "http://twitter.com/account/profile_image/manusporny"
|
28
|
+
}
|
29
|
+
JSON::LD::API.expand(input) =>
|
30
|
+
|
31
|
+
[{
|
32
|
+
"http://xmlns.com/foaf/0.1/name": ["Manu Sporny"],
|
33
|
+
"http://xmlns.com/foaf/0.1/homepage": ["http://manu.sporny.org/"],
|
34
|
+
"http://xmlns.com/foaf/0.1/avatar": ["http://twitter.com/account/profile_image/manusporny"]
|
35
|
+
}]
|
36
|
+
|
37
|
+
### Compact a Document
|
38
|
+
|
39
|
+
input = [{
|
40
|
+
"http://xmlns.com/foaf/0.1/name": ["Manu Sporny"],
|
41
|
+
"http://xmlns.com/foaf/0.1/homepage": ["http://manu.sporny.org/"],
|
42
|
+
"http://xmlns.com/foaf/0.1/avatar": ["http://twitter.com/account/profile_image/manusporny"]
|
43
|
+
}]
|
44
|
+
|
45
|
+
context = {
|
46
|
+
"@context": {
|
47
|
+
"name": "http://xmlns.com/foaf/0.1/name",
|
48
|
+
"homepage": "http://xmlns.com/foaf/0.1/homepage",
|
49
|
+
"avatar": "http://xmlns.com/foaf/0.1/avatar"
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
JSON::LD::API.compact(input, context) =>
|
54
|
+
{
|
55
|
+
"@context": {
|
56
|
+
"avatar": "http://xmlns.com/foaf/0.1/avatar",
|
57
|
+
"homepage": "http://xmlns.com/foaf/0.1/homepage",
|
58
|
+
"name": "http://xmlns.com/foaf/0.1/name"
|
59
|
+
},
|
60
|
+
"avatar": "http://twitter.com/account/profile_image/manusporny",
|
61
|
+
"homepage": "http://manu.sporny.org/",
|
62
|
+
"name": "Manu Sporny"
|
63
|
+
}
|
64
|
+
|
65
|
+
### Frame a Document
|
66
|
+
|
67
|
+
input = {
|
68
|
+
"@context": {
|
69
|
+
"Book": "http://example.org/vocab#Book",
|
70
|
+
"Chapter": "http://example.org/vocab#Chapter",
|
71
|
+
"contains": {"@id": "http://example.org/vocab#contains", "@type": "@id"},
|
72
|
+
"creator": "http://purl.org/dc/terms/creator",
|
73
|
+
"description": "http://purl.org/dc/terms/description",
|
74
|
+
"Library": "http://example.org/vocab#Library",
|
75
|
+
"title": "http://purl.org/dc/terms/title"
|
76
|
+
},
|
77
|
+
"@graph":
|
78
|
+
[{
|
79
|
+
"@id": "http://example.com/library",
|
80
|
+
"@type": "Library",
|
81
|
+
"contains": "http://example.org/library/the-republic"
|
82
|
+
},
|
83
|
+
{
|
84
|
+
"@id": "http://example.org/library/the-republic",
|
85
|
+
"@type": "Book",
|
86
|
+
"creator": "Plato",
|
87
|
+
"title": "The Republic",
|
88
|
+
"contains": "http://example.org/library/the-republic#introduction"
|
89
|
+
},
|
90
|
+
{
|
91
|
+
"@id": "http://example.org/library/the-republic#introduction",
|
92
|
+
"@type": "Chapter",
|
93
|
+
"description": "An introductory chapter on The Republic.",
|
94
|
+
"title": "The Introduction"
|
95
|
+
}]
|
96
|
+
}
|
97
|
+
|
98
|
+
frame = {
|
99
|
+
"@context": {
|
100
|
+
"Book": "http://example.org/vocab#Book",
|
101
|
+
"Chapter": "http://example.org/vocab#Chapter",
|
102
|
+
"contains": "http://example.org/vocab#contains",
|
103
|
+
"creator": "http://purl.org/dc/terms/creator",
|
104
|
+
"description": "http://purl.org/dc/terms/description",
|
105
|
+
"Library": "http://example.org/vocab#Library",
|
106
|
+
"title": "http://purl.org/dc/terms/title"
|
107
|
+
},
|
108
|
+
"@type": "Library",
|
109
|
+
"contains": {
|
110
|
+
"@type": "Book",
|
111
|
+
"contains": {
|
112
|
+
"@type": "Chapter"
|
113
|
+
}
|
114
|
+
}
|
115
|
+
}
|
116
|
+
JSON::LD.frame(input, frame) =>
|
117
|
+
{
|
118
|
+
"@context": {
|
119
|
+
"Book": "http://example.org/vocab#Book",
|
120
|
+
"Chapter": "http://example.org/vocab#Chapter",
|
121
|
+
"contains": "http://example.org/vocab#contains",
|
122
|
+
"creator": "http://purl.org/dc/terms/creator",
|
123
|
+
"description": "http://purl.org/dc/terms/description",
|
124
|
+
"Library": "http://example.org/vocab#Library",
|
125
|
+
"title": "http://purl.org/dc/terms/title"
|
126
|
+
},
|
127
|
+
"@graph": [
|
128
|
+
{
|
129
|
+
"@id": "http://example.com/library",
|
130
|
+
"@type": "Library",
|
131
|
+
"contains": {
|
132
|
+
"@id": "http://example.org/library/the-republic",
|
133
|
+
"@type": "Book",
|
134
|
+
"contains": {
|
135
|
+
"@id": "http://example.org/library/the-republic#introduction",
|
136
|
+
"@type": "Chapter",
|
137
|
+
"description": "An introductory chapter on The Republic.",
|
138
|
+
"title": "The Introduction"
|
139
|
+
},
|
140
|
+
"creator": "Plato",
|
141
|
+
"title": "The Republic"
|
142
|
+
}
|
143
|
+
}
|
144
|
+
]
|
145
|
+
}
|
146
|
+
|
147
|
+
### Turn JSON-LD into RDF (Turtle)
|
148
|
+
|
149
|
+
input = {
|
150
|
+
"@context": {
|
151
|
+
"": "http://manu.sporny.org/",
|
152
|
+
"foaf": "http://xmlns.com/foaf/0.1/"
|
153
|
+
},
|
154
|
+
"@id": "http://example.org/people#joebob",
|
155
|
+
"@type": "foaf:Person",
|
156
|
+
"foaf:name": "Joe Bob",
|
157
|
+
"foaf:nick": { "@list": [ "joe", "bob", "jaybe" ] }
|
158
|
+
}
|
159
|
+
|
160
|
+
JSON::LD::API.toRDF(input) =>
|
161
|
+
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
162
|
+
|
163
|
+
<http://example.org/people#joebob> a foaf:Person;
|
164
|
+
foaf:name "Joe Bob";
|
165
|
+
foaf:nick ("joe" "bob" "jaybe") .
|
166
|
+
|
167
|
+
### Turn RDF into JSON-LD
|
168
|
+
|
169
|
+
input =
|
170
|
+
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
171
|
+
|
172
|
+
<http://manu.sporny.org/#me> a foaf:Person;
|
173
|
+
foaf:knows [ a foaf:Person;
|
174
|
+
foaf:name "Gregg Kellogg"];
|
175
|
+
foaf:name "Manu Sporny" .
|
176
|
+
|
177
|
+
context =
|
178
|
+
{
|
179
|
+
"@context": {
|
180
|
+
"": "http://manu.sporny.org/",
|
181
|
+
"foaf": "http://xmlns.com/foaf/0.1/"
|
182
|
+
}
|
183
|
+
}
|
184
|
+
|
185
|
+
JSON::LD::fromRDF(input, context) =>
|
186
|
+
{
|
187
|
+
"@context": {
|
188
|
+
"": "http://manu.sporny.org/",
|
189
|
+
"foaf": "http://xmlns.com/foaf/0.1/"
|
190
|
+
},
|
191
|
+
"@id": ":#me",
|
192
|
+
"@type": "foaf:Person",
|
193
|
+
"foaf:name": "Manu Sporny",
|
194
|
+
"foaf:knows": {
|
195
|
+
"@type": "foaf:Person",
|
196
|
+
"foaf:name": "Gregg Kellogg"
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
## RDF Reader and Writer
|
201
|
+
{JSON::LD} also acts as a normal RDF reader and writer, using the standard RDF.rb reader/writer interfaces:
|
202
|
+
|
203
|
+
graph = RDF::Graph.load("etc/doap.jsonld", :format => :jsonld)
|
204
|
+
graph.dump(:jsonld, :standard_prefixes => true)
|
205
|
+
|
16
206
|
## Documentation
|
17
|
-
Full documentation available on [RubyDoc](http://rubydoc.info/gems/json-ld/
|
207
|
+
Full documentation available on [RubyDoc](http://rubydoc.info/gems/json-ld/file/README.markdown)
|
18
208
|
|
19
209
|
### Principle Classes
|
20
210
|
* {JSON::LD}
|
211
|
+
* {JSON::LD::API}
|
212
|
+
* {JSON::LD::Compact}
|
213
|
+
* {JSON::LD::EvaluationContext}
|
21
214
|
* {JSON::LD::Format}
|
215
|
+
* {JSON::LD::Frame}
|
216
|
+
* {JSON::LD::FromTriples}
|
22
217
|
* {JSON::LD::Reader}
|
218
|
+
* {JSON::LD::Triples}
|
23
219
|
* {JSON::LD::Writer}
|
24
220
|
|
25
221
|
## Dependencies
|
@@ -70,4 +266,4 @@ see <http://unlicense.org/> or the accompanying {file:UNLICENSE} file.
|
|
70
266
|
[PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html
|
71
267
|
[RDF.rb]: http://rdf.rubyforge.org/
|
72
268
|
[Backports]: http://rubygems.org/gems/backports
|
73
|
-
[JSON-LD]: http://json-ld.org/spec/
|
269
|
+
[JSON-LD]: http://json-ld.org/spec/latest/
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.2
|
data/lib/json/ld.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..')))
|
2
2
|
require 'rdf' # @see http://rubygems.org/gems/rdf
|
3
|
+
require 'backports' if RUBY_VERSION < "1.9"
|
3
4
|
|
4
5
|
module JSON
|
5
6
|
##
|
@@ -23,6 +24,7 @@ module JSON
|
|
23
24
|
require 'json'
|
24
25
|
require 'json/ld/extensions'
|
25
26
|
require 'json/ld/format'
|
27
|
+
require 'json/ld/utils'
|
26
28
|
autoload :API, 'json/ld/api'
|
27
29
|
autoload :EvaluationContext, 'json/ld/evaluation_context'
|
28
30
|
autoload :Normalize, 'json/ld/normalize'
|
@@ -36,6 +38,17 @@ module JSON
|
|
36
38
|
RDF.type.to_s => {"@type" => "@id"}
|
37
39
|
}.freeze
|
38
40
|
|
41
|
+
KEYWORDS = %w(
|
42
|
+
@container
|
43
|
+
@context
|
44
|
+
@id
|
45
|
+
@language
|
46
|
+
@list
|
47
|
+
@set
|
48
|
+
@type
|
49
|
+
@value
|
50
|
+
).freeze
|
51
|
+
|
39
52
|
# Regexp matching an NCName.
|
40
53
|
NC_REGEXP = Regexp.new(
|
41
54
|
%{^
|
@@ -53,6 +66,14 @@ module JSON
|
|
53
66
|
# Datatypes that are expressed in a native form and don't expand or compact
|
54
67
|
NATIVE_DATATYPES = [RDF::XSD.integer.to_s, RDF::XSD.boolean.to_s, RDF::XSD.double.to_s]
|
55
68
|
|
69
|
+
JSON_STATE = JSON::State.new(
|
70
|
+
:indent => " ",
|
71
|
+
:space => " ",
|
72
|
+
:space_before => "",
|
73
|
+
:object_nl => "\n",
|
74
|
+
:array_nl => "\n"
|
75
|
+
)
|
76
|
+
|
56
77
|
def self.debug?; @debug; end
|
57
78
|
def self.debug=(value); @debug = value; end
|
58
79
|
|
@@ -63,6 +84,9 @@ module JSON
|
|
63
84
|
# The target datatype specified in the coercion rule and the datatype for the typed literal do not match.
|
64
85
|
CONFLICTING_DATATYPES = 2
|
65
86
|
|
87
|
+
# A list containing another list was detected.
|
88
|
+
LIST_OF_LISTS_DETECTED = 3
|
89
|
+
|
66
90
|
attr :code
|
67
91
|
|
68
92
|
class Lossy < ProcessingError
|
@@ -78,6 +102,13 @@ module JSON
|
|
78
102
|
@code = CONFLICTING_DATATYPES
|
79
103
|
end
|
80
104
|
end
|
105
|
+
|
106
|
+
class ListOfLists < ProcessingError
|
107
|
+
def initialize(*args)
|
108
|
+
super
|
109
|
+
@code = LIST_OF_LISTS_DETECTED
|
110
|
+
end
|
111
|
+
end
|
81
112
|
end
|
82
113
|
|
83
114
|
class InvalidContext < Exception
|
@@ -115,10 +146,19 @@ module JSON
|
|
115
146
|
MULTIPLE_EMBEDS = 2
|
116
147
|
|
117
148
|
attr :code
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
149
|
+
|
150
|
+
class Syntax < InvalidFrame
|
151
|
+
def initialize(*args)
|
152
|
+
super
|
153
|
+
@code = INVALID_SYNTAX
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class MultipleEmbeds < InvalidFrame
|
158
|
+
def initialize(*args)
|
159
|
+
super
|
160
|
+
@code = MULTIPLE_EMBEDS
|
161
|
+
end
|
122
162
|
end
|
123
163
|
end
|
124
164
|
end
|
data/lib/json/ld/api.rb
CHANGED
@@ -1,4 +1,9 @@
|
|
1
1
|
require 'open-uri'
|
2
|
+
require 'json/ld/expand'
|
3
|
+
require 'json/ld/compact'
|
4
|
+
require 'json/ld/frame'
|
5
|
+
require 'json/ld/to_rdf'
|
6
|
+
require 'json/ld/from_rdf'
|
2
7
|
|
3
8
|
module JSON::LD
|
4
9
|
##
|
@@ -11,113 +16,84 @@ module JSON::LD
|
|
11
16
|
# @see http://json-ld.org/spec/latest/json-ld-api/#the-application-programming-interface
|
12
17
|
# @author [Gregg Kellogg](http://greggkellogg.net/)
|
13
18
|
class API
|
19
|
+
include Expand
|
20
|
+
include Compact
|
21
|
+
include Triples
|
22
|
+
include FromTriples
|
23
|
+
include Frame
|
24
|
+
|
14
25
|
attr_accessor :value
|
15
26
|
attr_accessor :context
|
16
27
|
|
17
28
|
##
|
18
29
|
# Initialize the API, reading in any document and setting global options
|
19
30
|
#
|
20
|
-
# @param [#read, Hash, Array] input
|
21
|
-
# @param [
|
31
|
+
# @param [String, #read, Hash, Array] input
|
32
|
+
# @param [String, #read,, Hash, Array] context
|
22
33
|
# An external context to use additionally to the context embedded in input when expanding the input.
|
23
34
|
# @param [Hash] options
|
24
35
|
# @yield [api]
|
25
36
|
# @yieldparam [API]
|
26
|
-
def initialize(input, context, options = {})
|
37
|
+
def initialize(input, context, options = {}, &block)
|
27
38
|
@options = options
|
28
|
-
@value =
|
39
|
+
@value = case input
|
40
|
+
when Array, Hash then input.dup
|
41
|
+
when IO, StringIO then JSON.parse(input.read)
|
42
|
+
when String
|
43
|
+
content = nil
|
44
|
+
RDF::Util::File.open_file(input) {|f| content = JSON.parse(f.read)}
|
45
|
+
content
|
46
|
+
end
|
29
47
|
@context = EvaluationContext.new(options)
|
30
48
|
@context = @context.parse(context) if context
|
31
|
-
|
49
|
+
|
50
|
+
if block_given?
|
51
|
+
case block.arity
|
52
|
+
when 0, -1 then instance_eval(&block)
|
53
|
+
else block.call(self)
|
54
|
+
end
|
55
|
+
end
|
32
56
|
end
|
33
57
|
|
34
58
|
##
|
35
59
|
# Expands the given input according to the steps in the Expansion Algorithm. The input must be copied, expanded and returned
|
36
60
|
# if there are no errors. If the expansion fails, an appropriate exception must be thrown.
|
37
61
|
#
|
38
|
-
#
|
62
|
+
# The resulting `Array` is returned via the provided callback.
|
63
|
+
#
|
64
|
+
# Note that for Ruby, if the callback is not provided and a block is given, it will be yielded
|
65
|
+
#
|
66
|
+
# @param [String, #read, Hash, Array] input
|
39
67
|
# The JSON-LD object to copy and perform the expansion upon.
|
40
|
-
# @param [
|
68
|
+
# @param [String, #read, Hash, Array] context
|
41
69
|
# An external context to use additionally to the context embedded in input when expanding the input.
|
70
|
+
# @param [Proc] callback (&block)
|
71
|
+
# Alternative to using block, with same parameters.
|
42
72
|
# @param [Hash{Symbol => Object}] options
|
73
|
+
# @option options [Boolean] :base
|
74
|
+
# Base IRI to use when processing relative IRIs.
|
43
75
|
# @raise [InvalidContext]
|
44
|
-
# @
|
76
|
+
# @yield jsonld
|
77
|
+
# @yieldparam [Array<Hash>] jsonld
|
78
|
+
# The expanded JSON-LD document
|
79
|
+
# @return [Array<Hash>]
|
45
80
|
# The expanded JSON-LD document
|
46
81
|
# @see http://json-ld.org/spec/latest/json-ld-api/#expansion-algorithm
|
47
|
-
def self.expand(input, context = nil, options = {})
|
82
|
+
def self.expand(input, context = nil, callback = nil, options = {})
|
48
83
|
result = nil
|
49
84
|
API.new(input, context, options) do |api|
|
50
85
|
result = api.expand(api.value, nil, api.context)
|
51
86
|
end
|
52
|
-
result
|
53
|
-
end
|
54
|
-
|
55
|
-
##
|
56
|
-
# Expand an Array or Object given an active context and performing local context expansion.
|
57
|
-
#
|
58
|
-
# @param [Array, Hash] input
|
59
|
-
# @param [RDF::URI] predicate
|
60
|
-
# @param [EvaluationContext] context
|
61
|
-
# @return [Array, Hash]
|
62
|
-
def expand(input, predicate, context)
|
63
|
-
debug("expand") {"input: #{input.class}, predicate: #{predicate.inspect}, context: #{context.inspect}"}
|
64
|
-
case input
|
65
|
-
when Array
|
66
|
-
# 1) If value is an array, process each item in value recursively using this algorithm,
|
67
|
-
# passing copies of the active context and active property.
|
68
|
-
depth {input.map {|v| expand(v, predicate, context)}}
|
69
|
-
when Hash
|
70
|
-
# Merge context
|
71
|
-
context = context.parse(input['@context']) if input['@context']
|
72
|
-
|
73
|
-
result = Hash.new
|
74
|
-
input.each do |key, value|
|
75
|
-
debug("expand") {"#{key}: #{value.inspect}"}
|
76
|
-
expanded_key = context.mapping(key) || key
|
77
|
-
case expanded_key
|
78
|
-
when '@context'
|
79
|
-
# Ignore in output
|
80
|
-
when '@id', '@type'
|
81
|
-
# If the key is @id or @type and the value is a string, expand the value according to IRI Expansion.
|
82
|
-
result[expanded_key] = case value
|
83
|
-
when String then context.expand_iri(value, :position => :subject, :depth => @depth).to_s
|
84
|
-
else depth { expand(value, predicate, context) }
|
85
|
-
end
|
86
|
-
debug("expand") {" => #{result[expanded_key].inspect}"}
|
87
|
-
when '@literal', '@language'
|
88
|
-
raise ProcessingError::Lossy, "Value of #{expanded_key} must be a string, was #{value.inspect}" unless value.is_a?(String)
|
89
|
-
result[expanded_key] = value
|
90
|
-
debug("expand") {" => #{result[expanded_key].inspect}"}
|
91
|
-
else
|
92
|
-
# 2.2.3) Otherwise, if the key is not a keyword, expand the key according to IRI Expansion rules and set as active property.
|
93
|
-
unless key[0,1] == '@'
|
94
|
-
predicate = context.expand_iri(key, :position => :predicate, :depth => @depth)
|
95
|
-
expanded_key = predicate.to_s
|
96
|
-
end
|
97
87
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
when Array, Hash then depth {expand(value, predicate, context)}
|
108
|
-
else
|
109
|
-
# 2.2.7) Otherwise, expand the value according to the Value Expansion rules, passing active property.
|
110
|
-
context.expand_value(predicate, value, :position => :object, :depth => @depth)
|
111
|
-
end
|
112
|
-
result[expanded_key] = value
|
113
|
-
debug("expand") {" => #{value.inspect}"}
|
114
|
-
end
|
115
|
-
end
|
116
|
-
result
|
117
|
-
else
|
118
|
-
# 2.3) Otherwise, expand the value according to the Value Expansion rules, passing active property.
|
119
|
-
context.expand_value(predicate, input, :position => :object, :depth => @depth)
|
120
|
-
end
|
88
|
+
# If, after the algorithm outlined above is run, the resulting element is an
|
89
|
+
# JSON object with just a @graph property, element is set to the value of @graph's value.
|
90
|
+
result = result['@graph'] if result.is_a?(Hash) && result.keys == %w(@graph)
|
91
|
+
|
92
|
+
# Finally, if element is a JSON object, it is wrapped into an array.
|
93
|
+
result = [result] unless result.is_a?(Array)
|
94
|
+
callback.call(result) if callback
|
95
|
+
yield result if block_given?
|
96
|
+
result
|
121
97
|
end
|
122
98
|
|
123
99
|
##
|
@@ -126,194 +102,214 @@ module JSON::LD
|
|
126
102
|
#
|
127
103
|
# If no context is provided, the input document is compacted using the top-level context of the document
|
128
104
|
#
|
129
|
-
#
|
105
|
+
# The resulting `Hash` is returned via the provided callback.
|
106
|
+
#
|
107
|
+
# Note that for Ruby, if the callback is not provided and a block is given, it will be yielded
|
108
|
+
#
|
109
|
+
# @param [String, #read, Hash, Array] input
|
130
110
|
# The JSON-LD object to copy and perform the compaction upon.
|
131
|
-
# @param [
|
111
|
+
# @param [String, #read, Hash, Array] context
|
132
112
|
# The base context to use when compacting the input.
|
113
|
+
# @param [Proc] callback (&block)
|
114
|
+
# Alternative to using block, with same parameters.
|
133
115
|
# @param [Hash{Symbol => Object}] options
|
134
|
-
#
|
116
|
+
# Other options passed to {#expand}
|
117
|
+
# @option options [Boolean] :optimize (false)
|
118
|
+
# Perform further optimmization of the compacted output.
|
119
|
+
# (Presently, this is a noop).
|
120
|
+
# @param [Hash{Symbol => Object}] options
|
121
|
+
# @yield jsonld
|
122
|
+
# @yieldparam [Hash] jsonld
|
123
|
+
# The compacted JSON-LD document
|
135
124
|
# @return [Hash]
|
136
125
|
# The compacted JSON-LD document
|
126
|
+
# @raise [InvalidContext, ProcessingError]
|
137
127
|
# @see http://json-ld.org/spec/latest/json-ld-api/#compaction-algorithm
|
138
|
-
def self.compact(input, context = nil, options = {})
|
128
|
+
def self.compact(input, context, callback = nil, options = {})
|
139
129
|
expanded = result = nil
|
140
130
|
|
141
|
-
|
142
|
-
|
131
|
+
# 1) Perform the Expansion Algorithm on the JSON-LD input.
|
132
|
+
# This removes any existing context to allow the given context to be cleanly applied.
|
133
|
+
API.new(input, nil, options) do
|
134
|
+
expanded = expand(value, nil, self.context)
|
135
|
+
debug(".compact") {"expanded input: #{value.to_json(JSON_STATE)}"}
|
136
|
+
|
137
|
+
# x) If no context provided, use context from input document
|
138
|
+
context ||= value.fetch('@context', nil)
|
143
139
|
end
|
144
140
|
|
145
|
-
API.new(expanded, context, options) do
|
146
|
-
|
147
|
-
# This removes any existing context to allow the given context to be cleanly applied.
|
148
|
-
|
149
|
-
result = api.compact(api.value, nil)
|
141
|
+
API.new(expanded, context, options) do
|
142
|
+
result = compact(value, nil)
|
150
143
|
|
151
144
|
# xxx) Add the given context to the output
|
152
145
|
result = case result
|
153
|
-
when Hash then
|
154
|
-
when Array
|
146
|
+
when Hash then self.context.serialize.merge(result)
|
147
|
+
when Array
|
148
|
+
kwgraph = self.context.compact_iri('@graph', :quiet => true)
|
149
|
+
self.context.serialize.merge(kwgraph => result)
|
150
|
+
when String
|
151
|
+
kwid = self.context.compact_iri('@id', :quiet => true)
|
152
|
+
self.context.serialize.merge(kwid => result)
|
155
153
|
end
|
156
154
|
end
|
155
|
+
callback.call(result) if callback
|
156
|
+
yield result if block_given?
|
157
157
|
result
|
158
158
|
end
|
159
159
|
|
160
|
-
##
|
161
|
-
# Compact an expanded Array or Hash given an active property and a context.
|
162
|
-
#
|
163
|
-
# @param [Array, Hash] input
|
164
|
-
# @param [RDF::URI] predicate (nil)
|
165
|
-
# @param [EvaluationContext] context
|
166
|
-
# @return [Array, Hash]
|
167
|
-
def compact(input, predicate = nil)
|
168
|
-
debug("compact") {"input: #{input.class}, predicate: #{predicate.inspect}"}
|
169
|
-
case input
|
170
|
-
when Array
|
171
|
-
# 1) If value is an array, process each item in value recursively using this algorithm,
|
172
|
-
# passing copies of the active context and active property.
|
173
|
-
debug("compact") {"Array[#{input.length}]"}
|
174
|
-
depth {input.map {|v| compact(v, predicate)}}
|
175
|
-
when Hash
|
176
|
-
result = Hash.new
|
177
|
-
input.each do |key, value|
|
178
|
-
debug("compact") {"#{key}: #{value.inspect}"}
|
179
|
-
compacted_key = context.alias(key)
|
180
|
-
debug("compact") {" => compacted key: #{compacted_key.inspect}"} unless compacted_key == key
|
181
|
-
|
182
|
-
case key
|
183
|
-
when '@id', '@type'
|
184
|
-
# If the key is @id or @type
|
185
|
-
result[compacted_key] = case value
|
186
|
-
when String, RDF::Value
|
187
|
-
# If the value is a string, compact the value according to IRI Compaction.
|
188
|
-
context.compact_iri(value, :position => :subject, :depth => @depth).to_s
|
189
|
-
when Hash
|
190
|
-
# Otherwise, if value is an object containing only the @id key, the compacted value
|
191
|
-
# if the result of performing IRI Compaction on that value.
|
192
|
-
if value.keys == ["@id"]
|
193
|
-
context.compact_iri(value["@id"], :position => :subject, :depth => @depth).to_s
|
194
|
-
else
|
195
|
-
depth { compact(value, predicate) }
|
196
|
-
end
|
197
|
-
else
|
198
|
-
# Otherwise, the compacted value is the result of performing this algorithm on the value
|
199
|
-
# with the current active property.
|
200
|
-
depth { compact(value, predicate) }
|
201
|
-
end
|
202
|
-
debug("compact") {" => compacted value: #{result[compacted_key].inspect}"}
|
203
|
-
else
|
204
|
-
# Otherwise, if the key is not a keyword, set as active property and compact according to IRI Compaction.
|
205
|
-
unless key[0,1] == '@'
|
206
|
-
predicate = RDF::URI(key)
|
207
|
-
compacted_key = context.compact_iri(key, :position => :predicate, :depth => @depth)
|
208
|
-
debug("compact") {" => compacted key: #{compacted_key.inspect}"}
|
209
|
-
end
|
210
|
-
|
211
|
-
# If the value is an object
|
212
|
-
compacted_value = if value.is_a?(Hash)
|
213
|
-
if value.keys == ['@id'] || value['@literal']
|
214
|
-
# If the value contains only an @id key or the value contains a @literal key, the compacted value
|
215
|
-
# is the result of performing Value Compaction on the value.
|
216
|
-
debug("compact") {"keys: #{value.keys.inspect}"}
|
217
|
-
context.compact_value(predicate, value, :depth => @depth)
|
218
|
-
elsif value.keys == ['@list'] && context.list(predicate)
|
219
|
-
# Otherwise, if the value contains only a @list key, and the active property is subject to list coercion,
|
220
|
-
# the compacted value is the result of performing this algorithm on that value.
|
221
|
-
debug("compact") {"list"}
|
222
|
-
depth {compact(value['@list'], predicate)}
|
223
|
-
else
|
224
|
-
# Otherwise, the compacted value is the result of performing this algorithm on the value
|
225
|
-
debug("compact") {"object"}
|
226
|
-
depth {compact(value, predicate)}
|
227
|
-
end
|
228
|
-
elsif value.is_a?(Array)
|
229
|
-
# Otherwise, if the value is an array, the compacted value is the result of performing this algorithm on the value.
|
230
|
-
debug("compact") {"array"}
|
231
|
-
depth {compact(value, predicate)}
|
232
|
-
else
|
233
|
-
# Otherwise, the value is already compacted.
|
234
|
-
debug("compact") {"value"}
|
235
|
-
value
|
236
|
-
end
|
237
|
-
debug("compact") {" => compacted value: #{compacted_value.inspect}"}
|
238
|
-
result[compacted_key || key] = compacted_value
|
239
|
-
end
|
240
|
-
end
|
241
|
-
result
|
242
|
-
else
|
243
|
-
# For other types, the compacted value is the input value
|
244
|
-
debug("compact") {input.class.to_s}
|
245
|
-
input
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
160
|
##
|
250
161
|
# Frames the given input using the frame according to the steps in the Framing Algorithm. The input is used to build the
|
251
162
|
# framed output and is returned if there are no errors. If there are no matches for the frame, null must be returned.
|
252
163
|
# Exceptions must be thrown if there are errors.
|
253
164
|
#
|
254
|
-
#
|
165
|
+
# The resulting `Array` is returned via the provided callback.
|
166
|
+
#
|
167
|
+
# Note that for Ruby, if the callback is not provided and a block is given, it will be yielded
|
168
|
+
#
|
169
|
+
# @param [String, #read, Hash, Array] input
|
255
170
|
# The JSON-LD object to copy and perform the framing on.
|
256
|
-
# @param [
|
171
|
+
# @param [String, #read, Hash, Array] frame
|
257
172
|
# The frame to use when re-arranging the data.
|
173
|
+
# @param [Proc] callback (&block)
|
174
|
+
# Alternative to using block, with same parameters.
|
258
175
|
# @param [Hash{Symbol => Object}] options
|
259
|
-
#
|
260
|
-
# @
|
176
|
+
# Other options passed to {#expand}
|
177
|
+
# @option options [Boolean] :embed (true)
|
178
|
+
# a flag specifying that objects should be directly embedded in the output,
|
179
|
+
# instead of being referred to by their IRI.
|
180
|
+
# @option options [Boolean] :explicit (false)
|
181
|
+
# a flag specifying that for properties to be included in the output,
|
182
|
+
# they must be explicitly declared in the framing context.
|
183
|
+
# @option options [Boolean] :omitDefault (false)
|
184
|
+
# a flag specifying that properties that are missing from the JSON-LD
|
185
|
+
# input should be omitted from the output.
|
186
|
+
# @yield jsonld
|
187
|
+
# @yieldparam [Hash] jsonld
|
261
188
|
# The framed JSON-LD document
|
262
|
-
|
263
|
-
|
189
|
+
# @return [Array<Hash>]
|
190
|
+
# The framed JSON-LD document
|
191
|
+
# @raise [InvalidFrame]
|
192
|
+
# @see http://json-ld.org/spec/latest/json-ld-api/#framing-algorithm
|
193
|
+
def self.frame(input, frame, callback = nil, options = {})
|
194
|
+
result = nil
|
195
|
+
match_limit = 0
|
196
|
+
framing_state = {
|
197
|
+
:embed => true,
|
198
|
+
:explicit => false,
|
199
|
+
:omitDefault => false,
|
200
|
+
:embeds => nil,
|
201
|
+
}
|
202
|
+
framing_state[:embed] = options[:embed] if options.has_key?(:embed)
|
203
|
+
framing_state[:explicit] = options[:explicit] if options.has_key?(:explicit)
|
204
|
+
framing_state[:omitDefault] = options[:omitDefault] if options.has_key?(:omitDefault)
|
264
205
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
206
|
+
# de-reference frame to create the framing object
|
207
|
+
frame = case frame
|
208
|
+
when Hash then frame.dup
|
209
|
+
when IO, StringIO then JSON.parse(frame.read)
|
210
|
+
when String
|
211
|
+
content = nil
|
212
|
+
RDF::Util::File.open_file(frame) {|f| content = JSON.parse(f.read)}
|
213
|
+
content
|
214
|
+
end
|
215
|
+
|
216
|
+
# Expand frame to simplify processing
|
217
|
+
expanded_frame = API.expand(frame)
|
218
|
+
|
219
|
+
# Expand input to simplify processing
|
220
|
+
expanded_input = API.expand(input)
|
221
|
+
|
222
|
+
# Initialize input using frame as context
|
223
|
+
API.new(expanded_input, nil, options) do
|
224
|
+
debug(".frame") {"context from frame: #{context.inspect}"}
|
225
|
+
debug(".frame") {"expanded frame: #{expanded_frame.to_json(JSON_STATE)}"}
|
226
|
+
debug(".frame") {"expanded input: #{value.to_json(JSON_STATE)}"}
|
227
|
+
|
228
|
+
# Get framing subjects from expanded input, replacing Blank Node identifiers as necessary
|
229
|
+
@subjects = Hash.ordered
|
230
|
+
depth {get_framing_subjects(@subjects, value, BlankNodeNamer.new("t"))}
|
231
|
+
debug(".frame") {"subjects: #{@subjects.to_json(JSON_STATE)}"}
|
232
|
+
|
233
|
+
result = []
|
234
|
+
frame(framing_state, @subjects, expanded_frame[0], result, nil)
|
235
|
+
debug(".frame") {"after frame: #{result.to_json(JSON_STATE)}"}
|
236
|
+
|
237
|
+
# Initalize context from frame
|
238
|
+
@context = depth {@context.parse(frame['@context'])}
|
239
|
+
# Compact result
|
240
|
+
compacted = depth {compact(result, nil)}
|
241
|
+
compacted = [compacted] unless compacted.is_a?(Array)
|
242
|
+
|
243
|
+
# Add the given context to the output
|
244
|
+
kwgraph = context.compact_iri('@graph', :quiet => true)
|
245
|
+
result = context.serialize.merge({kwgraph => compacted})
|
246
|
+
debug(".frame") {"after compact: #{result.to_json(JSON_STATE)}"}
|
247
|
+
result = cleanup_preserve(result)
|
248
|
+
end
|
249
|
+
|
250
|
+
callback.call(result) if callback
|
251
|
+
yield result if block_given?
|
252
|
+
result
|
278
253
|
end
|
279
254
|
|
280
255
|
##
|
281
|
-
# Processes the input according to the RDF Conversion Algorithm, calling the provided
|
256
|
+
# Processes the input according to the RDF Conversion Algorithm, calling the provided callback for each triple generated.
|
257
|
+
#
|
258
|
+
# Note that for Ruby, if the callback is not provided and a block is given, it will be yielded
|
282
259
|
#
|
283
|
-
# @param [
|
284
|
-
# The JSON-LD object to process when outputting
|
285
|
-
# @param [
|
260
|
+
# @param [String, #read, Hash, Array] input
|
261
|
+
# The JSON-LD object to process when outputting statements.
|
262
|
+
# @param [String, #read, Hash, Array] context
|
286
263
|
# An external context to use additionally to the context embedded in input when expanding the input.
|
264
|
+
# @param [Proc] callback (&block)
|
265
|
+
# Alternative to using block, with same parameteres.
|
266
|
+
# @param [{Symbol,String => Object}] options
|
267
|
+
# Options passed to {#expand}
|
287
268
|
# @param [Hash{Symbol => Object}] options
|
288
269
|
# @raise [InvalidContext]
|
289
270
|
# @yield statement
|
290
271
|
# @yieldparam [RDF::Statement] statement
|
291
|
-
|
292
|
-
|
293
|
-
|
272
|
+
def self.toRDF(input, context = nil, callback = nil, options = {})
|
273
|
+
# 1) Perform the Expansion Algorithm on the JSON-LD input.
|
274
|
+
# This removes any existing context to allow the given context to be cleanly applied.
|
275
|
+
expanded = expand(input, context, nil, options)
|
276
|
+
|
277
|
+
API.new(expanded, nil, options) do
|
278
|
+
debug(".expand") {"expanded input: #{value.to_json(JSON_STATE)}"}
|
279
|
+
# Start generating statements
|
280
|
+
statements("", value, nil, nil, nil) do |statement|
|
281
|
+
callback.call(statement) if callback
|
282
|
+
yield statement if block_given?
|
283
|
+
end
|
284
|
+
end
|
294
285
|
end
|
295
286
|
|
296
|
-
|
297
|
-
#
|
287
|
+
##
|
288
|
+
# Take an ordered list of RDF::Statements and turn them into a JSON-LD document.
|
298
289
|
#
|
299
|
-
#
|
300
|
-
#
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
290
|
+
# The resulting `Array` is returned via the provided callback.
|
291
|
+
#
|
292
|
+
# Note that for Ruby, if the callback is not provided and a block is given, it will be yielded
|
293
|
+
#
|
294
|
+
# @param [Array<RDF::Statement>] input
|
295
|
+
# @param [Proc] callback (&block)
|
296
|
+
# Alternative to using block, with same parameteres.
|
297
|
+
# @param [Hash{Symbol => Object}] options
|
298
|
+
# @yield jsonld
|
299
|
+
# @yieldparam [Hash] jsonld
|
300
|
+
# The JSON-LD document in expanded form
|
301
|
+
# @return [Array<Hash>]
|
302
|
+
# The JSON-LD document in expanded form
|
303
|
+
def self.fromRDF(input, callback = nil, options = {})
|
304
|
+
result = nil
|
305
|
+
|
306
|
+
API.new(nil, nil, options) do |api|
|
307
|
+
result = api.from_statements(input, BlankNodeNamer.new("t"))
|
308
|
+
end
|
309
309
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
@depth = (options[:depth] || old_depth) + 1
|
314
|
-
ret = yield
|
315
|
-
@depth = old_depth
|
316
|
-
ret
|
310
|
+
callback.call(result) if callback
|
311
|
+
yield result if block_given?
|
312
|
+
result
|
317
313
|
end
|
318
314
|
end
|
319
315
|
end
|