spiderfw 0.5.10 → 0.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. data/CHANGELOG +6 -0
  2. data/Rakefile +4 -1
  3. data/VERSION +1 -0
  4. data/apps/core/auth/controllers/login_controller.rb +10 -1
  5. data/apps/core/auth/controllers/mixins/auth_helper.rb +4 -2
  6. data/apps/core/auth/views/login.shtml +1 -1
  7. data/apps/core/components/_init.rb +1 -1
  8. data/apps/core/components/public/css/spider.css +0 -1
  9. data/apps/core/components/public/js/jquery/plugins/jquery.form.js +2 -1
  10. data/apps/core/components/public/js/list.js +14 -126
  11. data/apps/core/components/public/js/plugins/plugin.js +7 -0
  12. data/apps/core/components/public/js/plugins/sortable.js +124 -0
  13. data/apps/core/components/public/js/spider.js +212 -51
  14. data/apps/core/components/widgets/list/list.shtml +1 -0
  15. data/apps/core/components/widgets/table/table.rb +1 -1
  16. data/apps/core/components/widgets/table/table.shtml +3 -3
  17. data/apps/core/components/widgets/tabs/tabs.rb +70 -22
  18. data/apps/core/components/widgets/tabs/tabs.shtml +8 -2
  19. data/apps/core/forms/widgets/inputs/file_input/file_input.rb +4 -1
  20. data/data/locale/it/LC_MESSAGES/cms.mo +0 -0
  21. data/data/locale/it/LC_MESSAGES/spider.mo +0 -0
  22. data/data/locale/it/LC_MESSAGES/spider_files.mo +0 -0
  23. data/data/locale/it/LC_MESSAGES/spider_images.mo +0 -0
  24. data/lib/spiderfw.rb +3 -1
  25. data/lib/spiderfw/config/configuration.rb +3 -1
  26. data/lib/spiderfw/controller/controller.rb +8 -0
  27. data/lib/spiderfw/controller/mixins/static_content.rb +14 -2
  28. data/lib/spiderfw/controller/mixins/visual.rb +25 -25
  29. data/lib/spiderfw/controller/page_controller.rb +3 -0
  30. data/lib/spiderfw/controller/request.rb +4 -1
  31. data/lib/spiderfw/controller/session.rb +7 -6
  32. data/lib/spiderfw/create.rb +10 -1
  33. data/lib/spiderfw/model/base_model.rb +104 -57
  34. data/lib/spiderfw/model/condition.rb +9 -1
  35. data/lib/spiderfw/model/data_type.rb +15 -0
  36. data/lib/spiderfw/model/datatypes/uuid.rb +5 -0
  37. data/lib/spiderfw/model/extended_models/managed.rb +2 -2
  38. data/lib/spiderfw/model/mappers/db_mapper.rb +46 -21
  39. data/lib/spiderfw/model/mappers/mapper.rb +34 -8
  40. data/lib/spiderfw/model/mixins/list.rb +2 -0
  41. data/lib/spiderfw/model/mixins/tree.rb +2 -1
  42. data/lib/spiderfw/model/mixins/versioned.rb +12 -9
  43. data/lib/spiderfw/model/model.rb +72 -0
  44. data/lib/spiderfw/model/query_set.rb +7 -0
  45. data/lib/spiderfw/model/storage/base_storage.rb +5 -1
  46. data/lib/spiderfw/model/storage/db/adapters/mysql.rb +5 -2
  47. data/lib/spiderfw/model/storage/db/adapters/oci8.rb +9 -3
  48. data/lib/spiderfw/model/storage/db/db_storage.rb +8 -5
  49. data/lib/spiderfw/model/sync.rb +12 -6
  50. data/lib/spiderfw/requires.rb +1 -0
  51. data/lib/spiderfw/templates/blocks/parent_context.rb +26 -0
  52. data/lib/spiderfw/templates/blocks/widget.rb +17 -7
  53. data/lib/spiderfw/templates/layout.rb +11 -1
  54. data/lib/spiderfw/templates/template.rb +36 -3
  55. data/lib/spiderfw/templates/template_blocks.rb +36 -26
  56. data/lib/spiderfw/utils/annotations.rb +2 -1
  57. data/lib/spiderfw/utils/thread_out.rb +15 -0
  58. data/lib/spiderfw/widget/widget.rb +58 -16
  59. data/spider.gemspec +3 -0
  60. metadata +143 -48
