json-ld 0.1.5.2 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
data/History.markdown CHANGED
@@ -1,3 +1,10 @@
1
+ === 0.1.6
2
+ * Added flattening API, and updated algorithm.
3
+ * Fixed framing issues using updated flattening.
4
+
5
+ === 0.1.5
6
+ * Added support for @vocab.
7
+
1
8
  === 0.1.4.1
2
9
  * Include rdf-xsd for some specs.
3
10
  * Refactor #expand_value to deal with previous matching on RDF::Literal::Integer for sub-types.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.5.2
1
+ 0.1.6
data/lib/json/ld/api.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'open-uri'
2
2
  require 'json/ld/expand'
3
3
  require 'json/ld/compact'
4
+ require 'json/ld/flatten'
4
5
  require 'json/ld/frame'
5
6
  require 'json/ld/to_rdf'
6
7
  require 'json/ld/from_rdf'
@@ -19,6 +20,7 @@ module JSON::LD
19
20
  include Expand
20
21
  include Compact
21
22
  include Triples
23
+ include Flatten
22
24
  include FromTriples
23
25
  include Frame
24
26
 
@@ -151,6 +153,77 @@ module JSON::LD
151
153
  result
152
154
  end
153
155
 
156
+ ##
157
+ # 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.
158
+ #
159
+ # The resulting `Array` is returned via the provided callback.
160
+ #
161
+ # 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.
162
+ #
163
+ # @param [String, #read, Hash, Array] input
164
+ # The JSON-LD object or array of JSON-LD objects to flatten or an IRI referencing the JSON-LD document to flatten.
165
+ # @param [String, RDF::URI] graph
166
+ # 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.
167
+ # @param [String, #read, Hash, Array] context
168
+ # An optional external context to use additionally to the context embedded in input when expanding the input.
169
+ # @param [Proc] callback (&block)
170
+ # Alternative to using block, with same parameters.
171
+ # @param [Hash{Symbol => Object}] options
172
+ # Other options passed to {#expand}
173
+ # @option options [Boolean] :embed (true)
174
+ # a flag specifying that objects should be directly embedded in the output,
175
+ # instead of being referred to by their IRI.
176
+ # @option options [Boolean] :explicit (false)
177
+ # a flag specifying that for properties to be included in the output,
178
+ # they must be explicitly declared in the framing context.
179
+ # @option options [Boolean] :omitDefault (false)
180
+ # a flag specifying that properties that are missing from the JSON-LD
181
+ # input should be omitted from the output.
182
+ # @yield jsonld
183
+ # @yieldparam [Hash] jsonld
184
+ # The framed JSON-LD document
185
+ # @return [Array<Hash>]
186
+ # The framed JSON-LD document
187
+ # @raise [InvalidFrame]
188
+ # @see http://json-ld.org/spec/latest/json-ld-api/#framing-algorithm
189
+ def self.flatten(input, graph, context, callback = nil, options = {})
190
+ result = nil
191
+ graph ||= '@merged'
192
+
193
+ # Expand input to simplify processing
194
+ expanded_input = API.expand(input)
195
+
196
+ # Initialize input using frame as context
197
+ API.new(expanded_input, nil, options) do
198
+ debug(".flatten") {"expanded input: #{value.to_json(JSON_STATE)}"}
199
+
200
+ # Generate _nodeMap_
201
+ node_map = Hash.ordered
202
+ self.generate_node_map(value,
203
+ node_map,
204
+ (graph.to_s == '@merged' ? '@merged' : '@default'),
205
+ nil,
206
+ BlankNodeNamer.new("t"))
207
+
208
+ result = []
209
+
210
+ # If nodeMap has no property graph, return result, otherwise set definitions to its value.
211
+ definitions = node_map.fetch(graph.to_s, {})
212
+
213
+ # Foreach property and valud of definitions
214
+ definitions.keys.sort.each do |prop|
215
+ value = definitions[prop]
216
+ result << value
217
+ end
218
+
219
+ result
220
+ end
221
+
222
+ callback.call(result) if callback
223
+ yield result if block_given?
224
+ result
225
+ end
226
+
154
227
  ##
155
228
  # Frames the given input using the frame according to the steps in the Framing Algorithm. The input is used to build the
156
229
  # framed output and is returned if there are no errors. If there are no matches for the frame, null must be returned.
@@ -219,13 +292,20 @@ module JSON::LD
219
292
  #debug(".frame") {"expanded frame: #{expanded_frame.to_json(JSON_STATE)}"}
