talia_core 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. data/README.rdoc +6 -27
  2. data/VERSION.yml +3 -2
  3. data/config/database.yml +11 -11
  4. data/config/talia_core.yml +11 -6
  5. data/config/talia_core.yml.example +11 -6
  6. data/generators/talia_base/talia_base_generator.rb +3 -0
  7. data/generators/talia_base/templates/README +1 -1
  8. data/generators/talia_base/templates/app/controllers/source_data_controller.rb +1 -1
  9. data/generators/talia_base/templates/app/controllers/sources_controller.rb +31 -12
  10. data/generators/talia_base/templates/app/helpers/sources_helper.rb +77 -1
  11. data/generators/talia_base/templates/app/views/layouts/sources.html.erb +22 -0
  12. data/generators/talia_base/templates/app/views/sources/_data_list.html.erb +17 -0
  13. data/generators/talia_base/templates/app/views/sources/_property_item.html.erb +10 -0
  14. data/generators/talia_base/templates/app/views/sources/_property_list.html.erb +13 -0
  15. data/generators/talia_base/templates/app/views/sources/index.html.erb +16 -11
  16. data/generators/talia_base/templates/app/views/sources/semantic_templates/default/default.html.erb +8 -19
  17. data/generators/talia_base/templates/config/routes.rb +11 -0
  18. data/generators/talia_base/templates/migrations/create_semantic_relations.rb +2 -1
  19. data/generators/talia_base/templates/public/images/core/arrow.png +0 -0
  20. data/generators/talia_base/templates/public/images/core/building.png +0 -0
  21. data/generators/talia_base/templates/public/images/core/contents_top_left.gif +0 -0
  22. data/generators/talia_base/templates/public/images/core/document-horizontal-text.png +0 -0
  23. data/generators/talia_base/templates/public/images/core/document.png +0 -0
  24. data/generators/talia_base/templates/public/images/core/gear.png +0 -0
  25. data/generators/talia_base/templates/public/images/core/group.png +0 -0
  26. data/generators/talia_base/templates/public/images/core/header_bg.gif +0 -0
  27. data/generators/talia_base/templates/public/images/core/image.png +0 -0
  28. data/generators/talia_base/templates/public/images/core/imagebig.png +0 -0
  29. data/generators/talia_base/templates/public/images/core/left_edge.gif +0 -0
  30. data/generators/talia_base/templates/public/images/core/letter.png +0 -0
  31. data/generators/talia_base/templates/public/images/core/line.png +0 -0
  32. data/generators/talia_base/templates/public/images/core/logo.gif +0 -0
  33. data/generators/talia_base/templates/public/images/core/map.png +0 -0
  34. data/generators/talia_base/templates/public/images/core/period.png +0 -0
  35. data/generators/talia_base/templates/public/images/core/person.png +0 -0
  36. data/generators/talia_base/templates/public/images/core/person_default.png +0 -0
  37. data/generators/talia_base/templates/public/images/core/place.png +0 -0
  38. data/generators/talia_base/templates/public/images/core/source.png +0 -0
  39. data/generators/talia_base/templates/public/images/core/television.png +0 -0
  40. data/generators/talia_base/templates/public/images/core/text.png +0 -0
  41. data/generators/talia_base/templates/public/images/core/type.png +0 -0
  42. data/generators/talia_base/templates/public/images/core/video.png +0 -0
  43. data/generators/talia_base/templates/public/stylesheets/img/arrow.png +0 -0
  44. data/generators/talia_base/templates/public/stylesheets/main.css +276 -0
  45. data/generators/talia_base/templates/script/configure_talia +1 -1
  46. data/generators/talia_base/templates/script/setup_talia_backend +2 -0
  47. data/lib/core_ext/platform.rb +1 -0
  48. data/lib/core_ext/string.rb +6 -0
  49. data/lib/talia_core/active_source.rb +62 -3
  50. data/lib/talia_core/active_source_parts/class_methods.rb +36 -122
  51. data/lib/talia_core/active_source_parts/finders.rb +158 -0
  52. data/lib/talia_core/active_source_parts/predicate_handler.rb +7 -8
  53. data/lib/talia_core/active_source_parts/xml/generic_reader.rb +95 -11
  54. data/lib/talia_core/active_source_parts/xml/rdf_builder.rb +6 -13
  55. data/lib/talia_core/active_source_parts/xml/source_reader.rb +8 -3
  56. data/lib/talia_core/data_types/data_loader.rb +14 -6
  57. data/lib/talia_core/data_types/data_record.rb +5 -1
  58. data/lib/talia_core/data_types/iip_data.rb +1 -1
  59. data/lib/talia_core/data_types/mime_mapping.rb +8 -3
  60. data/lib/talia_core/errors.rb +4 -0
  61. data/lib/talia_core/initializer.rb +1 -8
  62. data/lib/talia_core/property_string.rb +58 -0
  63. data/lib/talia_core/semantic_collection_item.rb +3 -2
  64. data/lib/talia_core/semantic_collection_wrapper.rb +236 -198
  65. data/lib/talia_core/source.rb +130 -178
  66. data/lib/talia_core/source_types/collection.rb +15 -0
  67. data/lib/talia_core/source_types/dc_resource.rb +22 -0
  68. data/lib/talia_core/source_types/dummy_source.rb +22 -0
  69. data/lib/talia_core.rb +0 -1
  70. data/lib/talia_util/import_job_helper.rb +44 -16
  71. data/lib/talia_util/io_helper.rb +21 -1
  72. data/lib/talia_util/rake_tasks.rb +48 -72
  73. data/lib/talia_util/rdf_update.rb +22 -13
  74. data/lib/talia_util/test_helpers.rb +1 -1
  75. data/lib/talia_util.rb +0 -2
  76. data/test/core_ext/string_test.rb +5 -0
  77. data/test/talia_core/active_source_test.rb +151 -14
  78. data/test/talia_core/generic_xml_test.rb +46 -2
  79. data/test/talia_core/initializer_test.rb +0 -1
  80. data/test/talia_core/property_string_test.rb +78 -0
  81. data/test/talia_core/source_reader_test.rb +5 -1
  82. data/test/talia_core/source_test.rb +23 -32
  83. data/test/talia_util/import_job_helper_test.rb +1 -1
  84. data/test/talia_util/io_helper_test.rb +44 -0
  85. metadata +399 -373
  86. data/generators/talia_base/templates/app/views/sources/semantic_templates/default/province.html.erb +0 -19
  87. data/lib/acts_as_roled.rb +0 -11
  88. data/lib/talia_core/collection.rb +0 -13
  89. data/lib/talia_core/dc_resource.rb +0 -20
  90. data/lib/talia_core/dummy_source.rb +0 -20
  91. data/lib/talia_core/rails_ext/actionpack/action_controller/record_identifier.rb +0 -13
  92. data/lib/talia_core/rails_ext/actionpack/action_controller.rb +0 -1
  93. data/lib/talia_core/rails_ext/actionpack.rb +0 -1
  94. data/lib/talia_core/rails_ext.rb +0 -1
  95. data/lib/talia_util/data_import.rb +0 -91
  96. data/lib/talia_util/yaml_import.rb +0 -80