@@ -4,14 +4,9 @@ module Spider; module TemplateBlocks
4
4
 
5
5
  class Widget < Block
6
6
 
7
- def compile(options={})
8
- klass = Spider::Template.get_registered_class(@el.name)
7
+ def self.attributes_to_init_params(attributes)
9
8
  init_params = []
10
- id = @el.get_attribute('id')
11
- raise TemplateCompileError, "Widget #{@el.name} does not have an id" unless id
12
- template_attr = @el.get_attribute('template')
13
- @el.remove_attribute('template')
14
- @el.attributes.to_hash.each do |key, val|
9
+ attributes.each do |key, val|
15
10
  if (!val.empty? && val[0].chr == '@')
16
11
  sval = var_to_scene(val, 'scene')
17
12
  elsif (!val.empty? && val[0].chr == '$')
@@ -23,6 +18,21 @@ module Spider; module TemplateBlocks
23
18
  init_key = "\"#{init_key}\"" unless key =~ /^[\w\d]+$/
24
19
  init_params << ":#{init_key} => #{sval}"
25
20
  end
21
+ init_params
22
+ end
23
+
24
+ def compile(options={})
25
+ klass = Spider::Template.get_registered_class(@el.name)
26
+ init_params = []
27
+ id = @el.get_attribute('id')
28
+ raise TemplateCompileError, "Widget #{@el.name} does not have an id" unless id
29
+ template_attr = @el.get_attribute('template')
30
+ @el.remove_attribute('template')
31
+ if klass.respond_to?(:compile_block)
32
+ init, c = klass.compile_block(@el, id, @el.attributes.to_hash, options)
33
+ return CompiledBlock.new(init, c)
34
+ end
35
+ init_params = self.class.attributes_to_init_params(@el.attributes.to_hash)
26
36
 
27
37
  html = ""
28
38
  @el.each_child do |ch|
@@ -8,6 +8,15 @@ module Spider
8
8
  super
9
9
  @template = @template.is_a?(Template) ? @template : Template.new(@template)
10
10
  @template.init(scene) unless @template.init_done?
11
+
12
+ end
13
+
14
+ def render(*args)
15
+ prepare_assets unless @assets_prepared
16
+ super
17
+ end
18
+
19
+ def prepare_assets
11
20
  @template_assets = {:css => [], :js => []}
12
21
  seen = {}
13
22
  all_assets.each do |res|
@@ -17,7 +26,8 @@ module Spider
17
26
  @template_assets[res[:type].to_sym] << res[:src]
18
27
  end
19
28
  @content[:yield_to] = @template
20
- scene.assets = @template_assets
29
+ @scene.assets = @template_assets
30
+ @assets_prepared = true
21
31
  end
22
32
 
23
33
  @@named_layouts = {}
@@ -243,6 +243,7 @@ module Spider
243
243
  else
244
244
  owner_class = (@owner ? @owner.class : @owner_class )
245
245
  end
246
+ raise "Asset type not given for #{src}" unless type
246
247
  res = Spider.find_resource(type.to_sym, src, @path, [owner_class, @definer_class])
247
248
  controller = nil
248
249
  if (res && res.definer)
@@ -252,6 +253,10 @@ module Spider
252
253
  end
253
254
  ass[:path] = res.path if res
254
255
  if controller.respond_to?(:pub_url)
256
+ if src[0].chr == '/'
257
+ # strips the app path from the src. FIXME: should probably be done somewhere else
258
+ src = src[(2+controller.app.relative_path.length)..-1]
259
+ end
255
260
  ass[:src] = controller.pub_url + '/' + src
256
261
  else
257
262
  ass[:src] = src
@@ -274,6 +279,8 @@ module Spider
274
279
  doc = open(path){ |f| Hpricot.XML(f) }
275
280
  root = doc.root
276
281
  overrides = []
282
+ orig_overrides = @overrides
283
+ @overrides = []
277
284
  if root.children
278
285
  override_tags.each do |tag|
279
286
  overrides += root.children_of_type('tpl:'+tag)
@@ -282,7 +289,11 @@ module Spider
282
289
  overrides.each{ |o| o.set_attribute('class', 'to_delete') }