220
293
  #debug(".frame") {"expanded input: #{value.to_json(JSON_STATE)}"}
221
294
 
222
- # Get framing subjects from expanded input, replacing Blank Node identifiers as necessary
223
- @subjects = Hash.ordered
224
- depth {get_framing_subjects(@subjects, value, BlankNodeNamer.new("t"))}
225
- debug(".frame") {"subjects: #{@subjects.to_json(JSON_STATE)}"}
295
+ # Get framing nodes from expanded input, replacing Blank Node identifiers as necessary
296
+ all_nodes = Hash.ordered
297
+ depth do
298
+ generate_node_map(value,
299
+ all_nodes,
300
+ '@merged',
301
+ nil,
302
+ BlankNodeNamer.new("t"))
303
+ end
304
+ @node_map = all_nodes['@merged']
305
+ debug(".frame") {"node_map: #{@node_map.to_json(JSON_STATE)}"}
226
306
 
227
307
  result = []
228
- frame(framing_state, @subjects, expanded_frame[0], result, nil)
308
+ frame(framing_state, @node_map, expanded_frame[0], result, nil)
229
309
  debug(".frame") {"after frame: #{result.to_json(JSON_STATE)}"}
230
310
 
231
311
  # Initalize context from frame
@@ -288,8 +368,7 @@ module JSON::LD
288
368
  # @param [Proc] callback (&block)
289
369
  # Alternative to using block, with same parameteres.
290
370
  # @param [Hash{Symbol => Object}] options
291
- # @option options [Boolean] :useRdfType (false) use rdf:type instead of @type
292
- # @option options [Boolean] :useNativeDatatypes FIXME
371
+ # @option options [Boolean] :notType don't use @type for rdf:type
293
372
  # @yield jsonld
294
373
  # @yieldparam [Hash] jsonld
295
374
  # The JSON-LD document in expanded form
@@ -44,7 +44,7 @@ module JSON::LD
44
44
 
45
45
  case k
46
46
  when '@value', '@id'
47
- # If element has an @value property or element is a subject reference, return the result of performing
47
+ # If element has an @value property or element is a node reference, return the result of performing
48
48
  # Value Compaction on element using active property.
49
49
  v = context.compact_value(property, element, :depth => @depth)
50
50
  debug("compact") {"value optimization, return as #{v.inspect}"}
@@ -13,11 +13,6 @@ module JSON::LD
13
13
  # @attr_reader [RDF::URI]
14
14
  attr_reader :base
15
15
 
16
- # The base IRI of the context, if loaded remotely.
17
- #
18
- # @attr [RDF::URI]
19
- attr :context_base, true
20
-
21
16
  # A list of current, in-scope mappings from term to IRI.
22
17
  #
23
18
  # @attr [Hash{String => String}]
@@ -137,26 +132,18 @@ module JSON::LD
137
132
  raise JSON::LD::InvalidContext::Syntax, "Failed to parse remote context at #{context}: #{e.message}" if @options[:validate]
138
133
  self.dup
139
134
  end
140
- when nil
141
- debug("parse") {"nil"}
142
- # Load context document, if it is a string
143
- ec = EvaluationContext.new(options)
144
- when String
135
+ when String, nil
145
136
  debug("parse") {"remote: #{context}"}
146
137
  # Load context document, if it is a string
147
138
  ec = nil
148
139
  begin
149
- url = expand_iri(context, :base => context_base || base)
150
- ecdup = self.dup
151
- ecdup.context_base = url # Set context_base for recursive remote contexts
152
- RDF::Util::File.open_file(url) {|f| ec = ecdup.parse(f)}
140
+ RDF::Util::File.open_file(context.to_s) {|f| ec = parse(f)}
153
141
  ec.provided_context = context
154
- ec.context_base = url
155
142
  debug("parse") {"=> provided_context: #{context.inspect}"}
156
143
  ec
157
144
  rescue Exception => e
158
- debug("parse") {"Failed to retrieve @context from remote document at #{context.inspect}: #{e.message}"}
159
- raise JSON::LD::InvalidContext::LoadError, "Failed to retrieve remote context at #{context.inspect}: #{e.message}", e.backtrace if @options[:validate]
145
+ debug("parse") {"Failed to retrieve @context from remote document at #{context}: #{e.message}"}
146
+ raise JSON::LD::InvalidContext::LoadError, "Failed to parse remote context at #{context}: #{e.message}", e.backtrace if @options[:validate]
160
147
  self.dup
161
148
  end
162
149
  when Array
@@ -480,8 +467,6 @@ module JSON::LD
480
467
  # @param [Hash{Symbol => Object}] options