@@ -1,5 +1,5 @@
1
1
  module TaliaCore
2
-
2
+
3
3
  # Wraps the Array/Collection returned from the ActiveRecord, simply
4
4
  # "hiding" the SemanticProperty objects behind strings.
5
5
  class SemanticCollectionWrapper
@@ -71,6 +71,34 @@ module TaliaCore
71
71
  items.collect { |item| item.value }
72
72
  end
73
73
 
74
+ # Returns only the values of the given language.
75
+ # (At the moment this is not aware of region codes or any
76
+ # specialities, it just does a string matching)
77
+ #
78
+ # If no values with the given locale are found, this will
79
+ # fall back on the default locale and then to the values
80
+ # that don't have a locale at all.
81
+ def values_with_lang(language = 'en')
82
+ language_is_default = (language == I18n.default_locale.to_s)
83
+ real = []
84
+ default = []
85
+ unset = []
86
+ items.each do |item|
87
+ # FIXME: At the moment, this only works for value attributes, not for
88
+ # sources
89
+ if((val = item.value).respond_to?(:lang))
90
+ real << val if(val.lang == language)
91
+ default << val if(!language_is_default && (val.lang == I18n.default_locale.to_s))
92
+ unset << val if(val.lang.blank?)
93
+ else
94
+ default << val
95
+ end
96
+ end
97
+ return real unless(real.empty?)
98
+ return default unless(default.empty?)
99
+ unset
100
+ end
101
+
74
102
  # Size of the collection.
