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.
Files changed (45) hide show
  1. data/{README.markdown → README.md} +15 -3
  2. data/VERSION +1 -1
  3. data/lib/json/ld.rb +50 -87
  4. data/lib/json/ld/api.rb +85 -96
  5. data/lib/json/ld/compact.rb +103 -170
  6. data/lib/json/ld/context.rb +1137 -0
  7. data/lib/json/ld/expand.rb +212 -171
  8. data/lib/json/ld/extensions.rb +17 -1
  9. data/lib/json/ld/flatten.rb +145 -78
  10. data/lib/json/ld/frame.rb +1 -1
  11. data/lib/json/ld/from_rdf.rb +73 -103
  12. data/lib/json/ld/reader.rb +3 -1
  13. data/lib/json/ld/resource.rb +3 -3
  14. data/lib/json/ld/to_rdf.rb +98 -109
  15. data/lib/json/ld/utils.rb +54 -4
  16. data/lib/json/ld/writer.rb +5 -5
  17. data/spec/api_spec.rb +3 -28
  18. data/spec/compact_spec.rb +76 -113
  19. data/spec/{evaluation_context_spec.rb → context_spec.rb} +307 -563
  20. data/spec/expand_spec.rb +163 -187
  21. data/spec/flatten_spec.rb +119 -114
  22. data/spec/frame_spec.rb +5 -5
  23. data/spec/from_rdf_spec.rb +44 -24
  24. data/spec/suite_compact_spec.rb +11 -8
  25. data/spec/suite_error_expand_spec.rb +23 -0
  26. data/spec/suite_expand_spec.rb +3 -7
  27. data/spec/suite_flatten_spec.rb +3 -3
  28. data/spec/suite_frame_spec.rb +6 -6
  29. data/spec/suite_from_rdf_spec.rb +3 -3
  30. data/spec/suite_helper.rb +13 -6
  31. data/spec/suite_to_rdf_spec.rb +16 -10
  32. data/spec/test-files/test-1-rdf.ttl +4 -3
  33. data/spec/test-files/test-3-rdf.ttl +2 -1
  34. data/spec/test-files/test-4-compacted.json +1 -1
  35. data/spec/test-files/test-5-rdf.ttl +3 -2
  36. data/spec/test-files/test-6-rdf.ttl +3 -2
  37. data/spec/test-files/test-7-compacted.json +3 -3
  38. data/spec/test-files/test-7-expanded.json +3 -3
  39. data/spec/test-files/test-7-rdf.ttl +7 -6
  40. data/spec/test-files/test-9-compacted.json +1 -1
  41. data/spec/to_rdf_spec.rb +67 -75
  42. data/spec/writer_spec.rb +2 -0
  43. metadata +36 -24
  44. checksums.yaml +0 -15
  45. data/lib/json/ld/evaluation_context.rb +0 -984
@@ -1,8 +1,10 @@
1
1
  # JSON-LD reader/writer
2
2
 