481
468
  # @option options [:subject, :predicate, :object, :datatype] position
482
469
  # Useful when determining how to serialize.
483
- # @option options [RDF::URI] base (self.base)
484
- # Base IRI to use when expanding relative IRIs.
485
470
  #
486
471
  # @return [RDF::URI, String] IRI or String, if it's a keyword
487
472
  # @raise [RDF::ReaderError] if the iri cannot be expanded
@@ -491,7 +476,7 @@ module JSON::LD
491
476
  prefix, suffix = iri.split(':', 2)
492
477
  return mapping(iri) if mapping(iri) # If it's an exact match
493
478
  debug("expand_iri") {"prefix: #{prefix.inspect}, suffix: #{suffix.inspect}, vocab: #{vocab.inspect}"} unless options[:quiet]
494
- base = options.fetch(:base, self.base) unless [:predicate, :datatype].include?(options[:position])
479
+ base = [:subject, :object].include?(options[:position]) ? self.base : nil
495
480
  prefix = prefix.to_s
496
481
  case
497
482
  when prefix == '_' && suffix then bnode(suffix)
@@ -669,13 +654,13 @@ module JSON::LD
669
654
  # @param [Hash, String] value
670
655
  # Value (literal or IRI) to be expanded
671
656
  # @param [Hash{Symbol => Object}] options
672
- # @option options [Boolean] :useNativeTypes (true) use native representations
657
+ # @option options [Boolean] :native (true) use native representations
673
658
  #
674
659
  # @return [Hash] Object representation of value
675
660
  # @raise [RDF::ReaderError] if the iri cannot be expanded
676
661
  # @see http://json-ld.org/spec/latest/json-ld-api/#value-expansion
677
662
  def expand_value(property, value, options = {})
678
- options = {:useNativeTypes => true}.merge(options)
663
+ options = {:native => true}.merge(options)
679
664
  depth(options) do
680
665
  debug("expand_value") {"property: #{property.inspect}, value: #{value.inspect}, coerce: #{coerce(property).inspect}"}
681
666
  value = RDF::Literal(value) if RDF::Literal(value).has_datatype?
@@ -696,7 +681,7 @@ module JSON::LD
696
681
  when RDF::XSD.double.to_s
697
682
  {"@value" => value.to_s, "@type" => RDF::XSD.double.to_s}
698
683
  else
699
- if options[:useNativeTypes]
684
+ if options[:native]
700
685
  # Unless there's coercion, to not modify representation
701
686
  {"@value" => (value.is_a?(RDF::Literal::Boolean) ? value.object : value)}
702
687
  else
@@ -710,7 +695,7 @@ module JSON::LD
710
695
  {"@value" => RDF::Literal::Double.new(value, :canonicalize => true).to_s, "@type" => RDF::XSD.double.to_s}
711
696
  when RDF::XSD.integer.to_s, nil
712
697
  # Unless there's coercion, to not modify representation
713
- if options[:useNativeTypes]
698
+ if options[:native]
714
699
  {"@value" => value.is_a?(RDF::Literal::Integer) ? value.object : value}
715
700
  else
716
701
  {"@value" => value.to_s, "@type" => RDF::XSD.integer.to_s}
@@ -729,7 +714,7 @@ module JSON::LD
729
714
  when RDF::XSD.double.to_s
730
715
  {"@value" => RDF::Literal::Double.new(value, :canonicalize => true).to_s, "@type" => RDF::XSD.double.to_s}
731
716
  when nil
732
- if options[:useNativeTypes]
717
+ if options[:native]
733
718
  # Unless there's coercion, to not modify representation
734
719
  {"@value" => value.is_a?(RDF::Literal::Double) ? value.object : value}
735
720
  else
@@ -940,7 +925,7 @@ module JSON::LD
940
925
  else
941
926
  val_lang == language(term) ? 3 : (default_term ? 1 : 0)
942
927
  end
943
- else # subject definition/reference
928
+ else # node definition/reference
944
929
  coerce(term) == '@id' ? 3 : (default_term ? 1 : 0)
945
930
  end
946
931
 
@@ -76,17 +76,17 @@ module JSON::LD
76
76
  when Array
77
77
  depth do
78
78
  [value].flatten.map do |v|
79
- v = v['@id'] if subject_reference?(v)
80
- raise ProcessingError, "Object value must be a string or a subject reference: #{v.inspect}" unless v.is_a?(String)
79
+ v = v['@id'] if node_reference?(v)
80
+ raise ProcessingError, "Object value must be a string or a node reference: #{v.inspect}" unless v.is_a?(String)
81
81
  context.expand_iri(v, options.merge(:position => :property, :quiet => true)).to_s