75
103
  def size
76
104
  return items.size if(loaded?)
@@ -80,245 +108,255 @@ module TaliaCore
80
108
  items.size
81
109
  else
82
110
  SemanticRelation.count(:conditions => {
83
- 'subject_id' => @assoc_source.id,
84
- 'predicate_uri' => @assoc_predicate })
111
+ 'subject_id' => @assoc_source.id,
112
+ 'predicate_uri' => @assoc_predicate })
113
+ end
85
114
  end
86
- end
87
115
 
88
- # Joins the elments into a string
89
- def join(join_str = ', ')
90
- strs = items.collect { |item| item.value.to_s }
91
- strs.join(join_str)
92
- end
116
+ # Joins the elments into a string
117
+ def join(join_str = ', ')
118
+ strs = items.collect { |item| item.value.to_s }
119
+ strs.join(join_str)
120
+ end
93
121
 
94
- # Index of the given value
95
- def index(value)
96
- items.index(value)
97
- end
122
+ # Index of the given value
123
+ def index(value)
124
+ items.index(value)
125
+ end
98
126
 
99
- # Check if the collection includes the value
100
- def include?(value)
101
- items.include?(value)
102
- end
127
+ # Check if the collection includes the value
128
+ def include?(value)
129
+ items.include?(value)
130
+ end
103
131
 
104
- # Get the index of the given item
105
-
106
- # Push to collection. Giving a string will create a property to be created,
107
- # saved and associated.
108
- def <<(value)
109
- add_with_order(value, nil)
110
- end
111
- alias_method :concat, '<<'
112
-
113
- # Adds the object and gives the relation the given order.
114
- def add_with_order(value, order)
115
- # We use order exclusively for "ordering" predicates
116
- assit_equal(TaliaCore::OrderedSource.index_to_predicate(order), @assoc_predicate) if(order)
117
- raise(ArgumentError, "cannot add nil") unless(value != nil)
118
- if(value.kind_of?(Array))
119
- value.each { |v| add_record_for(v, order) }
120
- else
121
- add_record_for(value, order)
132
+ # Get the index of the given item
133
+
134
+ # Push to collection. Giving a string will create a property to be created,
135
+ # saved and associated.
136
+ def <<(value)
137
+ add_with_order(value, nil)
122
138
  end
123
- end
124
-
125
- # Replace a value with a new one
126
- def replace(old_value, new_value)
127
- idx = items.index(old_value)
128
- items[idx].destroy
129
- add_record_for(new_value) { |new_item| items[idx] = new_item }
130
- end
131
-
132
- # Remove the given value. With no parameters, the whole list will be
133
- # cleared and the RDF will be updated immediately.
134
- def remove(*params)
135
- if(params.length > 0)
136
- params.each { |par| remove_relation(par) }
137
- else
138
- if(loaded?)
139
- items.each { |item| item.relation.destroy }
139
+ alias_method :concat, '<<'
140
+
141
+ # Adds the object and gives the relation the given order.
142
+ def add_with_order(value, order)
143
+ # We use order exclusively for "ordering" predicates
144
+ assit_equal(TaliaCore::OrderedSource.index_to_predicate(order), @assoc_predicate) if(order)
145
+ raise(ArgumentError, "cannot add nil") unless(value != nil)
146
+ if(value.kind_of?(Array))
147
+ value.each { |v| add_record_for(v, order) }
148
+ else
149
+ add_record_for(value, order)
150
+ end
151
+ end
152
+
153
+ # Replace a value with a new one
154
+ def replace(old_value, new_value)
155
+ idx = items.index(old_value)
156
+ items[idx].destroy
157
+ add_record_for(new_value) { |new_item| items[idx] = new_item }
158
+ end
159
+
160
+ # Remove the given value. With no parameters, the whole list will be
161
+ # cleared and the RDF will be updated immediately.
162
+ def remove(*params)
163
+ if(params.length > 0)
164
+ params.each { |par| remove_relation(par) }
140
165
  else