283
290
  root.search('.to_delete').remove
284
291
  add_overrides overrides
292
+ @overrides += orig_overrides
285
293
  if (root.name == 'tpl:extend')
294
+
295
+ orig_overrides = @overrides
296
+ @overrides = []
286
297
  ext_src = root.get_attribute('src')
287
298
  ext_app = root.get_attribute('app')
288
299
  ext_widget = root.get_attribute('widget')
@@ -290,6 +301,7 @@ module Spider
290
301
  ext_widget = Spider::Template.get_registered_class(ext_widget)
291
302
  ext_src ||= ext_widget.default_template
292
303
  ext_owner = ext_widget
304
+ ext_app = ext_widget.app
293
305
  elsif ext_app
294
306
  ext_app = Spider.apps_by_path[ext_app]
295
307
  ext_owner = ext_widget
@@ -303,6 +315,13 @@ module Spider
303
315
  @dependencies << ext
304
316
  tpl = Template.new(ext)
305
317
  root = get_el(ext)
318
+ root.children_of_type('tpl:asset').each do |ass|
319
+ ass_src = ass.get_attribute('src')
320
+ unless ass_src[0].chr == '/'
321
+ ass.set_attribute('src', "/#{ext_app.relative_path}/#{ass_src}")
322
+ end
323
+ end
324
+ @overrides += orig_overrides
306
325
  if (assets && !assets.empty?)
307
326
  assets.each do |ass|
308
327
  root.innerHTML += ass.to_html
@@ -377,6 +396,7 @@ module Spider
377
396
  apply_widget_proc(widget, wp)
378
397
  end
379
398
  end
399
+ widget
380
400
  end
381
401
 
382
402
  def find_widget(path)
@@ -410,7 +430,7 @@ module Spider
410
430
  # Calls the run method on all widget instances.
411
431
  def run_widgets
412
432
  @widgets.each do |id, w|
413
- w.run unless w.did_run?
433
+ w.run if w.run? && !w.did_run?
414
434
  end
415
435
 
416
436
  end
@@ -450,12 +470,22 @@ module Spider
450
470
  # t.join
451
471
  # else
452
472
  scene.instance_eval("def __run_template\n"+@compiled.run_code+"end\n", @compiled.cache_path+'/run.rb', 0)
453
- scene.__run_template do |widget|
454
- @content[widget].render if @content[widget]
473
+ scene.__run_template do |yielded|
474
+ if yielded == :_parent
475
+ @owner.parent.template.run_block
476
+ else
477
+ @content[yielded].render if @content[yielded]
478
+ end
455
479
  end
456
480
  # end
457
481
  end
458
482
 
483
+ def run_block
484
+ @scene.__run_block do |yielded, block|
485
+ @content[yielded].render if @content[yielded]
486
+ end
487
+ end
488
+
459
489
  # Alias for #render.
460
490
  def run
461
491
  render(@scene)
@@ -506,6 +536,9 @@ module Spider
506
536
 
507
537
  # Applies an override to an (Hpricot) element.
508
538
  def apply_override(el, override)
539
+ if override.is_a?(Proc)
540
+ return override.call(el)
541
+ end
509
542
  search_string = override.get_attribute('search')
510
543
  override.name = 'tpl:override-content' if override.name == 'tpl:inline-override'
511
544
  if (search_string)
@@ -28,10 +28,12 @@ module Spider
28
28
  block = :Run
29
29
  elsif el.name == 'sp:yield'
30
30
  block = :Yield
31
- elsif el.name == 'sp:pass' || el.name == 'sp:template'
31
+ elsif el.name == 'sp:pass' || el.name == 'tpl:pass' || el.name == 'sp:template'
32
32
  block = :Pass
33
33
  elsif el.name == 'sp:debugger'
34
34
  block = :Debugger
35
+ elsif el.name == 'sp:parent-context'
36
+ block = :ParentContext
35
37
  elsif Spider::Template.registered?(el.name)
36
38
  klass = Spider::Template.get_registered_class(el.name)
37
39
  if klass < ::Spider::Widget
@@ -47,7 +49,34 @@ module Spider
47
49
  return block
48
50
  end
49
51
 