82
82
  end
83
83
  end
84
84
  when Hash
85
- # Empty object used for @type wildcard or subject reference
86
- if subject_reference?(value)
85
+ # Empty object used for @type wildcard or node reference
86
+ if node_reference?(value)
87
87
  context.expand_iri(value['@id'], options.merge(:position => :property, :quiet => true)).to_s
88
88
  elsif !value.empty?
89
- raise ProcessingError, "Object value of @type must be empty or a subject reference: #{value.inspect}"
89
+ raise ProcessingError, "Object value of @type must be empty or a node reference: #{value.inspect}"
90
90
  else
91
91
  value
92
92
  end
@@ -0,0 +1,109 @@
1
+ module JSON::LD
2
+ module Flatten
3
+ include Utils
4
+
5
+ ##
6
+ # Build hash of nodes used for framing. Also returns flattened representation of input.
7
+ #
8
+ # @param [Array, Hash] element
9
+ # Expanded element
10
+ # @param [Hash{String => Hash}] node_map
11
+ # map of nodes
12
+ # @param [String] graph
13
+ # Graph name for results
14
+ # @param [Array] list
15
+ # List for saving list elements
16
+ # @param [BlankNodeNamer] namer
17
+ # @param [String] id
18
+ # Identifier already associated with element
19
+ def generate_node_map(element, node_map, graph, list, namer, id = nil)
20
+ depth do
21
+ debug("nodeMap") {"element: #{element.inspect}, graph: #{graph}"}
22
+ if element.is_a?(Array)
23
+ element.map {|o| generate_node_map(o, node_map, graph, list, namer)}
24
+ elsif !element.is_a?(Hash) || value?(element)
25
+ list << element if list
26
+ else
27
+ # If the @id property exists and is an IRI, set id to its value, otherwise set it to a blank node identifier created by the Generate Blank Node Identifier algorithm.
28
+ # FIXME: (spec) id passed when it is allocated, so it is not re-allocated here.
29
+ id ||= blank_node?(element) ? namer.get_name(element.fetch('@id', nil)) : element['@id']
30
+
31
+ # If list is not nil, append a new node reference to list using id at the value for @id.
32
+ list << {'@id' => id} if list
33
+
34
+ # Let nodes be the value in nodeMap where the key is graph; if no such value exists, insert a new JSON object for the key graph.
35
+ debug("nodeMap") {"new graph: #{graph}"} unless node_map.has_key?(graph)
36
+ nodes = (node_map[graph] ||= Hash.ordered)
37
+
38
+ # If id is not in nodes, create a new JSON object node with id as the value for @id. Let node be the value of id in nodes.
39
+ debug("nodeMap") {"new node: #{id}"} unless nodes.has_key?(id)
40
+ node = (nodes[id] ||= Hash.ordered)
41
+ node['@id'] ||= id
42
+
43
+ # For each property that is not @id and each value in element ordered by property:
44
+ element.each do |prop, value|
45
+ case prop
46
+ when '@id'
47
+ # Skip @id, already assigned
48
+ when '@graph'
49
+ # If property is @graph, recursively call this algorithm passing value for element, nodeMap, nil for list and if graph is @merged use graph, otherwise use id for graph and then continue.
50
+ graph = graph == '@merged' ? '@merged' : id
51
+ generate_node_map(value, node_map, graph, nil, namer)
52
+ when /^@(?!type)/
53
+ # If property is not @type and is a keyword, merge property and value into node and then continue.
54
+ debug("nodeMap") {"merge keyword#{prop}: #{value.inspect}"}
55
+ node[prop] = value
56
+ else
57
+ raise InvalidFrame::Syntax,
58
+ "unexpected value: #{value.inspect}, expected array" unless
59
+ value.is_a?(Array)
60
+
61
+ # For each value v in the array value:
62
+ value.each do |v|
63
+ if node?(v) || node_reference?(v)
64
+ debug("nodeMap") {"node value #{prop}: #{v.inspect}"}
65
+ # If v is a node definition or node reference:
66
+ # If the property @id is not an IRI or it does not exist, map v to a new blank node identifier to avoid collisions.
67
+ name = blank_node?(v) ?
68
+ namer.get_name(v.fetch('@id', nil)) :
69
+ v['@id']
70
+
71
+ # If one does not already exist, add a node reference for v into node for property.
72
+ node[prop] ||= []
73
+ node[prop] << {'@id' => name} unless node[prop].any? {|n|
74
+ node_reference?(n) && n['@id'] == name
75
+ }
76
+
77
+ # Recursively call this algorithm passing v for value, nodeMap, graph, and nil for list.
78
+ generate_node_map(v, node_map, graph, nil, namer, name)
79
+ elsif list?(v)
80
+ # Otherwise if v has the property @list then recursively call this algorithm with the value of @list as element, nodeMap, graph, and a new array flattenedList as list.
81
+ debug("nodeMap") {"list value #{prop}: #{v.inspect}"}
82
+ flattened_list = []
83
+ generate_node_map(v['@list'],
84
+ node_map,
85
+ graph,
86
+ flattened_list,
87
+ namer)
88
+ # Create a new JSON object with the property @list set to flattenedList and add it to node for property.
89
+ (node[prop] ||= []) << {'@list' => flattened_list}
90
+ elsif prop == '@type'
91
+ # Otherwise, if property is @type and v is not an IRI, generate a new blank node identifier and add it to node for property.
92
+ # FIXME: @type shouldn't really be a BNode, and not clear how we even get here from expansion
93
+ name = blank_node?({'@id' => v}) ? namer.get_name(v) : v
94
+ (node[prop] ||= []) << name
95
+ else
96
+ debug("nodeMap") {"value #{prop}: #{v.inspect}"}
97
+ # Otherwise, add v to node for property.
98
+ (node[prop] ||= []) << v
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ debug("nodeMap") {node_map.to_json(JSON_STATE)}
106
+ end
107
+ end
108
+ end
109
+ end
data/lib/json/ld/frame.rb CHANGED
@@ -7,28 +7,27 @@ module JSON::LD
7
7
  #