3
- [![Build Status](https://secure.travis-ci.org/gkellogg/json-ld.png?branch=master)](http://travis-ci.org/gkellogg/json-ld)
4
3
  [JSON-LD][] reader/writer for [RDF.rb][RDF.rb] and fully conforming [JSON-LD][] processor.
5
4
 
5
+ [![Gem Version](https://badge.fury.io/rb/json-ld.png)](http://badge.fury.io/rb/json-ld)
6
+ [![Build Status](https://secure.travis-ci.org/gkellogg/json-ld.png?branch=master)](http://travis-ci.org/gkellogg/json-ld)
7
+
6
8
  ## Features
7
9
 
8
10
  JSON::LD parses and serializes [JSON-LD][] into [RDF][] and implements expansion, compaction and framing API interfaces.
@@ -206,11 +208,20 @@ Install with `gem install json-ld`
206
208
  ## Documentation
207
209
  Full documentation available on [RubyDoc](http://rubydoc.info/gems/json-ld/file/README.markdown)
208
210
 
211
+ ## Differences from [JSON-LD API][]
212
+ The specified JSON-LD API is based on a WebIDL definition intended for use within the browser.
213
+ This version implements a more Ruby-like variation of this API without the use
214
+ of futures and callback arguments, preferring Ruby blocks. All API methods
215
+ execute synchronously, so that the return from a method can be used as well as a block.
216
+
217
+ Note, the API method signatures differed in versions before 1.0, in that they also had
218
+ a callback parameter.
219
+
209
220
  ### Principal Classes
210
221
  * {JSON::LD}
211
222
  * {JSON::LD::API}
212
223
  * {JSON::LD::Compact}
213
- * {JSON::LD::EvaluationContext}
224
+ * {JSON::LD::Context}
214
225
  * {JSON::LD::Format}
215
226
  * {JSON::LD::Frame}
216
227
  * {JSON::LD::FromRDF}
@@ -219,7 +230,7 @@ Full documentation available on [RubyDoc](http://rubydoc.info/gems/json-ld/file/
219
230
  * {JSON::LD::Writer}
220
231
 
221
232
  ## Dependencies
222
- * [Ruby](http://ruby-lang.org/) (>= 1.8.7) or (>= 1.8.1 with [Backports][])
233
+ * [Ruby](http://ruby-lang.org/) (>= 1.9.2)
223
234
  * [RDF.rb](http://rubygems.org/gems/rdf) (>= 1.0)
224
235
  * [JSON](https://rubygems.org/gems/json) (>= 1.5)
225
236
 
@@ -267,3 +278,4 @@ see <http://unlicense.org/> or the accompanying {file:UNLICENSE} file.
267
278
  [RDF.rb]: http://rubygems.org/gems/rdf
268
279
  [Backports]: http://rubygems.org/gems/backports
269
280
  [JSON-LD]: http://json-ld.org/spec/latest/
281
+ [JSON-LD API]: http://json-ld.org/spec/latest/json-ld-api/
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.1
1
+ 1.0.0
@@ -26,7 +26,7 @@ module JSON
26
26
  require 'json/ld/format'
27
27
  require 'json/ld/utils'
28
28
  autoload :API, 'json/ld/api'
29
- autoload :EvaluationContext, 'json/ld/evaluation_context'
29
+ autoload :Context, 'json/ld/context'
30
30
  autoload :Normalize, 'json/ld/normalize'
31
31
  autoload :Reader, 'json/ld/reader'
32
32
  autoload :Resource, 'json/ld/resource'
@@ -40,11 +40,20 @@ module JSON
40
40
  }.freeze
41
41
 
42
42
  KEYWORDS = %w(
43
+ @base
43
44
  @container
44
45
  @context
46
+ @default
47
+ @embed
48
+ @embedChildren
49
+ @explicit
45
50
  @id
51
+ @index
52
+ @graph
46
53
  @language
47
54
  @list
55
+ @omitDefault
56
+ @reverse
48
57
  @set
49
58
  @type
50
59
  @value
@@ -80,98 +89,52 @@ module JSON
80
89
  def self.debug=(value); @debug = value; end
81
90
 
82
91
  class ProcessingError < Exception
83
- # The compaction would lead to a loss of information, such as a @language value.
84
- LOSSY_COMPACTION = 1
85
-
86
- # The target datatype specified in the coercion rule and the datatype for the typed literal do not match.
87
- CONFLICTING_DATATYPES = 2
88
-
89
- # A list containing another list was detected.
90
- LIST_OF_LISTS_DETECTED = 3
91
-
92
- # When processing a language map, a value not a
93
- ILLEGAL_LANGUAGE_MAP_DETECTED = 4
94
-
95
- attr_reader :code
96
-
97
- class Lossy < ProcessingError
98
- def initialize(*args)
99
- super
100
- @code = LOSSY_COMPACTION
101
- end
102
- end
103
-
104
- class Conflict < ProcessingError
105
- def initialize(*args)
106
- super
107
- @code = CONFLICTING_DATATYPES
108
- end
109
- end
110
-
111
- class LanguageMap < ProcessingError
112
- def initialize(*args)
113
- super
114
- @code = ILLEGAL_LANGUAGE_MAP_DETECTED
115
- end
116
- end
117
-
118
- class ListOfLists < ProcessingError
119
- def initialize(*args)
120
- super
121
- @code = LIST_OF_LISTS_DETECTED
122
- end
123
- end
92
+ class CompactionToListOfLists < ProcessingError; end
93
+ class Conflict < ProcessingError; end
94
+ class ConflictingIndexes < ProcessingError; end
95
+ class InvalidIdValue < ProcessingError; end
96
+ class InvalidIndexValue < ProcessingError; end
97
+ class InvalidLanguageMapValue < ProcessingError; end
98
+ class InvalidLanguageTaggedString < ProcessingError; end
99
+ class InvalidLanguageTaggedValue < ProcessingError; end
100
+ class InvalidReversePropertyMap < ProcessingError; end
101
+ class InvalidReversePropertyValue < ProcessingError; end
102
+ class InvalidReverseValue < ProcessingError; end
103
+ class InvalidSetOrListObject < ProcessingError; end
104
+ class InvalidTypedValue < ProcessingError; end
105
+ class InvalidTypeValue < ProcessingError; end
106
+ class InvalidValueObject < ProcessingError; end
107
+ class InvalidValueObjectValue < ProcessingError; end
108
+ class LanguageMap < ProcessingError; end
109
+ class ListOfLists < ProcessingError; end
110
+ class LoadingDocumentFailed < ProcessingError; end
111
+ class Lossy < ProcessingError; end
124
112
  end
125
113
 
126
114
  class InvalidContext < Exception
127
- # A general syntax error was detected in the @context. For example, if a @type key maps to anything
128
- # other than @id or an absolute IRI, this exception would be raised.
129
- INVALID_SYNTAX = 1
130
-
131
- # There was a problem encountered loading a remote context.
132
- LOAD_ERROR = 2
133
-
134
- attr_reader :code
135
-
136
- class Syntax < InvalidContext
137
- def initialize(*args)
138
- super
139
- @code = INVALID_SYNTAX
140
- end
141
- end
142
-
143
- class LoadError < InvalidContext
144
- def initialize(*args)
145
- super
146
- @code = LOAD_ERROR
147
- end
148
- end
115
+ class CollidingKeywords < InvalidContext; end
116
+ class CyclicIRIMapping < InvalidContext; end
117
+ class InvalidBaseIRI < InvalidContext; end
118
+ class InvalidBaseIRI < InvalidContext; end
119
+ class InvalidContainerMapping < InvalidContext; end
120
+ class InvalidDefaultLanguage < InvalidContext; end
121
+ class InvalidIRIMapping < InvalidContext; end
122
+ class InvalidKeywordAlias < InvalidContext; end
123
+ class InvalidLanguageMapping < InvalidContext; end
124
+ class InvalidLocalContext < InvalidContext; end
125
+ class InvalidRemoteContext < InvalidContext; end
126
+ class InvalidReverseProperty < InvalidContext; end
127
+ class InvalidTermDefinition < InvalidContext; end
128
+ class InvalidTypeMapping < InvalidContext; end
129
+ class InvalidVocabMapping < InvalidContext; end
130
+ class KeywordRedefinition < InvalidContext; end
131
+ class LoadingRemoteContextFailed < InvalidContext; end
132
+ class RecursiveContextInclusion < InvalidContext; end
149
133
  end
150
134
 
151
135
  class InvalidFrame < Exception
152
- # A frame must be either an object or an array of objects, if the frame is neither of these types,
153
- # this exception is thrown.
154
- INVALID_SYNTAX = 1
155
-
156
- # A subject IRI was specified in more than one place in the input frame.
157
- # More than one embed of a given subject IRI is not allowed, and if requested, must result in this exception.
158
- MULTIPLE_EMBEDS = 2
159
-
160
- attr_reader :code
161
-
162
- class Syntax < InvalidFrame
163
- def initialize(*args)
164
- super
165
- @code = INVALID_SYNTAX
166
- end
167
- end
168
-
169
- class MultipleEmbeds < InvalidFrame
170
- def initialize(*args)
171
- super
172
- @code = MULTIPLE_EMBEDS
173
- end
174
- end
136
+ class MultipleEmbeds < InvalidFrame; end
137
+ class Syntax < InvalidFrame; end
175
138
  end
176
139
  end
177
140
  end
@@ -8,11 +8,11 @@ require 'json/ld/from_rdf'
8
8
 
9
9
  module JSON::LD
10
10
  ##
11
- # A JSON-LD processor implementing the JsonLdProcessor interface.
11
+ # A JSON-LD processor based on the JsonLdProcessor interface.
12
12
  #
13
- # This API provides a clean mechanism that enables developers to convert JSON-LD data into a a variety of output formats that
14
- # are easier to work with in various programming languages. If a JSON-LD API is provided in a programming environment, the
15
- # entirety of the following API must be implemented.
13
+ # This API provides a clean mechanism that enables developers to convert JSON-LD data into a a variety of output formats that are easier to work with in various programming languages. If a JSON-LD API is provided in a programming environment, the entirety of the following API must be implemented.
14
+ #
15
+ # Note that the API method signatures are somewhat different than what is specified, as the use of Futures and explicit callback parameters is not as relevant for Ruby-based interfaces.
16
16
  #
17
17
  # @see http://json-ld.org/spec/latest/json-ld-api/#the-application-programming-interface
18
18
  # @author [Gregg Kellogg](http://greggkellogg.net/)
@@ -36,7 +36,7 @@ module JSON::LD
36
36
 
37
37
  # Input evaluation context
38
38
  # @!attribute [rw] context
39
- # @return [JSON::LD::EvaluationContext]
39
+ # @return [JSON::LD::Context]
40
40
  attr_accessor :context
41
41
 
42
42
  # Current Blank Node Namer
@@ -48,15 +48,13 @@ module JSON::LD
48
48
  # Initialize the API, reading in any document and setting global options
49
49
  #
50
50
  # @param [String, #read, Hash, Array] input
51
- # @param [String, #read,, Hash, Array, JSON::LD::EvaluationContext] context
51
+ # @param [String, #read,, Hash, Array, JSON::LD::Context] context
52
52
  # An external context to use additionally to the context embedded in input when expanding the input.
53
53
  # @param [Hash{Symbol => Object}] options
54
54
  # @option options [Boolean] :base
55
55
  # The Base IRI to use when expanding the document. This overrides the value of `input` if it is a _IRI_. If not specified and `input` is not an _IRI_, the base IRI defaults to the current document IRI if in a browser context, or the empty string if there is no document context.
56
56
  # @option options [Boolean] :compactArrays (true)
57
57
  # If set to `true`, the JSON-LD processor replaces arrays with just one element with that element during compaction. If set to `false`, all arrays will remain arrays even if they have just one element.
58
- # @option options [Proc] :conformanceCallback
59
- # The purpose of this option is to instruct the processor about whether or not it should continue processing. If the value is null, the processor should ignore any key-value pair associated with any recoverable conformance issue and continue processing. More details about this feature can be found in the ConformanceCallback section.
60
58
  # @option options [Boolean, String, RDF::URI] :flatten
61
59
  # If set to a value that is not `false`, the JSON-LD processor must modify the output of the Compaction Algorithm or the Expansion Algorithm by coalescing all properties associated with each subject via the Flattening Algorithm. The value of `flatten must` be either an _IRI_ value representing the name of the graph to flatten, or `true`. If the value is `true`, then the first graph encountered in the input document is selected and flattened.
62
60
  # @option options [Boolean] :optimize (false)
@@ -73,7 +71,7 @@ module JSON::LD
73
71
  def initialize(input, context, options = {}, &block)
74
72
  @options = {:compactArrays => true}.merge(options)
75
73
  options = {:rename_bnodes => true}.merge(options)
76
- @namer = options[:rename_bnodes] ? BlankNodeNamer.new("t") : BlankNodeMapper.new
74
+ @namer = options[:rename_bnodes] ? BlankNodeNamer.new("b") : BlankNodeMapper.new
77
75
  @value = case input
78
76
  when Array, Hash then input.dup
79
77
  when IO, StringIO then JSON.parse(input.read)
@@ -83,7 +81,7 @@ module JSON::LD
83
81
  RDF::Util::File.open_file(input, OPEN_OPTS) {|f| content = JSON.parse(f.read)}
84
82
  content
85
83
  end
86
- @context = EvaluationContext.new(@options)
84
+ @context = Context.new(@options)
87
85
  @context = @context.parse(context) if context
88
86
 
89
87
  if block_given?
@@ -98,16 +96,12 @@ module JSON::LD
98
96
  # Expands the given input according to the steps in the Expansion Algorithm. The input must be copied, expanded and returned
99
97
  # if there are no errors. If the expansion fails, an appropriate exception must be thrown.
100
98
  #
101
- # The resulting `Array` is returned via the provided callback.
102
- #
103
- # Note that for Ruby, if the callback is not provided and a block is given, it will be yielded
99
+ # The resulting `Array` either returned or yielded
104
100
  #
105
101
  # @param [String, #read, Hash, Array] input
106
102
  # The JSON-LD object to copy and perform the expansion upon.
107
- # @param [String, #read, Hash, Array, JSON::LD::EvaluationContext] context
103
+ # @param [String, #read, Hash, Array, JSON::LD::Context] context
108
104
  # An external context to use additionally to the context embedded in input when expanding the input.
109
- # @param [Proc] callback (&block)
110
- # Alternative to using block, with same parameters.
111
105
  # @param [Hash{Symbol => Object}] options
112
106
  # See options in {JSON::LD::API#initialize}
113
107
  # @raise [InvalidContext]
@@ -117,7 +111,7 @@ module JSON::LD
117
111
  # @return [Array<Hash>]
118
112
  # The expanded JSON-LD document
119
113
  # @see http://json-ld.org/spec/latest/json-ld-api/#expansion-algorithm
120
- def self.expand(input, context = nil, callback = nil, options = {})
114
+ def self.expand(input, context = nil, options = {})
121
115
  result = nil
122
116
  API.new(input, context, options) do |api|
123
117
  result = api.expand(api.value, nil, api.context)
@@ -129,7 +123,6 @@ module JSON::LD
129
123
 
130
124
  # Finally, if element is a JSON object, it is wrapped into an array.
131
125
  result = [result].compact unless result.is_a?(Array)
132
- callback.call(result) if callback
133
126
  yield result if block_given?
134
127
  result
135
128
  end
@@ -140,16 +133,12 @@ module JSON::LD
140
133
  #
141
134
  # If no context is provided, the input document is compacted using the top-level context of the document
142
135
  #
143
- # The resulting `Hash` is returned via the provided callback.
144
- #
145
- # Note that for Ruby, if the callback is not provided and a block is given, it will be yielded
136
+ # The resulting `Hash` is either returned or yielded, if a block is given.
146
137
  #
147
138
  # @param [String, #read, Hash, Array] input
148
139
  # The JSON-LD object to copy and perform the compaction upon.
149
- # @param [String, #read, Hash, Array, JSON::LD::EvaluationContext] context
140
+ # @param [String, #read, Hash, Array, JSON::LD::Context] context
150
141
  # The base context to use when compacting the input.
151
- # @param [Proc] callback (&block)
152
- # Alternative to using block, with same parameters.
153
142
  # @param [Hash{Symbol => Object}] options
154
143
  # See options in {JSON::LD::API#initialize}
155
144
  # Other options passed to {JSON::LD::API.expand}
@@ -160,48 +149,38 @@ module JSON::LD
160
149
  # The compacted JSON-LD document
161
150
  # @raise [InvalidContext, ProcessingError]
162
151
  # @see http://json-ld.org/spec/latest/json-ld-api/#compaction-algorithm
163
- def self.compact(input, context, callback = nil, options = {})
152
+ def self.compact(input, context, options = {})
164
153
  expanded = result = nil
165
154
 
166
155
  # 1) Perform the Expansion Algorithm on the JSON-LD input.
167
156
  # This removes any existing context to allow the given context to be cleanly applied.
168
- expanded = API.expand(input, nil, nil, options.merge(:debug => nil))
157
+ expanded = API.expand(input, nil, options.merge(:debug => nil))
169
158
 
170
159
  API.new(expanded, context, options) do
171
160
  debug(".compact") {"expanded input: #{expanded.to_json(JSON_STATE)}"}
172
161
  result = compact(value, nil)
173
162
 
174
163
  # xxx) Add the given context to the output
175
- result = case result
176
- when Hash then self.context.serialize.merge(result)
177
- when Array
178
- kwgraph = self.context.compact_iri('@graph', :quiet => true)
179
- self.context.serialize.merge(kwgraph => result)
180
- when String
181
- kwid = self.context.compact_iri('@id', :quiet => true)
182
- self.context.serialize.merge(kwid => result)
164
+ ctx = self.context.serialize
165
+ if result.is_a?(Array)
166
+ kwgraph = self.context.compact_iri('@graph', :vocab => true, :quiet => true)
167
+ result = result.empty? ? {} : {kwgraph => result}
183
168
  end
169
+ result = ctx.merge(result) unless ctx.empty?
184
170
  end
185
- callback.call(result) if callback
186
171
  yield result if block_given?
187
172
  result
188
173
  end
189
174
 
190
175
  ##
191
- # Flattens the given input according to the steps in the Flattening Algorithm. The input must be flattened and returned if there are no errors. If the flattening fails, an appropriate exception must be thrown.
192
- #
193
- # The resulting `Array` is returned via the provided callback.
176
+ # This algorithm flattens an expanded JSON-LD document by collecting all properties of a node in a single JSON object and labeling all blank nodes with blank node identifiers. This resulting uniform shape of the document, may drastically simplify the code required to process JSON-LD data in certain applications.
194
177
  #
195
- # Note that for Ruby, if the callback is not provided and a block is given, it will be yielded. If there is no block, the value will be returned.
178
+ # The resulting `Array` is either returned, or yielded if a block is given.
196
179
  #
197
180
  # @param [String, #read, Hash, Array] input
198
181
  # The JSON-LD object or array of JSON-LD objects to flatten or an IRI referencing the JSON-LD document to flatten.
199
- # @param [String, RDF::URI] graph
200
- # The graph in the document that should be flattened. To return the default graph @default has to be passed, for the merged graph @merged and for any other graph the IRI identifying the graph has to be passed. The default value is @merged.
201
182
  # @param [String, #read, Hash, Array, JSON::LD::EvaluationContext] context
202
183
  # An optional external context to use additionally to the context embedded in input when expanding the input.
203
- # @param [Proc] callback (&block)
204
- # Alternative to using block, with same parameters.
205
184
  # @param [Hash{Symbol => Object}] options
206
185
  # See options in {JSON::LD::API#initialize}
207
186
  # Other options passed to {JSON::LD::API.expand}
@@ -212,38 +191,45 @@ module JSON::LD
212
191
  # The framed JSON-LD document
213
192
  # @raise [InvalidFrame]
214
193
  # @see http://json-ld.org/spec/latest/json-ld-api/#framing-algorithm
215
- def self.flatten(input, graph, context, callback = nil, options = {})
216
- result = nil
217
- graph ||= '@merged'
194
+ def self.flatten(input, context, options = {})
195
+ flattened = []
218
196
 
219
197
  # Expand input to simplify processing
220
- expanded_input = API.expand(input)
198
+ expanded_input = API.expand(input, nil, options)
221
199
 
222
200
  # Initialize input using frame as context
223
201
  API.new(expanded_input, nil, options) do
224
202
  debug(".flatten") {"expanded input: #{value.to_json(JSON_STATE)}"}
225
203
 
226
- # Generate _nodeMap_
204
+ # Initialize node map to a JSON object consisting of a single member whose key is @default and whose value is an empty JSON object.
227
205
  node_map = Hash.ordered
228
- self.generate_node_map(value, node_map, (graph.to_s == '@merged' ? '@merged' : '@default'))
229
-
230
- result = []
206
+ node_map['@default'] = Hash.ordered
207
+ self.generate_node_map(value, node_map)
231
208
 
232
- # If nodeMap has no property graph, return result, otherwise set definitions to its value.
233
- definitions = node_map.fetch(graph.to_s, {})
234
-
235
- # Foreach property and value of definitions
236
- definitions.keys.sort.each do |prop|
237
- value = definitions[prop]
238
- result << value
209
+ default_graph = node_map['@default']
210
+ node_map.keys.kw_sort.reject {|k| k == '@default'}.each do |graph_name|
211
+ graph = node_map[graph_name]
212
+ entry = default_graph[graph_name] ||= {'@id' => graph_name}
213
+ nodes = entry['@graph'] ||= []
214
+ graph.keys.kw_sort.each do |id|
215
+ nodes << graph[id]
216
+ end
217
+ end
218
+ default_graph.keys.kw_sort.each do |id|
219
+ flattened << default_graph[id]
220
+ end
221
+
222
+ if context && !flattened.empty?
223
+ # Otherwise, return the result of compacting flattened according the Compaction algorithm passing context ensuring that the compaction result uses the @graph keyword (or its alias) at the top-level, even if the context is empty or if there is only one element to put in the @graph array. This ensures that the returned document has a deterministic structure.
224
+ compacted = compact(flattened, nil)
225
+ compacted = [compacted] unless compacted.is_a?(Array)
226
+ kwgraph = self.context.compact_iri('@graph', :quiet => true)
227
+ flattened = self.context.serialize.merge(kwgraph => compacted)
239
228
  end
240
-
241
- result
242
229
  end
243
230
 
244
- callback.call(result) if callback
245
- yield result if block_given?
246
- result
231
+ yield flattened if block_given?
232
+ flattened
247
233
  end
248
234
 
249
235
  ##
@@ -251,16 +237,12 @@ module JSON::LD
251
237
  # framed output and is returned if there are no errors. If there are no matches for the frame, null must be returned.
252
238
  # Exceptions must be thrown if there are errors.
253
239
  #
254
- # The resulting `Array` is returned via the provided callback.
255
- #
256
- # Note that for Ruby, if the callback is not provided and a block is given, it will be yielded
240
+ # The resulting `Array` is either returned, or yielded if a block is given.
257
241
  #
258
242
  # @param [String, #read, Hash, Array] input
259
243
  # The JSON-LD object to copy and perform the framing on.
260
244
  # @param [String, #read, Hash, Array] frame
261
245
  # The frame to use when re-arranging the data.
262
- # @param [Proc] callback (&block)
263
- # Alternative to using block, with same parameters.
264
246
  # @param [Hash{Symbol => Object}] options
265
247
  # See options in {JSON::LD::API#initialize}
266
248
  # Other options passed to {JSON::LD::API.expand}
@@ -280,7 +262,7 @@ module JSON::LD
280
262
  # The framed JSON-LD document
281
263
  # @raise [InvalidFrame]
282
264
  # @see http://json-ld.org/spec/latest/json-ld-api/#framing-algorithm
283
- def self.frame(input, frame, callback = nil, options = {})
265
+ def self.frame(input, frame, options = {})
284
266
  result = nil
285
267
  match_limit = 0
286
268
  framing_state = {
@@ -312,19 +294,22 @@ module JSON::LD
312
294
  # Initialize input using frame as context
313
295
  API.new(expanded_input, nil, options) do
314
296
  #debug(".frame") {"context from frame: #{context.inspect}"}
315
- #debug(".frame") {"expanded frame: #{expanded_frame.to_json(JSON_STATE)}"}
316
- #debug(".frame") {"expanded input: #{value.to_json(JSON_STATE)}"}
297
+ debug(".frame") {"raw frame: #{frame.to_json(JSON_STATE)}"}
298
+ debug(".frame") {"expanded frame: #{expanded_frame.to_json(JSON_STATE)}"}
299
+ debug(".frame") {"expanded input: #{value.to_json(JSON_STATE)}"}
317
300
 
318
301
  # Get framing nodes from expanded input, replacing Blank Node identifiers as necessary
319
302
  all_nodes = Hash.ordered
303
+ old_dbg, @options[:debug] = @options[:debug], nil
320
304
  depth do
321
305
  generate_node_map(value, all_nodes, '@merged')
322
306
  end
307
+ @options[:debug] = old_dbg
323
308
  @node_map = all_nodes['@merged']
324
309
  debug(".frame") {"node_map: #{@node_map.to_json(JSON_STATE)}"}
325
310
 
326
311
  result = []
327
- frame(framing_state, @node_map, expanded_frame[0], result, nil)
312
+ frame(framing_state, @node_map, (expanded_frame.first || {}), result, nil)
328
313
  debug(".frame") {"after frame: #{result.to_json(JSON_STATE)}"}
329
314
 
330
315
  # Initalize context from frame
@@ -340,7 +325,6 @@ module JSON::LD
340
325
  result = cleanup_preserve(result)
341
326
  end
342
327
 
343
- callback.call(result) if callback
344
328
  yield result if block_given?
345
329
  result
346
330
  end
@@ -348,14 +332,10 @@ module JSON::LD
348
332
  ##
349
333
  # Processes the input according to the RDF Conversion Algorithm, calling the provided callback for each triple generated.
350
334
  #
351
- # Note that for Ruby, if the callback is not provided and a block is given, it will be yielded
352
- #
353
335
  # @param [String, #read, Hash, Array] input
354
336
  # The JSON-LD object to process when outputting statements.
355
- # @param [String, #read, Hash, Array, JSON::LD::EvaluationContext] context
337
+ # @param [String, #read, Hash, Array, JSON::LD::Context] context
356
338
  # An external context to use additionally to the context embedded in input when expanding the input.
357
- # @param [Proc] callback (&block)
358
- # Alternative to using block, with same parameteres.
359
339
  # @param [{Symbol,String => Object}] options
360
340
  # See options in {JSON::LD::API#initialize}
361
341
  # Options passed to {JSON::LD::API.expand}
@@ -363,21 +343,35 @@ module JSON::LD
363
343
  # @return [Array<RDF::Statement>] if no block given
364
344
  # @yield statement
365
345
  # @yieldparam [RDF::Statement] statement
366
- def self.toRDF(input, context = nil, callback = nil, options = {}, &block)
346
+ def self.toRDF(input, context = nil, options = {}, &block)
367
347
  results = []
368
- API.new(input, context, options) do |api|
348
+ results.extend(RDF::Enumerable)
349
+
350
+ # Expand input to simplify processing
351
+ expanded_input = API.expand(input, context, options)
352
+
353
+ API.new(expanded_input, context, options) do
369
354
  # 1) Perform the Expansion Algorithm on the JSON-LD input.
370
355
  # This removes any existing context to allow the given context to be cleanly applied.
371
- result = api.expand(api.value, nil, api.context)
356
+ debug(".toRDF") {"expanded input: #{expanded_input.to_json(JSON_STATE)}"}
357
+
358
+ # Generate _nodeMap_
359
+ node_map = Hash.ordered
360
+ node_map['@default'] = Hash.ordered
361
+ generate_node_map(expanded_input, node_map)
362
+ debug(".toRDF") {"node map: #{node_map.to_json(JSON_STATE)}"}
372
363
 
373
- api.send(:debug, ".expand") {"expanded input: #{result.to_json(JSON_STATE)}"}
374
364
  # Start generating statements
375
- api.statements("", result, nil, nil, nil) do |statement|
376
- callback ||= block if block_given?
377
- if callback
378
- callback.call(statement)
379
- else
380
- results << statement
365
+ node_map.each do |graph_name, graph|
366
+ context = as_resource(graph_name) unless graph_name == '@default'
367
+ debug(".toRDF") {"context: #{context ? context.to_ntriples : 'null'}"}
368
+ graph_to_rdf(graph).each do |statement|
369
+ statement.context = context if context
370
+ if block_given?
371
+ yield statement
372
+ else
373
+ results << statement
374
+ end
381
375
  end
382
376
  end
383
377
  end
@@ -387,13 +381,9 @@ module JSON::LD
387
381
  ##
388
382
  # Take an ordered list of RDF::Statements and turn them into a JSON-LD document.
389
383
  #
390
- # The resulting `Array` is returned via the provided callback.
391
- #
392
- # Note that for Ruby, if the callback is not provided and a block is given, it will be yielded
384
+ # The resulting `Array` is either returned or yielded, if a block is given.
393
385
  #
394
386
  # @param [Array<RDF::Statement>] input
395
- # @param [Proc] callback (&block)
396
- # Alternative to using block, with same parameteres.
397
387
  # @param [Hash{Symbol => Object}] options
398
388
  # See options in {JSON::LD::API#initialize}
399
389
  # @yield jsonld
@@ -401,7 +391,7 @@ module JSON::LD
401
391
  # The JSON-LD document in expanded form
402
392
  # @return [Array<Hash>]
403
393
  # The JSON-LD document in expanded form
404
- def self.fromRDF(input, callback = nil, options = {}, &block)
394
+ def self.fromRDF(input, options = {}, &block)
405
395
  options = {:useNativeTypes => true}.merge(options)
406
396
  result = nil
407
397
 
@@ -409,8 +399,7 @@ module JSON::LD
409
399
  result = api.from_statements(input)
410
400
  end
411
401
 
412
- callback ||= block if block_given?
413
- callback.call(result) if callback
402
+ yield result if block_given?
414
403
  result
415
404
  end
416
405
  end