52
+ def self.parse_content(el, allowed_blocks=nil, template=nil)
53
+ content_blocks = []
54
+ last_block = nil
55
+ el.each_child do |ch|
56
+ #Spider.logger.debug "TRAVERSING CHILD #{ch}"
57
+ # Gives the preceding block the chance to "eat" the next elements
58
+ next if (last_block && last_block.get_following(ch))
59
+ last_block = TemplateBlocks.parse_element(ch, allowed_blocks, template)
60
+ content_blocks << last_block if (last_block)
61
+ end
62
+ return content_blocks
63
+ end
64
+
65
+ def self.compile_content(el, c='', init='', options={})
66
+ c ||= ""
67
+ init ||= ""
68
+ blocks = self.parse_content(el, options[:allowed_blocks], options[:template])
69
+ blocks.each do |block|
70
+ compiled = block.compile(options)
71
+ next unless compiled
72
+ c += compiled.run_code if (compiled.run_code)
73
+ init += compiled.init_code if (compiled.init_code)
74
+ end
75
+ return [c, init]
76
+ end
77
+
50
78
  class Block
79
+ attr_reader :el, :template, :allowed_blocks
51
80
 
52
81
  def initialize(el, template=nil, allowed_blocks=nil)
53
82
  @el = el
@@ -57,34 +86,13 @@ module Spider
57
86
  end
58
87
 
59
88
  def parse_content(el)
60
- content_blocks = []
61
- last_block = nil
62
- el.each_child do |ch|
63
- #Spider.logger.debug "TRAVERSING CHILD #{ch}"
64
- # Gives the preceding block the chance to "eat" the next elements
65
- next if (last_block && last_block.get_following(ch))
66
- last_block = TemplateBlocks.parse_element(ch, @allowed_blocks, @template)
67
- content_blocks << last_block if (last_block)
68
- end
69
- return content_blocks
89
+ TemplateBlocks.parse_content(el, @allowed_blocks, @template)
70
90
  end
71
91
 
72
92
  def compile_content(c='', init='', options={})
73
- c ||= ""
74
- init ||= ""
75
- blocks = parse_content(@el)
76
- blocks.each do |block|
77
- compiled = block.compile(options)
78
- next unless compiled
79
- # if (compiled.run_code =~ /nil/)
80
- # Spider::Logger.debug("NIL BLOCK")
81
- # Spider::Logger.debug(block)
82
- # Spider::Logger.debug(compiled.run_code)
83
- # end
84
- c += compiled.run_code if (compiled.run_code)
85
- init += compiled.init_code if (compiled.init_code)
86
- end
87
- return [c, init]
93
+ options[:allowed_blocks] ||= @allowed_blocks
94
+ options[:template] ||= @template
95
+ TemplateBlocks.compile_content(@el, c, init, options)
88
96
  end
89
97
 
90
98
  def get_following(el)
@@ -206,3 +214,5 @@ require 'spiderfw/templates/blocks/tag'
206
214
  require 'spiderfw/templates/blocks/widget'
207
215
  require 'spiderfw/templates/blocks/run'
208
216
  require 'spiderfw/templates/blocks/debugger'
217
+ require 'spiderfw/templates/blocks/parent_context'
218
+
@@ -109,6 +109,7 @@ module Annotations
109
109
  if (@annotations)
110
110
  @annotations.each do |method, vals|
111
111
  vals.each do |k, args|
112
+ args = [args] unless args.is_a?(Array)
112
113
  subclass.annotate(method, k, *args)
113
114
  end
114
115
  end
@@ -161,7 +162,7 @@ module Annotations
161
162
  end
162
163
  end
163
164
 
164
- # Defines an annotation. The given block will be called whenever the name annotation
165
+ # Defines an annotation. The given block will be called whenever the "name" annotation
165
166
  # is encountered; it will be passed the current Class, the annotated Method, and the annotation arguments.
166
167
  def define_annotation(name, &proc)
167
168
  @defined_annotations ||= {}
@@ -21,4 +21,19 @@ module ThreadOut #:nodoc:
21
21
  self.write(stuff)
22
22
  end
23
23
 