8
8
  # @param [Hash{Symbol => Object}] state
9
9
  # Current framing state
10
- # @param [Hash{String => Hash}] subjects
11
- # Map of flattened subjects
10
+ # @param [Hash{String => Hash}] nodes
11
+ # Map of flattened nodes
12
12
  # @param [Hash{String => Object}] frame
13
13
  # @param [Hash{String => Object}] parent
14
- # Parent subject or top-level array
14
+ # Parent node or top-level array
15
15
  # @param [String] property
16
16
  # Property referencing this frame, or null for array.
17
17
  # @raise [JSON::LD::InvalidFrame]
18
- def frame(state, subjects, frame, parent, property)
19
- raise ProcessingError, "why isn't @subjects a hash?: #{@subjects.inspect}" unless @subjects.is_a?(Hash)
18
+ def frame(state, nodes, frame, parent, property)
20
19
  depth do
21
20
  debug("frame") {"state: #{state.inspect}"}
22
- debug("frame") {"subjects: #{subjects.keys.inspect}"}
21
+ debug("frame") {"nodes: #{nodes.keys.inspect}"}
23
22
  debug("frame") {"frame: #{frame.to_json(JSON_STATE)}"}
24
23
  debug("frame") {"parent: #{parent.to_json(JSON_STATE)}"}
25
24
  debug("frame") {"property: #{property.inspect}"}
26
25
  # Validate the frame
27
26
  validate_frame(state, frame)
28
27
 
29
- # Create a set of matched subjects by filtering subjects by checking the map of flattened subjects against frame
28
+ # Create a set of matched nodes by filtering nodes by checking the map of flattened nodes against frame
30
29
  # This gives us a hash of objects indexed by @id
31
- matches = filter_subjects(state, subjects, frame)
30
+ matches = filter_nodes(state, nodes, frame)
32
31
  debug("frame") {"matches: #{matches.keys.inspect}"}
33
32
 
34
33
  # Get values for embedOn and explicitOn
@@ -36,7 +35,7 @@ module JSON::LD
36
35
  explicit = get_frame_flag(state, frame, 'explicit');
37
36
  debug("frame") {"embed: #{embed.inspect}, explicit: #{explicit.inspect}"}
38
37
 
39
- # For each id and subject from the set of matched subjects ordered by id
38
+ # For each id and node from the set of matched nodes ordered by id
40
39
  matches.keys.sort.each do |id|
41
40
  element = matches[id]
42
41
  # If the active property is null, set the map of embeds in state to an empty map
@@ -45,7 +44,7 @@ module JSON::LD
45
44
  output = {'@id' => id}
46
45
 
47
46
  # prepare embed meta info
48
- embedded_subject = {:parent => parent, :property => property}
47
+ embedded_node = {:parent => parent, :property => property}
49
48
 
