json-ld 0.9.1 → 1.0.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.
- 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
|
|