24
+ def self.output_to(io)
25
+ if block_given?
26
+ prev_out = Thread.current[:stdout]
27
+ Thread.current[:stdout] = io
28
+ yield
29
+ Thread.current[:stdout] = prev_out
30
+ else
31
+ Thread.current[:stdout] = io
32
+ end
33
+ end
34
+
35
+ def output_to(io, &proc)
36
+ self.class.output_to(io, &proc)
37
+ end
38
+
24
39
  end
@@ -26,6 +26,7 @@ module Spider
26
26
  def inherited(subclass)
27
27
  subclass.instance_variable_set(:@attributes, attributes.clone)
28
28
  subclass.instance_variable_set(:@scene_attributes, @scene_attributes.clone) if @scene_attributes
29
+ super
29
30
  end
30
31
 
31
32
  def attribute(name, params={})
@@ -56,6 +57,11 @@ module Spider
56
57
  attr_reader(name)
57
58
  end
58
59
 
60
+ def s_attribute(name, params={})
61
+ attribute(name, params)
62
+ attr_to_scene(name)
63
+ end
64
+
59
65
  def i_attr_accessor(name, params={})
60
66
  params[:instance_attr] = true
61
67
  i_attribute(name, params)
@@ -209,9 +215,14 @@ module Spider
209
215
  locale = @request.locale.language
210
216
  include_js = [
211
217
  '/js/jquery/jquery-1.4.2.js', '/js/inheritance.js', '/js/spider.js', '/js/jquery/plugins/jquery.query-2.1.6.js',
212
- '/js/jquery/jquery-ui/development-bundle/ui/jquery-ui-1.7.2.custom.js', #'/js/jquery/jquery-ui/development-bundle/ui/jquery-ui-1.7.2.custom.min.js'
213
- "/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-#{locale}.js", '/js/jquery/plugins/jquery.form.js'
218
+ '/js/jquery/plugins/jquery.form.js',
219
+ '/js/plugins/plugin.js'
214
220
  ]
221
+ # include_js << [
222
+ # '/js/jquery/jquery-ui/development-bundle/ui/jquery-ui-1.7.2.custom.js',
223
+ # #'/js/jquery/jquery-ui/development-bundle/ui/jquery-ui-1.7.2.custom.min.js',
224
+ # "/js/jquery/jquery-ui/development-bundle/ui/i18n/ui.datepicker-#{locale}.js"
225
+ # ]
215
226
  include_css = [
216
227
  '/css/spider.css', '/js/jquery/jquery-ui/css/smoothness/jquery-ui-1.7.2.custom.css',
217
228
  ]
@@ -264,19 +275,27 @@ module Spider
264
275
  end
265
276
 
266
277
  def widget_before(action='')
267
- return unless active?
268
278
  Spider.logger.debug("Widget #{self} widget_before(#{action})")
269
279
  widget_init(action)
270
280
  prepare
281
+ prepare_scene(@scene)
271
282
  @before_done = true
272
283
  end
273
284
 
274
285
 
275
286
  def active?
287
+
276
288
  return @active unless @active.nil?
289
+ return @active = true if @is_target
290
+ return @active = false if attributes[:"sp:target-only"] == "true"
277
291
  @active = (!@request.params['_wt'] || @target_mode)
278
292
  end
279
293
 
294
+ def active=(val)
295
+
296
+ @active = val
297
+ end
298
+
280
299
  def before_done?
281
300
  @before_done
282
301
  end
@@ -332,7 +351,7 @@ module Spider
332
351
  end
333
352
 
334
353
  # Instantiates this widget's own subwidgets.
335
- def init_widgets
354
+ def init_widgets(template=@template)
336
355
  if (self.class.scene_attributes)
337
356
  self.class.scene_attributes.each do |name|
338
357
  @scene[name] = instance_variable_get("@#{name}")
@@ -340,8 +359,8 @@ module Spider
340
359
  end
341
360
  template.request = @request
342
361
  template.response = @response
343
- @template.init(@scene)
344
- @template.widgets.each do |name, w|
362
+ template.init(@scene)
363
+ template.widgets.each do |name, w|
345
364
  add_widget(w)
346
365
  end
347
366
  @widgets.each do |id, w|
@@ -383,7 +402,7 @@ module Spider
383
402
 
384
403
  def run(action='')
385
404
  @widgets.each do |wname, w|
386
- w.run
405
+ w.run if w.run?
387
406
  end