50
49
  # If embedOn is true, and id is in map of embeds from state
51
50
  if embed && (existing = state[:embeds].fetch(id, nil))
@@ -58,7 +57,7 @@ module JSON::LD
58
57
  # If existing has a parent which is an array containing a JSON object with @id equal to id, element has already been embedded and can be overwritten, so set embedOn to true
59
58
  existing[:parent].detect {|p| p['@id'] == id}
60
59
  else
61
- # Otherwise, existing has a parent which is a subject definition. Set embedOn to true if any of the items in parent property is a subject definition or subject reference for id because the embed can be overwritten
60
+ # Otherwise, existing has a parent which is a node definition. Set embedOn to true if any of the items in parent property is a node definition or node reference for id because the embed can be overwritten
62
61
  existing[:parent].fetch(existing[:property], []).any? do |v|
63
62
  v.is_a?(Hash) && v.fetch('@id', nil) == id
64
63
  end
@@ -74,14 +73,14 @@ module JSON::LD
74
73
  add_frame_output(state, parent, property, output)
75
74
  else
76
75
  # Add embed to map of embeds for id
77
- state[:embeds][id] = embedded_subject
78
- debug("frame") {"add embedded_subject: #{embedded_subject.inspect}"}
76
+ state[:embeds][id] = embedded_node
77
+ debug("frame") {"add embedded_node: #{embedded_node.inspect}"}
79
78
 
80
- # Process each property and value in the matched subject as follows
79
+ # Process each property and value in the matched node as follows
81
80
  element.keys.sort.each do |prop|
82
81
  value = element[prop]
83
82
  if prop[0,1] == '@'
84
- # If property is a keyword, add property and a copy of value to output and continue with the next property from subject
83
+ # If property is a keyword, add property and a copy of value to output and continue with the next property from node
85
84
  output[prop] = value.dup
86
85
  next
87
86
  end
@@ -89,7 +88,7 @@ module JSON::LD
89
88
  # If property is not in frame:
90
89
  unless frame.has_key?(prop)
91
90
  debug("frame") {"non-framed property #{prop}"}
92
- # If explicitOn is false, Embed values from subject in output using subject as element and property as active property
91
+ # If explicitOn is false, Embed values from node in output using node as element and property as active property
93
92
  embed_values(state, element, prop, output) unless explicit
94
93
 
95
94
  # Continue to next property
@@ -110,29 +109,29 @@ module JSON::LD
110
109
 
111
110
  # Process each listitem in the @list array as follows
112
111
  item['@list'].each do |listitem|
113
- if subject_reference?(listitem)
112
+ if node_reference?(listitem)
114
113
  itemid = listitem['@id']
115
114
  debug("frame") {"list item of #{prop} recurse for #{itemid.inspect}"}
116
115
 
117
- # If listitem is a subject reference process listitem recursively using this algorithm passing a new map of subjects that contains the @id of listitem as the key and the subject reference as the value. Pass the first value from frame for property as frame, list as parent, and @list as active property.
118
- frame(state, {itemid => @subjects[itemid]}, frame[prop].first, list, '@list')
116
+ # If listitem is a node reference process listitem recursively using this algorithm passing a new map of nodes that contains the @id of listitem as the key and the node reference as the value. Pass the first value from frame for property as frame, list as parent, and @list as active property.
117
+ frame(state, {itemid => @node_map[itemid]}, frame[prop].first, list, '@list')
119
118
  else
120
119
  # Otherwise, append a copy of listitem to @list in list.
121
- debug("frame") {"list item of #{prop} non-subject ref #{listitem.inspect}"}
120
+ debug("frame") {"list item of #{prop} non-node ref #{listitem.inspect}"}
122
121
  add_frame_output(state, list, '@list', listitem)
123
122
  end
124
123
  end
125
- elsif subject_reference?(item)
126
- # If item is a subject reference process item recursively
124
+ elsif node_reference?(item)
125
+ # If item is a node reference process item recursively
127
126
  # Recurse into sub-objects
128
127
  itemid = item['@id']
129
128
  debug("frame") {"value property #{prop} recurse for #{itemid.inspect}"}
130
129
 
131
- # passing a new map as subjects that contains the @id of item as the key and the subject reference as the value. Pass the first value from frame for property as frame, output as parent, and property as active property
132
- frame(state, {itemid => @subjects[itemid]}, frame[prop].first, output, prop)
130
+ # passing a new map as nodes that contains the @id of item as the key and the node reference as the value. Pass the first value from frame for property as frame, output as parent, and property as active property
131
+ frame(state, {itemid => @node_map[itemid]}, frame[prop].first, output, prop)
133
132
  else