141
- SemanticRelation.destroy_all(
166
+ if(loaded?)
167
+ items.each { |item| item.relation.destroy }
168
+ else
169
+ SemanticRelation.destroy_all(
142
170
  :subject_id => @assoc_source.id,
143
171
  :predicate_uri => @assoc_predicate
144
- )
172
+ )
173
+ end
174
+ @assoc_source.my_rdf.remove(N::URI.new(@assoc_predicate))
175
+ @items = []
176
+ @loaded = true
145
177
  end
146
- @assoc_source.my_rdf.remove(N::URI.new(@assoc_predicate))
147
- @items = []
148
- @loaded = true
149
178
  end
150
- end
151
179
 
152
- # This attempts to save the items to the database
153
- def save_items!
154
- return if(clean?) # If there are no items, nothing was modified
155
- @assoc_source.save! unless(@assoc_source.id)
156
- @items.each do |item|
157
- next if(item.fat_relation) # we skip the fat relations, they are never new and never saveable
158
- rel = item.plain_relation
159
- must_save = rel.new_record?
160
- if(rel.object_id.nil?)
161
- rel.object.save! if(rel.object.new_record?)
162
- rel.object_id = rel.object.id
163
- must_save = true
164
- end
165
- unless(rel.subject_id != nil)
166
- rel.subject_id = @assoc_source.id
167
- must_save = true
180
+ # This attempts to save the items to the database
181
+ def save_items!
182
+ return if(clean?) # If there are no items, nothing was modified
183
+ @assoc_source.save! unless(@assoc_source.id)
184
+ @items.each do |item|
185
+ next if(item.fat_relation) # we skip the fat relations, they are never new and never saveable
186
+ rel = item.plain_relation
187
+ must_save = rel.new_record?
188
+ if(rel.object_id.nil?)
189
+ rel.object.save! if(rel.object.new_record?)
190
+ rel.object_id = rel.object.id
191
+ must_save = true
192
+ end
193
+ unless(rel.subject_id != nil)
194
+ rel.subject_id = @assoc_source.id
195
+ must_save = true
196
+ end
197
+ rel.save! if(must_save)
168
198
  end
169
- rel.save! if(must_save)
199
+ @items = nil unless(loaded?) # Otherwise we'll have trouble reload-and merging
170
200
  end
171
- @items = nil unless(loaded?) # Otherwise we'll have trouble reload-and merging
172
- end
173
201
 
174
- # Indicates of the internal collection is loaded
175
- def loaded?
176
- @loaded
177
- end
202
+ # Indicates of the internal collection is loaded
203
+ def loaded?
204
+ @loaded
205
+ end
178
206
 
179
- # Indicates that the wraper is "clean", that is it hasn't been written to
180
- # or read from
181
- def clean?
182
- @items.nil?
183
- end
207
+ # Indicates that the wraper is "clean", that is it hasn't been written to
208
+ # or read from
209
+ def clean?
210
+ @items.nil?
211
+ end
184
212
 
185
- def empty?
186
- self.size == 0
187
- end
213
+ def empty?
214
+ self.size == 0
215
+ end
188
216
 
189
- # Injector for a fat relation. This must take place before flagging the
190
- # source as "loaded"
191
- def inject_fat_item(fat_rel)
192
- raise(RuntimeError, 'Trying to inject in loaded object.') if(loaded?)
193
- @items ||= []
194
- @items << SemanticCollectionItem.new(fat_rel, :fat)
195
- end
217
+ # Injector for a fat relation. This must take place before flagging the
218
+ # source as "loaded"
219
+ def inject_fat_item(fat_rel)
220
+ raise(RuntimeError, 'Trying to inject in loaded object.') if(loaded?)
221
+ @items ||= []
222
+ @items << SemanticCollectionItem.new(fat_rel, :fat)
223
+ end
224
+
225
+ # Forces this relation to be empty. This initializes the relation
226
+ # as if no elements exist. This doesn't look anything up in the
227
+ # databse. *Warning* Only call this if you need an empty wrapper
228
+ # that doesn't look up anything in the database
229
+ def init_as_empty!
230
+ raise(ArgumentError, "Already initialized!") if(loaded?)
231
+ @items = []
232
+ @loaded = true
233
+ end
196
234
 
197
- private
235
+ private
198
236
 
