json-ld 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,166 @@
1
+ require 'rdf/nquads'
2
+
3
+ module JSON::LD
4
+ module FromTriples
5
+ include Utils
6
+
7
+ ##
8
+ # Generate a JSON-LD array representation from an array of `RDF::Statement`.
9
+ # Representation is in expanded form
10
+ #
11
+ # @param [Array<RDF::Statement>] input
12
+ # @param [BlankNodeNamer] namer
13
+ # @return [Array<Hash>] the JSON-LD document in normalized form
14
+ def from_statements(input, namer)
15
+ array = []
16
+ listMap = {}
17
+ restMap = {}
18
+ subjectMap = {}
19
+ bnode_map = {}
20
+
21
+ value = nil
22
+ ec = EvaluationContext.new
23
+
24
+ # Create a map for subject to object representation
25
+
26
+ # For each triple in input
27
+ input.each do |statement|
28
+ debug("statement") { statement.to_nquads.chomp}
29
+
30
+ subject = ec.expand_iri(statement.subject).to_s
31
+ name = ec.expand_iri(statement.context).to_s if statement.context
32
+ subject = namer.get_name(subject) if subject[0,2] == "_:"
33
+ name = namer.get_name(name) if name.to_s[0,2] == "_:"
34
+
35
+ case statement.predicate
36
+ when RDF.first
37
+ # If property is rdf:first,
38
+ # create a new entry in _listMap_ for _name_ and _subject_ and an array value
39
+ # containing the object representation and continue to the next statement.
40
+ object_rep = ec.expand_value(nil, statement.object)
41
+ object_rep['@id'] = namer.get_name(object_rep['@id']) if blank_node?(object_rep)
42
+ debug("rdf:first") { "save object #{[object_rep].inspect}"}
43
+ listMap[name] ||= {}
44
+ listMap[name][subject] = [object_rep]
45
+ next
46
+ when RDF.rest
47
+ # If property is rdf:rest,
48
+ # and object is a blank node,
49
+ # create a new entry in _restMap_ for _name_ and _subject_ and a value being the
50
+ # result of IRI expansion on the object and continue to the next statement.
51
+ next unless statement.object.is_a?(RDF::Node)
52
+ object_rep = ec.expand_iri(statement.object).to_s
53
+ object_rep = namer.get_name(object_rep) if object_rep[0,2] == '_:'
54
+ debug("rdf:rest") { "save object #{object_rep.inspect}"}
55
+ restMap[name] ||= {}
56
+ restMap[name][subject] = object_rep
57
+ next
58
+ end
59
+
60
+ # If name is not null
61
+ if name
62
+ # If _subjectMap_ does not have an entry for null as name and _name_ as subject
63
+ subjectMap[nil] ||= {}
64
+ value = subjectMap[nil][name]
65
+ unless value
66
+ # Create a new JSON Object with key/value pair of @id and a string representation
67
+ # of name and append to array.
68
+ debug("@id") { "new subject: #{name} for graph"}
69
+ value = Hash.ordered
70
+ value['@id'] = name
71
+ array << (subjectMap[nil][name] = value)
72
+ else
73
+ # Otherwise, use that entry as value
74
+ end
75
+
76
+ # If value does not have an entry for @graph, initialize it as a new array
77
+ a = value['@graph'] ||= []
78
+
79
+ # If subjectMap does not have an entry for name and subject
80
+ subjectMap[name] ||= {}
81
+ value = subjectMap[name][subject]
82
+ unless value
83
+ # Create a new JSON Object with key/value pair of @id and a string representation
84
+ # of name and append to the the graph array for name and use as value.
85
+ debug("@id") { "new subject: #{subject} for graph: #{name}"}
86
+ value = Hash.ordered
87
+ value['@id'] = subject
88
+ a << (subjectMap[name][subject] = value)
89
+ else
90
+ # Otherwise, use that entry as value
91
+ end
92
+ else
93
+ # Otherwise, if subjectMap does not have an entry for _name_ and _subject_
94
+ subjectMap[name] ||= {}
95
+ value = subjectMap[nil][subject]
96
+ unless value
97
+ # Create a new JSON Object with key/value pair of @id and a string representation
98
+ # of subject and append to array.
99
+ debug("@id") { "new subject: #{subject}"}
100
+ value = Hash.ordered
101
+ value['@id'] = subject
102
+ array << (subjectMap[nil][subject] = value)
103
+ else
104
+ # Otherwise, use that entry as value
105
+ end
106
+ end
107
+
108
+ # If property is http://www.w3.org/1999/02/22-rdf-syntax-ns#type:
109
+ if statement.predicate == RDF.type
110
+ object = ec.expand_iri(statement.object).to_s
111
+ debug("@type") { object.inspect}
112
+ # append the string representation of object to the array value for the key @type, creating
113
+ # an entry if necessary
114
+ (value['@type'] ||= []) << object
115
+ elsif statement.object == RDF.nil
116
+ # Otherwise, if object is http://www.w3.org/1999/02/22-rdf-syntax-ns#nil, let
117
+ # key be the string representation of predicate. Set the value
118
+ # for key to an empty @list representation {"@list": []}
119
+ key = ec.expand_iri(statement.predicate).to_s
120
+ value[key] = {"@list" => []}
121
+ else
122
+ # Otherwise, let key be the string representation of predicate and let object representation
123
+ # be object represented in expanded form as described in Value Expansion.
124
+ key = ec.expand_iri(statement.predicate).to_s
125
+ object = ec.expand_value(key, statement.object)
126
+ debug("object") {"detected that #{object.inspect} is a blank node"}
127
+ object['@id'] = object_iri = namer.get_name(object['@id']) if blank_node?(object)
128
+
129
+ debug("key/value") { "key: #{key}, :value #{object.inspect}"}
130
+
131
+ # Non-normative, save a reference for the bnode to allow for easier list expansion
132
+ bnode_map[object_iri] = {:obj => value, :key => key} if statement.object.is_a?(RDF::Node)
133
+
134
+ # append the object object representation to the array value for key, creating
135
+ # an entry if necessary
136
+ (value[key] ||= []) << object
137
+ end
138
+ end
139
+
140
+ # For each key/value _prev_, _rest_ entry in _restMap_, append to the _listMap_ value identified
141
+ # by _prev_ the _listMap_ value identified by _rest_
142
+ debug("restMap") {restMap.inspect}
143
+ restMap.each do |gname, map|
144
+ map.each do |prev, rest|
145
+ debug("@list") { "Fold #{rest} into #{prev}"}
146
+ listMap[gname][prev] += listMap[gname][rest]
147
+ end
148
+ end
149
+
150
+ # For each key/value _node_, _list_, in _listMap_ where _list_ exists as a value of an object in _array_,
151
+ # replace the object value with _list_
152
+ debug("listMap") {listMap.inspect}
153
+ listMap.each do |gname, map|
154
+ map.each do |node, list|
155
+ next unless bnode_map.has_key?(node)
156
+ debug("@list") { "Replace #{bnode_map[node][:obj][bnode_map[node][:key]]} with #{listMap[node]}"}
157
+ bnode_map[node][:obj][bnode_map[node][:key]] = {"@list" => listMap[gname][node]}
158
+ end
159
+ end
160
+
161
+ # Return array as the graph representation.
162
+ debug("fromRdf") {array.to_json(JSON_STATE)}
163
+ array
164
+ end
165
+ end
166
+ end
@@ -10,10 +10,10 @@ module JSON::LD
10
10
  format Format