134
133
  # Otherwise, append a copy of item to active property in output.
135
- debug("frame") {"value property #{prop} non-subject ref #{item.inspect}"}
134
+ debug("frame") {"value property #{prop} non-node ref #{item.inspect}"}
136
135
  add_frame_output(state, output, prop, item)
137
136
  end
138
137
  end
@@ -164,89 +163,6 @@ module JSON::LD
164
163
  end
165
164
  end
166
165
 
167
- ##
168
- # Build hash of subjects used for framing. Also returns flattened representation
169
- # of input.
170
- #
171
- # @param [Hash{String => Hash}] subjects
172
- # destination for mapped subjects and their Object representations
173
- # @param [Array, Hash] input
174
- # JSON-LD in expanded form
175
- # @param [BlankNodeNamer] namer
176
- # @return
177
- # input with subject definitions changed to references
178
- def get_framing_subjects(subjects, input, namer)
179
- depth do
180
- debug("framing subjects") {"input: #{input.inspect}"}
181
- case input
182
- when Array
183
- input.map {|o| get_framing_subjects(subjects, o, namer)}
184
- when Hash
185
- case
186
- when subject?(input) || subject_reference?(input)
187
- # Get name for subject, mapping old blank node identifiers to new
188
- name = blank_node?(input) ? namer.get_name(input.fetch('@id', nil)) : input['@id']
189
- debug("framing subjects") {"new subject: #{name.inspect}"} unless subjects.has_key?(name)
190
- subject = subjects[name] ||= {'@id' => name}
191
-
192
- # In property order
193
- input.keys.sort.each do |prop|
194
- value = input[prop]
195
- case prop
196
- when '@id'
197
- # Skip @id, already assigned
198
- when /^@/
199
- # Copy other keywords
200
- subject[prop] = value
201
- else
202
- case value
203
- when Hash
204
- # Special case @list, which is not in expanded form
205
- raise InvalidFrame::Syntax, "Unexpected hash value: #{value.inspect}" unless value.has_key?('@list')
206
-
207
- # Map entries replacing subjects with subject references
208
- subject[prop] = {"@list" =>
209
- value['@list'].map {|o| get_framing_subjects(subjects, o, namer)}
210
- }
211
- when Array
212
- # Map array entries
213
- subject[prop] = get_framing_subjects(subjects, value, namer)
214
- else
215
- raise InvalidFrame::Syntax, "unexpected value: #{value.inspect}"
216
- end
217
- end
218
- end
219
-
220
- # Return as subject reference
221
- {"@id" => name}
222
- else
223
- # At this point, it's not a subject or a reference, just return input
224
- input
225
- end
226
- else
227
- # Returns equivalent representation
228
- input
229
- end
230
- end
231
- end
232
-
233
- ##
234
- # Flatten input, used in framing.
235
- #
236
- # This algorithm works by transforming input to statements, and then back to JSON-LD
237
- #
238
- # @return [Array{Hash}]
239
- def flatten
240
- debug("flatten")
241
- expanded = depth {self.expand(self.value, nil, context)}
242
- statements = []
243
- depth {self.statements("", expanded, nil, nil, nil ) {|s| statements << s}}
244
- debug("flatten") {"statements: #{statements.map(&:to_nquads).join("\n")}"}
245
-
246
- # Transform back to JSON-LD, not flattened
247
- depth {self.from_statements(statements)}
248
- end
249
-
250
166
  ##
251
167
  # Replace @preserve keys with the values, also replace @null with null
252
168
  #
@@ -289,50 +205,51 @@ module JSON::LD
289
205
  private
290
206
 
291
207
  ##
292
- # Returns a map of all of the subjects that match a parsed frame.
208
+ # Returns a map of all of the nodes that match a parsed frame.
293
209
  #
294
210
  # @param state the current framing state.
295
- # @param subjects the set of subjects to filter.
211
+ # @param nodes the set of nodes to filter.
296
212
  # @param frame the parsed frame.
297
213
  #
298
- # @return all of the matched subjects.
299
- def filter_subjects(state, subjects, frame)
300
- subjects.dup.keep_if {|id, element| filter_subject(state, element, frame)}
214
+ # @return all of the matched nodes.
215
+ def filter_nodes(state, nodes, frame)
216
+ nodes.dup.keep_if {|id, element| element && filter_node(state, element, frame)}
301
217
  end
302
218
 
303
219
  ##
