json-ld 0.9.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/{README.markdown → README.md} +15 -3
- data/VERSION +1 -1
- data/lib/json/ld.rb +50 -87
- data/lib/json/ld/api.rb +85 -96
- data/lib/json/ld/compact.rb +103 -170
- data/lib/json/ld/context.rb +1137 -0
- data/lib/json/ld/expand.rb +212 -171
- data/lib/json/ld/extensions.rb +17 -1
- data/lib/json/ld/flatten.rb +145 -78
- data/lib/json/ld/frame.rb +1 -1
- data/lib/json/ld/from_rdf.rb +73 -103
- data/lib/json/ld/reader.rb +3 -1
- data/lib/json/ld/resource.rb +3 -3
- data/lib/json/ld/to_rdf.rb +98 -109
- data/lib/json/ld/utils.rb +54 -4
- data/lib/json/ld/writer.rb +5 -5
- data/spec/api_spec.rb +3 -28
- data/spec/compact_spec.rb +76 -113
- data/spec/{evaluation_context_spec.rb → context_spec.rb} +307 -563
- data/spec/expand_spec.rb +163 -187
- data/spec/flatten_spec.rb +119 -114
- data/spec/frame_spec.rb +5 -5
- data/spec/from_rdf_spec.rb +44 -24
- data/spec/suite_compact_spec.rb +11 -8
- data/spec/suite_error_expand_spec.rb +23 -0
- data/spec/suite_expand_spec.rb +3 -7
- data/spec/suite_flatten_spec.rb +3 -3
- data/spec/suite_frame_spec.rb +6 -6
- data/spec/suite_from_rdf_spec.rb +3 -3
- data/spec/suite_helper.rb +13 -6
- data/spec/suite_to_rdf_spec.rb +16 -10
- data/spec/test-files/test-1-rdf.ttl +4 -3
- data/spec/test-files/test-3-rdf.ttl +2 -1
- data/spec/test-files/test-4-compacted.json +1 -1
- data/spec/test-files/test-5-rdf.ttl +3 -2
- data/spec/test-files/test-6-rdf.ttl +3 -2
- data/spec/test-files/test-7-compacted.json +3 -3
- data/spec/test-files/test-7-expanded.json +3 -3
- data/spec/test-files/test-7-rdf.ttl +7 -6
- data/spec/test-files/test-9-compacted.json +1 -1
- data/spec/to_rdf_spec.rb +67 -75
- data/spec/writer_spec.rb +2 -0
- metadata +36 -24
- checksums.yaml +0 -15
- data/lib/json/ld/evaluation_context.rb +0 -984
data/lib/json/ld/reader.rb
CHANGED
@@ -49,7 +49,9 @@ module JSON::LD
|
|
49
49
|
# @private
|
50
50
|
# @see RDF::Reader#each_statement
|
51
51
|
def each_statement(&block)
|
52
|
-
JSON::LD::API.toRDF(@doc, @options[:context],
|
52
|
+
JSON::LD::API.toRDF(@doc, @options[:context], @options).each do |statement|
|
53
|
+
block.call(statement)
|
54
|
+
end
|
53
55
|
end
|
54
56
|
|
55
57
|
##
|
data/lib/json/ld/resource.rb
CHANGED
@@ -11,7 +11,7 @@ module JSON::LD
|
|
11
11
|
attr_reader :id
|
12
12
|
|
13
13
|
# @!attribute [r] context
|
14
|
-
# @return [JSON::LD::
|
14
|
+
# @return [JSON::LD::Context] Context associated with this resource
|
15
15
|
attr_reader :context
|
16
16
|
|
17
17
|
# Is this resource clean (i.e., saved to mongo?)
|
@@ -51,9 +51,9 @@ module JSON::LD
|
|
51
51
|
# Manage contexts used by resources.
|
52
52
|
#
|
53
53
|
# @param [String] ctx
|
54
|
-
# @return [JSON::LD::
|
54
|
+
# @return [JSON::LD::Context]
|
55
55
|
def self.set_context(ctx)
|
56
|
-
(@@contexts ||= {})[ctx] = JSON::LD::
|
56
|
+
(@@contexts ||= {})[ctx] = JSON::LD::Context.new.parse(ctx)
|
57
57
|
end
|
58
58
|
|
59
59
|
# A new resource from the parsed graph
|
data/lib/json/ld/to_rdf.rb
CHANGED
@@ -7,145 +7,134 @@ module JSON::LD
|
|
7
7
|
|
8
8
|
##
|
9
9
|
#
|
10
|
-
# @param [String]
|
11
|
-
#
|
12
|
-
# @
|
13
|
-
|
14
|
-
|
15
|
-
# Inherited subject
|
16
|
-
# @param [RDF::URI] property
|
17
|
-
# Inherited property
|
18
|
-
# @param [RDF::Node] name
|
19
|
-
# Inherited inherited graph name
|
20
|
-
# @return [RDF::Resource] defined by this element
|
21
|
-
# @yield statement
|
22
|
-
# @yieldparam [RDF::Statement] statement
|
23
|
-
def statements(path, element, subject, property, name, &block)
|
24
|
-
debug(path) {"statements: e=#{element.inspect}, s=#{subject.inspect}, p=#{property.inspect}, n=#{name.inspect}"}
|
10
|
+
# @param [Hash{String => Hash}] active_graph
|
11
|
+
# A hash of IRI to Node definitions
|
12
|
+
# @return [Array<RDF::Statement>] statements in this graph, without context
|
13
|
+
def graph_to_rdf(active_graph)
|
14
|
+
debug('graph_to_rdf') {"graph_to_rdf: #{active_graph.inspect}"}
|
25
15
|
|
26
|
-
|
27
|
-
|
28
|
-
when Hash
|
29
|
-
# Other shortcuts to allow use of this method for terminal associative arrays
|
30
|
-
object = if element.has_key?('@value')
|
31
|
-
# 1.2) If the JSON object has a @value key, set the active object to a literal value as follows ...
|
32
|
-
literal_opts = {}
|
33
|
-
literal_opts[:datatype] = RDF::URI(element['@type']) if element['@type']
|
34
|
-
literal_opts[:language] = element['@language'].to_sym if element['@language']
|
35
|
-
RDF::Literal.new(element['@value'], literal_opts)
|
36
|
-
elsif element.has_key?('@list')
|
37
|
-
# 1.3 (Lists)
|
38
|
-
parse_list("#{path}[#{'@list'}]", element['@list'], property, name, &block)
|
39
|
-
end
|
16
|
+
# Initialize results as an empty array
|
17
|
+
results = []
|
40
18
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
active_subject = if element.fetch('@id', nil).is_a?(String)
|
48
|
-
# 1.5 Subject
|
49
|
-
# 1.5.1 Set active object (subject)
|
50
|
-
context.expand_iri(element['@id'], :quiet => true)
|
51
|
-
else
|
52
|
-
# 1.6) Generate a blank node identifier and set it as the active subject.
|
53
|
-
node
|
54
|
-
end
|
19
|
+
depth do
|
20
|
+
# For each id-node in active_graph
|
21
|
+
active_graph.each do |id, node|
|
22
|
+
# Initialize subject as the IRI or BNode representation of id
|
23
|
+
subject = as_resource(id)
|
24
|
+
debug("graph_to_rdf") {"subject: #{subject.to_ntriples}"}
|
55
25
|
|
56
|
-
#
|
57
|
-
|
58
|
-
|
59
|
-
value = element[key]
|
60
|
-
active_property = case key
|
26
|
+
# For each property-values in node
|
27
|
+
node.each do |property, values|
|
28
|
+
case property
|
61
29
|
when '@type'
|
62
|
-
# If
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
statements("#{path}[#{key}]", value, nil, nil, (active_subject unless active_subject.node?), &block)
|
69
|
-
next
|
30
|
+
# If property is @type, construct triple as an RDF Triple composed of id, rdf:type, and object from values where id and object are represented either as IRIs or Blank Nodes
|
31
|
+
results += values.map do |value|
|
32
|
+
object = as_resource(value)
|
33
|
+
debug("graph_to_rdf") {"type: #{object.to_ntriples}"}
|
34
|
+
RDF::Statement.new(subject, RDF.type, object)
|
35
|
+
end
|
70
36
|
when /^@/
|
71
|
-
# Otherwise, if
|
72
|
-
next
|
37
|
+
# Otherwise, if @type is any other keyword, skip to the next property-values pair
|
73
38
|
else
|
74
|
-
#
|
75
|
-
#
|
76
|
-
|
77
|
-
|
39
|
+
# Otherwise, property is an IRI or Blank Node identifier
|
40
|
+
# Initialize predicate from property as an IRI or Blank node
|
41
|
+
predicate = as_resource(property)
|
42
|
+
debug("graph_to_rdf") {"predicate: #{predicate.to_ntriples}"}
|
78
43
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
44
|
+
# For each item in values
|
45
|
+
values.each do |item|
|
46
|
+
if item.has_key?('@list')
|
47
|
+
debug("graph_to_rdf") {"list: #{item.inspect}"}
|
48
|
+
# If item is a list object, initialize list_results as an empty array, and object to the result of the List Conversion algorithm, passing the value associated with the @list key from item and list_results.
|
49
|
+
list_results = []
|
50
|
+
object = parse_list(item['@list'], list_results)
|
51
|
+
|
52
|
+
# Append a triple composed of subject, prediate, and object to results and add all triples from list_results to results.
|
53
|
+
results << RDF::Statement.new(subject, predicate, object)
|
54
|
+
results += list_results
|
55
|
+
else
|
56
|
+
# Otherwise, item is a value object or a node definition. Generate object as the result of the Object Converstion algorithm passing item.
|
57
|
+
object = parse_object(item)
|
58
|
+
debug("graph_to_rdf") {"object: #{object.to_ntriples}"}
|
59
|
+
# Append a triple composed of subject, prediate, and literal to results.
|
60
|
+
results << RDF::Statement.new(subject, predicate, object)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
90
64
|
end
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return results
|
69
|
+
results
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Parse an item, either a value object or a node definition
|
74
|
+
# @param [Hash] item
|
75
|
+
# @return [RDF::Value]
|
76
|
+
def parse_object(item)
|
77
|
+
if item.has_key?('@value')
|
78
|
+
# Otherwise, if element is a JSON object that contains the key @value
|
79
|
+
# Initialize value to the value associated with the @value key in element. Initialize datatype to the value associated with the @type key in element, or null if element does not contain that key.
|
80
|
+
value, datatype = item.fetch('@value'), item.fetch('@type', nil)
|
81
|
+
|
82
|
+
case value
|
104
83
|
when TrueClass, FalseClass
|
105
|
-
|
106
|
-
|
107
|
-
|
84
|
+
# If value is true or false, then set value its canonical lexical form as defined in the section Data Round Tripping. If datatype is null, set it to xsd:boolean.
|
85
|
+
value = value.to_s
|
86
|
+
datatype ||= RDF::XSD.boolean.to_s
|
87
|
+
when Float, Fixnum
|
88
|
+
# Otherwise, if value is a number, then set value to its canonical lexical form as defined in the section Data Round Tripping. If datatype is null, set it to either xsd:integer or xsd:double, depending on if the value contains a fractional and/or an exponential component.
|
89
|
+
lit = RDF::Literal.new(value, :canonicalize => true)
|
90
|
+
value = lit.to_s
|
91
|
+
datatype ||= lit.datatype
|
108
92
|
else
|
109
|
-
|
93
|
+
# Otherwise, if datatype is null, set it to xsd:string or rdf:langString, depending on if item has a @language key.
|
94
|
+
datatype ||= item.has_key?('@language') ? RDF.langString : RDF::XSD.string
|
110
95
|
end
|
111
|
-
|
96
|
+
|
97
|
+
# Initialize literal as an RDF literal using value and datatype. If element has the key @language and datatype is xsd:string, then add the value associated with the @language key as the language of the object.
|
98
|
+
language = item.fetch('@language', nil)
|
99
|
+
literal = RDF::Literal.new(value, :datatype => datatype, :language => language)
|
112
100
|
|
113
|
-
|
114
|
-
|
115
|
-
|
101
|
+
# Return literal
|
102
|
+
literal
|
103
|
+
else
|
104
|
+
# Otherwise, value must be a node definition containing only @id whos value is an IRI or Blank Node identifier
|
105
|
+
raise "Expected node reference, got #{item.inspect}" unless item.keys == %w(@id)
|
106
|
+
# Return value associated with @id as an IRI or Blank node
|
107
|
+
as_resource(item['@id'])
|
108
|
+
end
|
116
109
|
end
|
117
110
|
|
118
111
|
##
|
119
112
|
# Parse a List
|
120
113
|
#
|
121
|
-
# @param [String] path
|
122
|
-
# location within JSON hash
|
123
114
|
# @param [Array] list
|
124
115
|
# The Array to serialize as a list
|
125
|
-
# @param [RDF::
|
126
|
-
#
|
127
|
-
# @param [RDF::Resource] name
|
128
|
-
# Inherited named graph context
|
116
|
+
# @param [Array<RDF::Statement>] list_results
|
117
|
+
# Statements for each item in the list
|
129
118
|
# @return [RDF::Resource] BNode or nil for head of list
|
130
|
-
|
131
|
-
|
132
|
-
def parse_list(path, list, property, name, &block)
|
133
|
-
debug(path) {"list: #{list.inspect}, p=#{property.inspect}, n=#{name.inspect}"}
|
119
|
+
def parse_list(list, list_results)
|
120
|
+
debug('parse_list') {"list: #{list.inspect}"}
|
134
121
|
|
135
122
|
last = list.pop
|
136
123
|
result = first_bnode = last ? node : RDF.nil
|
137
124
|
|
138
125
|
depth do
|
139
126
|
list.each do |list_item|
|
140
|
-
#
|
141
|
-
|
127
|
+
# Set first to the result of the Object Converstion algorithm passing item.
|
128
|
+
object = parse_object(list_item)
|
129
|
+
list_results << RDF::Statement.new(first_bnode, RDF.first, object)
|
142
130
|
rest_bnode = node
|
143
|
-
|
131
|
+
list_results << RDF::Statement.new(first_bnode, RDF.rest, rest_bnode)
|
144
132
|
first_bnode = rest_bnode
|
145
133
|
end
|
146
134
|
if last
|
147
|
-
|
148
|
-
|
135
|
+
object = parse_object(last)
|
136
|
+
list_results << RDF::Statement.new(first_bnode, RDF.first, object)
|
137
|
+
list_results << RDF::Statement.new(first_bnode, RDF.rest, RDF.nil)
|
149
138
|
end
|
150
139
|
end
|
151
140
|
result
|
data/lib/json/ld/utils.rb
CHANGED
@@ -27,7 +27,12 @@ module JSON::LD
|
|
27
27
|
# @param [Object] value
|
28
28
|
# @return [Boolean]
|
29
29
|
def blank_node?(value)
|
30
|
-
|
30
|
+
case value
|
31
|
+
when nil then true
|
32
|
+
when String then value[0,2] == '_:'
|
33
|
+
else
|
34
|
+
(node?(value) || node_reference?(value)) && value.fetch('@id', '_:')[0,2] == '_:'
|
35
|
+
end
|
31
36
|
end
|
32
37
|
|
33
38
|
##
|
@@ -57,17 +62,62 @@ module JSON::LD
|
|
57
62
|
value.is_a?(Hash) && value.has_key?('@value')
|
58
63
|
end
|
59
64
|
|
65
|
+
##
|
66
|
+
# Represent an id as an IRI or Blank Node
|
67
|
+
# @param [String] id
|
68
|
+
# @return [RDF::Resource]
|
69
|
+
def as_resource(id)
|
70
|
+
@nodes ||= {} # Re-use BNodes
|
71
|
+
id[0,2] == '_:' ? (@nodes[id] ||= RDF::Node.new(id[2..-1])) : RDF::URI(id)
|
72
|
+
end
|
73
|
+
|
60
74
|
private
|
61
75
|
|
76
|
+
# Merge the last value into an array based for the specified key if hash is not null and value is not already in that array
|
77
|
+
def merge_value(hash, key, value)
|
78
|
+
return unless hash
|
79
|
+
values = hash[key] ||= []
|
80
|
+
if key == '@list'
|
81
|
+
values << value
|
82
|
+
elsif list?(value)
|
83
|
+
values << value
|
84
|
+
elsif !values.include?(value)
|
85
|
+
values << value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Merge values into compacted results, creating arrays if necessary
|
90
|
+
def merge_compacted_value(hash, key, value)
|
91
|
+
return unless hash
|
92
|
+
case hash[key]
|
93
|
+
when nil then hash[key] = value
|
94
|
+
when Array
|
95
|
+
if value.is_a?(Array)
|
96
|
+
hash[key].concat(value)
|
97
|
+
else
|
98
|
+
hash[key] << value
|
99
|
+
end
|
100
|
+
else
|
101
|
+
hash[key] = [hash[key]]
|
102
|
+
if value.is_a?(Array)
|
103
|
+
hash[key].concat(value)
|
104
|
+
else
|
105
|
+
hash[key] << value
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
62
110
|
# Add debug event to debug array, if specified
|
63
111
|
#
|
64
112
|
# param [String] message
|
65
113
|
# yieldreturn [String] appended to message, to allow for lazy-evaulation of message
|
66
114
|
def debug(*args)
|
115
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
67
116
|
return unless ::JSON::LD.debug? || @options[:debug]
|
117
|
+
depth = options[:depth] || @depth || 0
|
68
118
|
list = args
|
69
119
|
list << yield if block_given?
|
70
|
-
message = " " *
|
120
|
+
message = " " * depth * 2 + (list.empty? ? "" : list.join(": "))
|
71
121
|
puts message if JSON::LD::debug?
|
72
122
|
@options[:debug] << message if @options[:debug].is_a?(Array)
|
73
123
|
end
|
@@ -76,9 +126,9 @@ module JSON::LD
|
|
76
126
|
def depth(options = {})
|
77
127
|
old_depth = @depth || 0
|
78
128
|
@depth = (options[:depth] || old_depth) + 1
|
79
|
-
|
129
|
+
yield
|
130
|
+
ensure
|
80
131
|
@depth = old_depth
|
81
|
-
ret
|
82
132
|
end
|
83
133
|
end
|
84
134
|
|
data/lib/json/ld/writer.rb
CHANGED
@@ -59,7 +59,7 @@ module JSON::LD
|
|
59
59
|
attr_reader :graph
|
60
60
|
|
61
61
|
# @!attribute [r] context
|
62
|
-
# @return [
|
62
|
+
# @return [Context] context used to load and administer contexts
|
63
63
|
attr_reader :context
|
64
64
|
|
65
65
|
##
|
@@ -83,7 +83,7 @@ module JSON::LD
|
|
83
83
|
# the prefix mappings to use (not supported by all writers)
|
84
84
|
# @option options [Boolean] :standard_prefixes (false)
|
85
85
|
# Add standard prefixes to @prefixes, if necessary.
|
86
|
-
# @option options [IO, Array, Hash, String,
|
86
|
+
# @option options [IO, Array, Hash, String, Context] :context (Hash.ordered)
|
87
87
|
# context to use when serializing. Constructed context for native serialization.
|
88
88
|
# @yield [writer] `self`
|
89
89
|
# @yieldparam [RDF::Writer] writer
|
@@ -149,13 +149,13 @@ module JSON::LD
|
|
149
149
|
# Turn graph into a triple array
|
150
150
|
statements = @repo.each_statement.to_a
|
151
151
|
debug("writer") { "serialize #{statements.length} statements, #{@options.inspect}"}
|
152
|
-
result = API.fromRDF(statements,
|
152
|
+
result = API.fromRDF(statements, @options)
|
153
153
|
|
154
154
|
# If we were provided a context, or prefixes, use them to compact the output
|
155
155
|
context = RDF::Util::File.open_file(@options[:context]) if @options[:context].is_a?(String)
|
156
156
|
context ||= @options[:context]
|
157
157
|
context ||= if @options[:prefixes] || @options[:language] || @options[:standard_prefixes]
|
158
|
-
ctx =
|
158
|
+
ctx = Context.new(@options)
|
159
159
|
ctx.language = @options[:language] if @options[:language]
|
160
160
|
@options[:prefixes].each do |prefix, iri|
|
161
161
|
ctx.set_mapping(prefix, iri) if prefix && iri
|
@@ -166,7 +166,7 @@ module JSON::LD
|
|
166
166
|
# Perform compaction, if we have a context
|
167
167
|
if context
|
168
168
|
debug("writer") { "compact result"}
|
169
|
-
result = API.compact(result, context,
|
169
|
+
result = API.compact(result, context, @options)
|
170
170
|
end
|
171
171
|
|
172
172
|
@output.write(result.to_json(JSON_STATE))
|
data/spec/api_spec.rb
CHANGED
@@ -5,28 +5,6 @@ require 'spec_helper'
|
|
5
5
|
describe JSON::LD::API do
|
6
6
|
before(:each) { @debug = []}
|
7
7
|
|
8
|
-
context "callbacks" do
|
9
|
-
describe ".compact" do
|
10
|
-
it "needs to be implemented"
|
11
|
-
end
|
12
|
-
|
13
|
-
describe ".expand" do
|
14
|
-
it "needs to be implemented"
|
15
|
-
end
|
16
|
-
|
17
|
-
describe ".frame" do
|
18
|
-
it "needs to be implemented"
|
19
|
-
end
|
20
|
-
|
21
|
-
describe ".fromRDF" do
|
22
|
-
it "needs to be implemented"
|
23
|
-
end
|
24
|
-
|
25
|
-
describe ".toRDF" do
|
26
|
-
it "needs to be implemented"
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
8
|
context "Test Files" do
|
31
9
|
Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), 'test-files/*-input.*'))) do |filename|
|
32
10
|
test = File.basename(filename).sub(/-input\..*$/, '')
|
@@ -39,21 +17,18 @@ describe JSON::LD::API do
|
|
39
17
|
ttl = filename.sub(/-input\..*$/, '-rdf.ttl')
|
40
18
|
|
41
19
|
context test do
|
42
|
-
before(:all) do
|
43
|
-
end
|
44
|
-
|
45
20
|
it "expands" do
|
46
|
-
jld = JSON::LD::API.expand(File.open(filename), (File.open(context) if context),
|
21
|
+
jld = JSON::LD::API.expand(File.open(filename), (File.open(context) if context), :debug => @debug)
|
47
22
|
jld.should produce(JSON.load(File.open(expanded)), @debug)
|
48
23
|
end if File.exist?(expanded)
|
49
24
|
|
50
25
|
it "compacts" do
|
51
|
-
jld = JSON::LD::API.compact(File.open(filename), File.open(context),
|
26
|
+
jld = JSON::LD::API.compact(File.open(filename), File.open(context), :debug => @debug)
|
52
27
|
jld.should produce(JSON.load(File.open(compacted)), @debug)
|
53
28
|
end if File.exist?(compacted) && File.exist?(context)
|
54
29
|
|
55
30
|
it "frame" do
|
56
|
-
jld = JSON::LD::API.frame(File.open(filename), File.open(frame),
|
31
|
+
jld = JSON::LD::API.frame(File.open(filename), File.open(frame), :debug => @debug)
|
57
32
|
jld.should produce(JSON.load(File.open(framed)), @debug)
|
58
33
|
end if File.exist?(framed) && File.exist?(frame)
|
59
34
|
|