11
11
 
12
12
  ##
13
- # The graph constructed when parsing.
14
- #
15
- # @return [RDF::Graph]
16
- attr_reader :graph
13
+ # Override normal symbol generation
14
+ def self.to_sym
15
+ :jsonld
16
+ end
17
17
 
18
18
  ##
19
19
  # Initializes the RDF/JSON reader instance.
@@ -26,6 +26,8 @@ module JSON::LD
26
26
  # @yieldreturn [void] ignored
27
27
  # @raise [RDF::ReaderError] if the JSON document cannot be loaded
28
28
  def initialize(input = $stdin, options = {}, &block)
29
+ options[:base_uri] ||= options[:base] if options.has_key?(:base)
30
+ options[:base] ||= options[:base_uri] if options.has_key?(:base_uri)
29
31
  super do
30
32
  begin
31
33
  @doc = JSON.load(input)
@@ -47,12 +49,7 @@ module JSON::LD
47
49
  # @private
48
50
  # @see RDF::Reader#each_statement
49
51
  def each_statement(&block)
50
- @callback = block
51
-
52
- # initialize the evaluation context with initial context
53
- ec = EvaluationContext.new(@options)
54
-
55
- traverse("", @doc, nil, nil, ec)
52
+ JSON::LD::API.toRDF(@doc, @options[:context], nil, @options, &block)
56
53
  end