304
- # Returns true if the given subject matches the given frame.
220
+ # Returns true if the given node matches the given frame.
305
221
  #
306
- # Matches either based on explicit type inclusion where the subject
222
+ # Matches either based on explicit type inclusion where the node
307
223
  # has any type listed in the frame. If the frame has empty types defined
308
- # matches subjects not having a @type. If the frame has a type of {} defined
309
- # matches subjects having any type defined.
224
+ # matches nodes not having a @type. If the frame has a type of {} defined
225
+ # matches nodes having any type defined.
310
226
  #
311
- # Otherwise, does duck typing, where the subject must have all of the properties
227
+ # Otherwise, does duck typing, where the node must have all of the properties
312
228
  # defined in the frame.
313
229
  #
314
230
  # @param [Hash{Symbol => Object}] state the current frame state.
315
- # @param [Hash{String => Object}] subject the subject to check.
231
+ # @param [Hash{String => Object}] node the node to check.
316
232
  # @param [Hash{String => Object}] frame the frame to check.
317
233
  #
318
- # @return true if the subject matches, false if not.
319
- def filter_subject(state, subject, frame)
234
+ # @return true if the node matches, false if not.
235
+ def filter_node(state, node, frame)
236
+ debug("frame") {"filter node: #{node.inspect}"}
320
237
  if types = frame.fetch('@type', nil)
321
- subject_types = subject.fetch('@type', [])
238
+ node_types = node.fetch('@type', [])
322
239
  raise InvalidFrame::Syntax, "frame @type must be an array: #{types.inspect}" unless types.is_a?(Array)
323
- raise InvalidFrame::Syntax, "subject @type must be an array: #{subject_types.inspect}" unless subject_types.is_a?(Array)
324
- # If frame has an @type, use it for selecting appropriate subjects.
325
- debug("frame") {"filter subject: #{subject_types.inspect} has any of #{types.inspect}"}
240
+ raise InvalidFrame::Syntax, "node @type must be an array: #{node_types.inspect}" unless node_types.is_a?(Array)
241
+ # If frame has an @type, use it for selecting appropriate nodes.
242
+ debug("frame") {"filter node: #{node_types.inspect} has any of #{types.inspect}"}
326
243
 
327
244
  # Check for type wild-card, or intersection
328
- types == [{}] ? !subject_types.empty? : subject_types.any? {|t| types.include?(t)}
245
+ types == [{}] ? !node_types.empty? : node_types.any? {|t| types.include?(t)}
329
246
  else
330
- # Duck typing, for subjects not having a type, but having @id
247
+ # Duck typing, for nodes not having a type, but having @id
331
248
 
332
249
  # Subject matches if it has all the properties in the frame
333
250
  frame_keys = frame.keys.reject {|k| k[0,1] == '@'}
334
- subject_keys = subject.keys.reject {|k| k[0,1] == '@'}
335
- (frame_keys & subject_keys) == frame_keys
251
+ node_keys = node.keys.reject {|k| k[0,1] == '@'}
252
+ (frame_keys & node_keys) == frame_keys
336
253
  end
337
254
  end
338
255
 
@@ -361,13 +278,13 @@ module JSON::LD
361
278
  property = embed[:property];
362
279
 
363
280
  # create reference to replace embed
364
- subject = {}
365
- subject['@id'] = id
281
+ node = {}
282
+ node['@id'] = id
366
283
  ref = {'@id' => id}
367
284
 
368
285
  # remove existing embed
369
- if subject?(parent)
370
- # replace subject with reference
286
+ if node?(parent)
287
+ # replace node with reference
371
288
  parent[property].map! do |v|
372
289
  v.is_a?(Hash) && v.fetch('@id', nil) == id ? ref : v
373
290
  end
@@ -418,7 +335,7 @@ module JSON::LD
418
335
  def embed_values(state, element, property, output)
419
336
  element[property].each do |o|
420
337
  # Get element @id, if this is an object
421
- sid = o['@id'] if subject_reference?(o)
338
+ sid = o['@id'] if node_reference?(o)
422
339
  if sid
423
340
  unless state[:embeds].has_key?(sid)
424
341
  debug("frame") {"embed element #{sid.inspect}"}
@@ -427,7 +344,7 @@ module JSON::LD
427
344
  state[:embeds][sid] = embed
428
345
 
429
346
  # Recurse into element
430
- s = @subjects.fetch(sid, {'@id' => sid})
347
+ s = @node_map.fetch(sid, {'@id' => sid})
431
348
  o = {}
432
349
  s.each do |prop, value|
433
350
  if prop[0,1] == '@'