388
407
  if (@parent)
389
408
  @parent.after_widget(@id.to_sym)
@@ -396,7 +415,7 @@ module Spider
396
415
  end
397
416
 
398
417
  def run?
399
- @is_target || (!@target_mode && !attributes[:"sp:target_only"])
418
+ @is_target || (!@target_mode && !attributes[:"sp:target-only"])
400
419
  end
401
420
 
402
421
  def init_widget_done?
@@ -410,6 +429,7 @@ module Spider
410
429
 
411
430
  def render
412
431
  prepare_scene(@scene)
432
+ set_scene_vars(@scene)
413
433
  @template.render(@scene) unless @target_mode && !@is_target
414
434
  end
415
435
 
@@ -455,6 +475,10 @@ module Spider
455
475
  return p
456
476
  end
457
477
 
478
+ def has_params?
479
+ !params.empty?
480
+ end
481
+
458
482
  def session(container=@request.session, klass=Hash)
459
483
  s = (container['_w'] ||= klass.new)
460
484
  @id_path[0..-2].each{ |id| s = (s[id] ||= klass.new) }
@@ -481,7 +505,7 @@ module Spider
481
505
  def add_widget(widget)
482
506
  widget.id_path = @id_path + [widget.id]
483
507
  widget.parent = self
484
- widget.active = true if @is_target || @active
508
+ # widget.active = true if @is_target || @active
485
509
  @widgets[widget.id.to_sym] = widget
486
510
  if (@widgets_runtime_content[widget.id.to_sym])
487
511
  @widgets_runtime_content[widget.id.to_sym].each do |content|
@@ -514,7 +538,9 @@ module Spider
514
538
 
515
539
 
516
540
  def parse_runtime_content_xml(xml, src_path=nil)
517
- parse_runtime_content(Hpricot(xml), src_path)
541
+ return if xml.empty?
542
+ doc = Hpricot(xml)
543
+ parse_runtime_content(doc, src_path) if doc.children && doc.root && doc.root.children
518
544
  end
519
545
 
520
546
  def parse_runtime_content(doc, src_path=nil)
@@ -613,11 +639,7 @@ module Spider
613
639
 
614
640
  def prepare_scene(scene)
615
641
  scene = super
616
- if (self.class.scene_attributes) # Repeat for new instance variables
617
- self.class.scene_attributes.each do |name|
618
- @scene[name] = instance_variable_get("@#{name}")
619
- end
620
- end
642
+
621
643
  # FIXME: owner_controller should be (almost) always defined
622
644
  scene.controller[:request_path] = owner_controller.request_path if owner_controller
623
645
  scene.widget[:request_path] = widget_request_path
@@ -631,6 +653,14 @@ module Spider
631
653
  return scene
632
654
  end
633
655
 
656
+ def set_scene_vars(scene)
657
+ if (self.class.scene_attributes) # Repeat for new instance variables
658
+ self.class.scene_attributes.each do |name|
659
+ @scene[name] = instance_variable_get("@#{name}")
660
+ end
661
+ end
662
+ end
663
+
634
664
  def css_class
635
665
  return @css_class if @css_class
636
666
  supers = self.class.ancestors.select{ |c| c != Spider::Widget && c.subclass_of?(Spider::Widget)}
@@ -665,8 +695,20 @@ module Spider
665
695
 
666
696
  module WidgetScene
667
697
 
698
+ def widget_target
699
+ "#{self[:request][:path]}?_wt=#{self[:widget][:id_path].join('/')}"
700
+ end
701
+
668
702
  def widget_action(name, *params)
669
- "#{self[:request][:path]}?_wt=#{self[:widget][:id_path].join('/')}&_we=#{name}"+(params.map{|p| "&_wp[]=#{p}"}).join('')
703
+ "#{self.widget_target}&_we=#{name}"+(params.map{|p| "&_wp[]=#{p}"}).join('')
704
+ end
705
+
706
+ def widget_params(params)
707
+ "#{self[:request][:path]}?"+params.map{ |k, v| "_w#{self[:widget][:param]}[#{k}]=#{v}"}.join('&')
708
+ end
709
+
710
+ def widget_param(name)
711
+ "_w#{self[:widget][:param]}[#{name}]"
670
712
  end
671
713
 
672
714
  end