57
54
 
58
55
  ##
@@ -63,227 +60,6 @@ module JSON::LD
63
60
  block.call(*statement.to_triple)
64
61
  end
65
62
  end
66
-
67
- private
68
- ##
69
- #
70
- # @param [String] path
71
- # location within JSON hash
72
- # @param [Hash, Array, String] element
73
- # The current JSON element being processed
74
- # @param [RDF::URI] subject
75
- # Inherited subject
76
- # @param [RDF::URI] property
77
- # Inherited property
78
- # @param [EvaluationContext] ec
79
- # The active context
80
- # @return [RDF::Resource] defined by this element
81
- # @yield :resource
82
- # @yieldparam [RDF::Resource] :resource
83
- def traverse(path, element, subject, property, ec)
84
- debug(path) {"traverse: e=#{element.class.inspect}, s=#{subject.inspect}, p=#{property.inspect}, e=#{ec.inspect}"}
85
-
86
- traverse_result = case element
87
- when Hash
88
- # 2.1) If a @context keyword is found, the processor merges each key-value pair in
89
- # the local context into the active context ...
90
- if element['@context']
91
- # Merge context
92
- ec = ec.parse(element['@context'])
93
- prefixes.merge!(ec.mappings) # Update parsed prefixes
94
- end
95
-
96
- # 2.2) Create a copy of the current JSON object, changing keys that map to JSON-LD keywords with those keywords.
97
- # Use the new JSON object in subsequent steps
98
- new_element = {}
99
- element.each do |k, v|
100
- k = ec.mapping(k) if ec.mapping(k).to_s[0,1] == '@'
101
- new_element[k] = v
102
- end
103
- unless element == new_element
104
- debug(path) {"traverse: keys after map: #{new_element.keys.inspect}"}
105
- element = new_element
106
- end
107
-
108
- # Other shortcuts to allow use of this method for terminal associative arrays
109
- object = if element['@literal']
110
- # 2.3) If the JSON object has a @literal key, set the active object to a literal value as follows ...
111
- literal_opts = {}
112
- literal_opts[:datatype] = ec.expand_iri(element['@type'], :position => :datatype) if element['@type']
113
- literal_opts[:language] = element['@language'].to_sym if element['@language']
114
- RDF::Literal.new(element['@literal'], literal_opts)
115
- elsif element['@list']
116
- # 2.4 (Lists)
117
- parse_list("#{path}[#{'@list'}]", element['@list'], property, ec) do |resource|
118
- add_triple(path, subject, property, resource) if subject && property
119
- end
120
- end
121
-
122
- if object
123
- yield object if block_given?
124
- return object
125
- end
126
-
127
- active_subject = if element['@id'].is_a?(String)
128
- # 2.5 Subject
129
- # 2.5.1 Set active object (subject)
130
- ec.expand_iri(element['@id'], :position => :subject)
131
- elsif element['@id']
132
- # 2.5.2 Recursively process hash or Array values
133
- traverse("#{path}[#{'@id'}]", element['@id'], subject, property, ec) do |resource|
134
- add_triple(path, subject, property, resource) if subject && property
135
- end
136
- else
137
- # 2.6) Generate a blank node identifier and set it as the active subject.
138
- RDF::Node.new
139
- end
140
-
141
- subject = active_subject
142
-
143
- # 2.7) For each key in the JSON object that has not already been processed, perform the following steps:
144
- element.each do |key, value|
145
- # 2.7.1) If a key that is not @context, @id, or @type, set the active property by
146
- # performing Property Processing on the key.
147
- property = case key
148
- when '@type' then RDF.type
149
- when /^@/ then next
150
- else ec.expand_iri(key, :position => :predicate)
151
- end
152
-
153
- # 2.7.3) List expansion
154
- object = if ec.list(property) && value.is_a?(Array)
155
- # If the active property is the target of a @list coercion, and the value is an array,
156
- # process the value as a list starting at Step 3.1.
157
- parse_list("#{path}[#{key}]", value, property, ec) do |resource|
158
- # Adds triple for head BNode only, the rest of the list is done within the method
159
- add_triple(path, subject, property, resource) if subject && property
160
- end
161
- else
162
- traverse("#{path}[#{key}]", value, subject, property, ec) do |resource|
163
- # Adds triples for each value
164
- add_triple(path, subject, property, resource) if subject && property
165
- end
166
- end
167
- end
168
-
169
- # 2.8) The subject is returned
170
- subject
171
- when Array
172
- # 3) If a regular array is detected ...
173
- element.each_with_index do |v, i|
174
- traverse("#{path}[#{i}]", v, subject, property, ec) do |resource|
175
- add_triple(path, subject, property, resource) if subject && property
176
- end
177
- end
178
- nil # No real value returned from an array
179
- when String
180
- # 4) Perform coersion of the value, or generate a literal
181
- debug(path) do
182
- "traverse(#{element}): coerce(#{property.inspect}) == #{ec.coerce(property).inspect}, " +
183
- "ec=#{ec.coercions.inspect}"
184
- end
185
- if ec.coerce(property) == '@id'
186
- # 4.1) If the active property is the target of a @id coercion ...
187
- ec.expand_iri(element, :position => :object)
188
- elsif ec.coerce(property)
189
- # 4.2) Otherwise, if the active property is the target of coercion ..
190
- RDF::Literal.new(element, :datatype => ec.coerce(property))
191
- else
192
- # 4.3) Otherwise, set the active object to a plain literal value created from the string.
193
- RDF::Literal.new(element, :language => ec.language)
194
- end
195
- when Float
196
- object = RDF::Literal::Double.new(element)
197
- debug(path) {"traverse(#{element}): native: #{object.inspect}"}
198
- object
199
- when Fixnum
200
- object = RDF::Literal.new(element)
201
- debug(path) {"traverse(#{element}): native: #{object.inspect}"}
202
- object
203
- when TrueClass, FalseClass
204
- object = RDF::Literal::Boolean.new(element)
205
- debug(path) {"traverse(#{element}): native: #{object.inspect}"}
206
- object
207
- else
208
- raise RDF::ReaderError, "Traverse to unknown element: #{element.inspect} of type #{element.class}"
209
- end
210
-
211
- # Yield and return traverse_result
212
- yield traverse_result if traverse_result && block_given?
213
- traverse_result
214
- end
215
-
216
- ##
217
- # Parse a List
218
- #
219
- # @param [String] path
220
- # location within JSON hash
221
- # @param [Array] list
222
- # The Array to serialize as a list
223
- # @param [RDF::URI] property
224
- # Inherited property
225
- # @param [EvaluationContext] ec
226
- # The active context
227
- # @return [RDF::Resource] BNode or nil for head of list
228
- # @yield :resource
229
- # BNode or nil for head of list
230
- # @yieldparam [RDF::Resource] :resource
231
- def parse_list(path, list, property, ec)
232
- debug(path) {"list: #{list.inspect}, p=#{property.inspect}, e=#{ec.inspect}"}
233
-
234
- last = list.pop
235
- result = first_bnode = last ? RDF::Node.new : RDF.nil
236
-
237
- list.each do |list_item|
238
- # Traverse the value, using _property_, not rdf:first, to ensure that
239
- # proper type coercion is performed
240
- traverse("#{path}", list_item, first_bnode, property, ec) do |resource|
241
- add_triple("#{path}", first_bnode, RDF.first, resource)
242
- end
243
- rest_bnode = RDF::Node.new
244
- add_triple("#{path}", first_bnode, RDF.rest, rest_bnode)
245
- first_bnode = rest_bnode
246
- end
247
- if last
248
- traverse("#{path}", last, first_bnode, property, ec) do |resource|
249
- add_triple("#{path}", first_bnode, RDF.first, resource)
250
- end
251
- add_triple("#{path}", first_bnode, RDF.rest, RDF.nil)
252
- end
253
-
254
- yield result if block_given?
255
- result
256
- end
257
-
258
- ##
259
- # add a statement, object can be literal or URI or bnode
260
- #
261
- # @param [String] path
262
- # @param [URI, BNode] subject the subject of the statement
263
- # @param [URI] predicate the predicate of the statement
264
- # @param [URI, BNode, Literal] object the object of the statement
265
- # @return [Statement] Added statement
266
- # @raise [ReaderError] Checks parameter types and raises if they are incorrect if parsing mode is _validate_.
267
- def add_triple(path, subject, predicate, object)
268
- predicate = RDF.type if predicate == '@type'
269
- statement = RDF::Statement.new(subject, predicate, object)
270
- debug(path) {"statement: #{statement.to_ntriples}"}
271
- @callback.call(statement)
272
- end
273
-
274
- ##
275
- # Add debug event to debug array, if specified
276
- #
277
- # @param [XML Node, any] node:: XML Node or string for showing context
278
- # @param [String] message
279
- # @yieldreturn [String] appended to message, to allow for lazy-evaulation of message
280
- def debug(*args)
281
- return unless ::JSON::LD.debug? || @options[:debug]
282
- message = " " * (@depth || 0) * 2 + (args.empty? ? "" : args.join(": "))
283
- message += yield if block_given?
284
- puts message if JSON::LD::debug?
285
- @options[:debug] << message if @options[:debug].is_a?(Array)
286
- end
287
63
  end