199
- # Load the current relation. (Loading should be lazy, so that the database
200
- # is not hit until needed.
201
- def load!
202
- # The "fat" relations contain all the data to build the related objects if
203
- # required
204
- relations = SemanticRelation.find_fat_relations(@assoc_source, @assoc_predicate)
237
+ # Load the current relation. (Loading should be lazy, so that the database
238
+ # is not hit until needed.
239
+ def load!
240
+ # The "fat" relations contain all the data to build the related objects if
241
+ # required
242
+ relations = SemanticRelation.find_fat_relations(@assoc_source, @assoc_predicate)
205
243
 
206
- init_from_fat_rels(relations)
207
- end
244
+ init_from_fat_rels(relations)
245
+ end
208
246
 
209
- # Inject a fat relation into the items
247
+ # Inject a fat relation into the items
248
+
249
+ # Inititlizes the collection from the given collection of "fat" relations
250
+ def init_from_fat_rels(fat_relations)
251
+ # Check if there are records that have been added previously
252
+ old_items = @items
253
+ # Create the internal collection
254
+ @items = Array.new(fat_relations.size)
255
+ fat_relations.each_index do |idx|
256
+ rel = SemanticCollectionItem.new(fat_relations.at(idx), :fat)
257
+ @items[idx] = rel
258
+ end
259
+ @items = (@items | old_items) if(old_items)
260
+ @loaded = true
261
+ @items
262
+ end
210
263
 
264
+ # Returns the items in the collection
265
+ def items
266
+ load! unless(loaded?)
267
+ @items
268
+ end
211
269
 
212
- # Inititlizes the collection from the given collection of "fat" relations
213
- def init_from_fat_rels(fat_relations)
214
- # Check if there are records that have been added previously
215
- old_items = @items
216
- # Create the internal collection
217
- @items = Array.new(fat_relations.size)
218
- fat_relations.each_index do |idx|
219
- rel = SemanticCollectionItem.new(fat_relations.at(idx), :fat)
220
- @items[idx] = rel
270
+ # Deletes the relation where with the current predicate and the given
271
+ # value.
272
+ def remove_relation(value)
273
+ idx = items.index(value)
274
+ return unless(idx)
275
+ remove_at(idx)
221
276
  end
222
- @items = (@items | old_items) if(old_items)
223
- @loaded = true
224
- @items
225
- end
226
277
 
227
- # Returns the items in the collection
228
- def items
229
- load! unless(loaded?)
230
- @items
231
- end
278
+ # Removes a relation at the given index
279
+ def remove_at(index)
280
+ items.at(index).relation.destroy
281
+ items.delete_at(index)
282
+ end
232
283
 
233
- # Deletes the relation where with the current predicate and the given
234
- # value.
235
- def remove_relation(value)
236
- idx = items.index(value)
237
- return unless(idx)
238
- remove_at(idx)
239
- end
284
+ # Creates a record for a value and adds it. This will add the given value if it's
285
+ # a database record and otherwise create a property with the given value.
286
+ # The block can be given when you want to add the new SemanticCollectionItem
287
+ # to the colleciton in a specific way.
288
+ # are loaded.
289
+ def add_record_for(value, order = nil)
290
+ if(@force_type)
291
+ # If we have a type, we must transform the value
292
+ value = value.respond_to?(:uri) ? value.uri : value
293
+ value = ActiveSource.new(value.to_s)
294
+ end
240
295
 
241
- # Removes a relation at the given index
242
- def remove_at(index)
243
- items.at(index).relation.destroy
244
- items.delete_at(index)
245
- end
296
+ value = check_for_source(value) if(value.is_a?(ActiveSource))
246
297
 
247
- # Creates a record for a value and adds it. This will add the given value if it's
248
- # a database record and otherwise create a property with the given value.
249
- # The block can be given when you want to add the new SemanticCollectionItem
250
- # to the colleciton in a specific way.
251
- # are loaded.
252
- def add_record_for(value, order = nil)
253
- if(@force_type)
254
- # If we have a type, we must transform the value
255
- value = value.respond_to?(:uri) ? value.uri : value
256
- value = ActiveSource.new(value.to_s)
298
+ rel = create_predicate(value)
299
+ rel.rel_order = order if(order)
300
+ item = SemanticCollectionItem.new(rel, :plain)
301
+ block_given? ? yield(item) : insert_item(item)
257
302
  end
258
303
 