288
64
  end
289
65
 
@@ -0,0 +1,181 @@
1
+ require 'rdf/nquads'
2
+
3
+ module JSON::LD
4
+ module Triples
5
+ include Utils
6
+
7
+ ##
8
+ #
9
+ # @param [String] path
10
+ # location within JSON hash
11
+ # @param [Hash, Array, String] element
12
+ # The current JSON element being processed
13
+ # @param [RDF::Node] subject
14
+ # Inherited subject
15
+ # @param [RDF::URI] property
16
+ # Inherited property
17
+ # @param [RDF::Node] name
18
+ # Inherited inherited graph name
19
+ # @return [RDF::Resource] defined by this element
20
+ # @yield :statement
21
+ # @yieldparam [RDF::Statement] :statement
22
+ def statements(path, element, subject, property, name, &block)
23
+ debug(path) {"statements: e=#{element.inspect}, s=#{subject.inspect}, p=#{property.inspect}, n=#{name.inspect}"}
24
+ @node_seq = "jld_t0000" unless subject || property
25
+
26
+ traverse_result = depth do
27
+ case element
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
40
+
41
+ if object
42
+ # 1.4
43
+ add_quad(path, subject, property, object, name, &block) if subject && property
44
+ return object
45
+ end
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'], :quite => true)
51
+ else
52
+ # 1.6) Generate a blank node identifier and set it as the active subject.
53
+ node
54
+ end
55
+
56
+ # 1.7) For each key in the JSON object that has not already been processed,
57
+ # perform the following steps:
58
+ element.each do |key, value|
59
+ active_property = case key
60
+ when '@type'
61
+ # If the key is @type, set the active property to rdf:type.
62
+ RDF.type
63
+ when '@graph'
64
+ # Otherwise, if property is @graph, process value algorithm recursively, using active subject
65
+ # as graph name and null values for active subject and active property and then continue to
66
+ # next property
67
+ statements("#{path}[#{key}]", value, nil, nil, active_subject, &block)
68
+ next
69
+ when /^@/
70
+ # Otherwise, if property is a keyword, skip this step.
71
+ next
72
+ else
73
+ # 1.7.1) If a key that is not @id, @graph, or @type, set the active property by
74
+ # performing Property Processing on the key.
75
+ context.expand_iri(key, :quite => true)
76
+ end
77
+
78
+ debug("statements[Step 1.7.4]")
79
+ statements("#{path}[#{key}]", value, active_subject, active_property, name, &block)
80
+ end
81
+
82
+ # 1.8) The active_subject is returned
83
+ active_subject
84
+ when Array
85
+ # 2) If a regular array is detected ...
86
+ debug("statements[Step 2]")
87
+ element.each_with_index do |v, i|
88
+ statements("#{path}[#{i}]", v, subject, property, name, &block)
89
+ end
90
+ nil # No real value returned from an array
91
+ when String
92
+ object = RDF::Literal.new(element)
93
+ debug(path) {"statements[Step 3]: plain: #{object.inspect}"}
94
+ object
95
+ when Float
96
+ object = RDF::Literal::Double.new(element)
97
+ debug(path) {"statements[Step 4]: native: #{object.inspect}"}
98
+ object
99
+ when Fixnum
100
+ object = RDF::Literal.new(element)
101
+ debug(path) {"statements[Step 5]: native: #{object.inspect}"}
102
+ object
103
+ when TrueClass, FalseClass
104
+ object = RDF::Literal::Boolean.new(element)
105
+ debug(path) {"statements[Step 6]: native: #{object.inspect}"}
106
+ object
107
+ else
108
+ raise RDF::ReaderError, "Traverse to unknown element: #{element.inspect} of type #{element.class}"
109
+ end
110
+ end
111
+
112
+ # Yield and return traverse_result
113
+ add_quad(path, subject, property, traverse_result, name, &block) if subject && property && traverse_result
114
+ traverse_result
115
+ end
116
+
117
+ ##
118
+ # Parse a List
119
+ #
120
+ # @param [String] path
121
+ # location within JSON hash
122
+ # @param [Array] list
123
+ # The Array to serialize as a list
124
+ # @param [RDF::URI] property
125
+ # Inherited property
126
+ # @param [RDF::Resource] name
127
+ # Inherited named graph context
128
+ # @param [EvaluationContext] ec
129
+ # The active context
130
+ # @return [RDF::Resource] BNode or nil for head of list
131
+ # @yield :statement
132
+ # @yieldparam [RDF::Statement] :statement
133
+ def parse_list(path, list, property, name, &block)
134
+ debug(path) {"list: #{list.inspect}, p=#{property.inspect}, n=#{name.inspect}"}
135
+
136
+ last = list.pop
137
+ result = first_bnode = last ? node : RDF.nil
138
+
139
+ depth do
140
+ list.each do |list_item|
141
+ # Traverse the value
142
+ statements("#{path}", list_item, first_bnode, RDF.first, name, &block)
143
+ rest_bnode = node
144
+ add_quad("#{path}", first_bnode, RDF.rest, rest_bnode, name, &block)
145
+ first_bnode = rest_bnode
146
+ end
147
+ if last
148
+ statements("#{path}", last, first_bnode, RDF.first, name, &block)
149
+ add_quad("#{path}", first_bnode, RDF.rest, RDF.nil, name, &block)
150
+ end
151
+ end
152
+ result
153
+ end
154
+
155
+ ##
156
+ # Create a new named node using the sequence
157
+ def node
158
+ n = RDF::Node.new(@node_seq)
159
+ @node_seq = @node_seq.succ
160
+ n
161
+ end
162
+
163
+ ##
164
+ # add a statement, object can be literal or URI or bnode
165
+ #
166
+ # @param [String] path
167
+ # @param [RDF::Resource] subject the subject of the statement
168
+ # @param [RDF::URI] predicate the predicate of the statement
169
+ # @param [RDF::Term] object the object of the statement
170
+ # @param [RDF::Resource] name the named graph context of the statement
171
+ # @yield :statement
172
+ # @yieldparam [RDF::Statement] :statement
173
+ def add_quad(path, subject, predicate, object, name)
174
+ predicate = RDF.type if predicate == '@type'
175
+ object = RDF::URI(object.to_s) if object.literal? && predicate == RDF.type
176
+ statement = RDF::Statement.new(subject, predicate, object, :context => name)
177
+ debug(path) {"statement: #{statement.to_nquads}"}
178
+ yield statement
179
+ end
180
+ end
181
+ end