259
- value = check_for_source(value) if(value.is_a?(ActiveSource))
260
-
261
- rel = create_predicate(value)
262
- rel.rel_order = order if(order)
263
- item = SemanticCollectionItem.new(rel, :plain)
264
- block_given? ? yield(item) : insert_item(item)
265
- end
266
-
267
- # Insert a new item
268
- def insert_item(item)
269
- @items ||= []
270
- @items << item
271
- end
304
+ # Insert a new item
305
+ def insert_item(item)
306
+ @items ||= []
307
+ @items << item
308
+ end
272
309
 
273
- # Write a triple to the store. For normal operation it's recommended that
274
- # the usual accessor methods are used. This method does less checking
275
- # and does not accept array objects as value.
276
- def create_predicate(value)
277
- # TODO: Semantic Properties should only be created inside, since assigning
278
- # one to multiple relations and then deleting breaks integrity.
279
- # The whole semantic property should be flattened into a field in
280
- # SemanticRelation anyway.
281
- assit(!value.is_a?(SemanticProperty), "Should not pass in Semantic Properties here!")
282
- # We need to manually create the relation, to add the predicate_url
283
- to_add = SemanticRelation.new(
310
+ # Write a triple to the store. For normal operation it's recommended that
311
+ # the usual accessor methods are used. This method does less checking
312
+ # and does not accept array objects as value.
313
+ def create_predicate(value)
314
+ # TODO: Semantic Properties should only be created inside, since assigning
315
+ # one to multiple relations and then deleting breaks integrity.
316
+ # The whole semantic property should be flattened into a field in
317
+ # SemanticRelation anyway.
318
+ assit(!value.is_a?(SemanticProperty), "Should not pass in Semantic Properties here!")
319
+ # We need to manually create the relation, to add the predicate_url
320
+ to_add = SemanticRelation.new(
284
321
  :subject_id => @assoc_source.id,
285
322
  :predicate_uri => @assoc_predicate
286
- ) # Create a new relation linked to this object
323
+ ) # Create a new relation linked to this object
287
324
 
288
- if(value.is_a?(TaliaCore::ActiveSource) || value.is_a?(TaliaCore::SemanticProperty))
289
- to_add.object = value
290
- else
291
- prop = TaliaCore::SemanticProperty.new
292
- prop.value = value
293
- to_add.object = prop
325
+ if(value.is_a?(TaliaCore::ActiveSource) || value.is_a?(TaliaCore::SemanticProperty))
326
+ to_add.object = value
327
+ else
328
+ prop = TaliaCore::SemanticProperty.new
329
+ # Check if we need to add from a PropertyString
330
+ prop.value = value.is_a?(PropertyString) ? value.to_rdf : value
331
+ to_add.object = prop
332
+ end
333
+ to_add
294
334
  end
295
- to_add
296
- end
297
335
 
298
336
 
299
337
 
300
- # This "checks" for the given source. If a source with the same URI has been
301
- # added to any collection wrapper unsaved
302
- def check_for_source(source)
303
- return source unless(source.new_record?)
304
- cached = unsaved_source_cache[source.uri.to_s]
305
- if(cached.nil?)
306
- unsaved_source_cache[source.uri.to_s] = source
307
- cached = source
338
+ # This "checks" for the given source. If a source with the same URI has been
339
+ # added to any collection wrapper unsaved
340
+ def check_for_source(source)
341
+ return source unless(source.new_record?)
342
+ cached = unsaved_source_cache[source.uri.to_s]
343
+ if(cached.nil?)
344
+ unsaved_source_cache[source.uri.to_s] = source
345
+ cached = source
346
+ end
347
+ cached
308
348
  end
309
- cached
310
- end
311
349
 
312
- # Cache for sources that were added as unsaved elements
313
- def self.unsaved_source_cache
314
- @unsaved_source_cache ||= {}
315
- end
350
+ # Cache for sources that were added as unsaved elements
351
+ def self.unsaved_source_cache
352
+ @unsaved_source_cache ||= {}
353
+ end
316
354
 
317
- def unsaved_source_cache
318
- SemanticCollectionWrapper.unsaved_source_cache
319
- end
355
+ def unsaved_source_cache
356
+ SemanticCollectionWrapper.unsaved_source_cache
357
+ end
320
358
 
321
359
 
360
+ end
361
+
322
362
  end
323
-
324
- end