lutaml-model 0.4.0 → 0.5.0

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +34 -18
  3. data/README.adoc +166 -8
  4. data/lib/lutaml/model/key_value_mapping.rb +0 -1
  5. data/lib/lutaml/model/key_value_mapping_rule.rb +3 -1
  6. data/lib/lutaml/model/mapping_rule.rb +14 -2
  7. data/lib/lutaml/model/serialize.rb +67 -52
  8. data/lib/lutaml/model/type/decimal.rb +5 -0
  9. data/lib/lutaml/model/version.rb +1 -1
  10. data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +1 -0
  11. data/lib/lutaml/model/xml_adapter/builder/oga.rb +180 -0
  12. data/lib/lutaml/model/xml_adapter/builder/ox.rb +1 -0
  13. data/lib/lutaml/model/xml_adapter/oga/document.rb +20 -0
  14. data/lib/lutaml/model/xml_adapter/oga/element.rb +117 -0
  15. data/lib/lutaml/model/xml_adapter/oga_adapter.rb +77 -44
  16. data/lib/lutaml/model/xml_adapter/xml_document.rb +11 -9
  17. data/lib/lutaml/model/xml_mapping.rb +0 -1
  18. data/lib/lutaml/model/xml_mapping_rule.rb +16 -4
  19. data/spec/address_spec.rb +1 -0
  20. data/spec/fixtures/sample_model.rb +7 -0
  21. data/spec/lutaml/model/custom_model_spec.rb +47 -1
  22. data/spec/lutaml/model/included_spec.rb +192 -0
  23. data/spec/lutaml/model/mixed_content_spec.rb +48 -32
  24. data/spec/lutaml/model/multiple_mapping_spec.rb +329 -0
  25. data/spec/lutaml/model/ordered_content_spec.rb +1 -1
  26. data/spec/lutaml/model/render_nil_spec.rb +3 -0
  27. data/spec/lutaml/model/serializable_spec.rb +1 -1
  28. data/spec/lutaml/model/type/boolean_spec.rb +62 -0
  29. data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +11 -11
  30. data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +1 -1
  31. data/spec/lutaml/model/xml_adapter_spec.rb +2 -2
  32. data/spec/lutaml/model/xml_mapping_spec.rb +24 -9
  33. data/spec/sample_model_spec.rb +114 -0
  34. metadata +8 -2
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module XmlAdapter
6
+ module Builder
7
+ class Oga
8
+ def self.build(options = {}, &block)
9
+ if block_given?
10
+ XmlAdapter::Builder::Oga.new(options, &block)
11
+ else
12
+ XmlAdapter::Builder::Oga.new(options)
13
+ end
14
+ end
15
+
16
+ attr_reader :document, :current_node, :options
17
+
18
+ def initialize(options = {})
19
+ @document = XmlAdapter::Oga::Document.new
20
+ @current_node = @document
21
+ @options = options
22
+ yield(self) if block_given?
23
+ end
24
+
25
+ def create_element(name, attributes = {}, &block)
26
+ if @current_namespace && !name.start_with?("#{@current_namespace}:")
27
+ name = "#{@current_namespace}:#{name}"
28
+ end
29
+
30
+ if block_given?
31
+ element(name, attributes, &block)
32
+ else
33
+ element(name, attributes)
34
+ end
35
+ end
36
+
37
+ def element(name, attributes = {})
38
+ oga_element = ::Oga::XML::Element.new(name: name)
39
+ if block_given?
40
+ element_attributes(oga_element, attributes)
41
+ @current_node.children << oga_element
42
+ # Save previous node to reset the pointer for the rest of the iteration
43
+ previous_node = @current_node
44
+ # Set current node to new element as pointer for the block
45
+ @current_node = oga_element
46
+ yield(self)
47
+ # Reset the pointer for the rest of the iterations
48
+ @current_node = previous_node
49
+ end
50
+ oga_element
51
+ end
52
+
53
+ def add_element(oga_element, child)
54
+ if child.is_a?(String)
55
+ current_element = oga_element.is_a?(XmlAdapter::Oga::Document) ? current_node : oga_element
56
+ add_xml_fragment(current_element, child)
57
+ elsif oga_element.is_a?(XmlAdapter::Oga::Document)
58
+ oga_element.children.last.children << child
59
+ else
60
+ oga_element.children << child
61
+ end
62
+ end
63
+
64
+ def add_attribute(element, name, value)
65
+ attribute = ::Oga::XML::Attribute.new(
66
+ name: name,
67
+ value: value.to_s,
68
+ )
69
+ if element.is_a?(XmlAdapter::Oga::Document)
70
+ element.children.last.attributes << attribute
71
+ else
72
+ element.attributes << attribute
73
+ end
74
+ end
75
+
76
+ def create_and_add_element(
77
+ element_name,
78
+ prefix: (prefix_unset = true
79
+ nil),
80
+ attributes: {},
81
+ &block
82
+ )
83
+ @current_namespace = nil if prefix.nil? && !prefix_unset
84
+ prefixed_name = if prefix
85
+ "#{prefix}:#{element_name}"
86
+ elsif @current_namespace && !element_name.start_with?("#{@current_namespace}:")
87
+ "#{@current_namespace}:#{element_name}"
88
+ else
89
+ element_name
90
+ end
91
+
92
+ if block_given?
93
+ element(prefixed_name, attributes, &block)
94
+ else
95
+ element(prefixed_name, attributes)
96
+ end
97
+ end
98
+
99
+ def <<(text)
100
+ @current_node.text(text)
101
+ end
102
+
103
+ def add_xml_fragment(element, content)
104
+ fragment = "<fragment>#{content}</fragment>"
105
+ parsed_fragment = ::Oga.parse_xml(fragment)
106
+ parsed_children = parsed_fragment.children.first.children
107
+ if element.is_a?(XmlAdapter::Oga::Document)
108
+ element.children.last.children += parsed_children
109
+ else
110
+ element.children += parsed_children
111
+ end
112
+ end
113
+
114
+ def add_text(element, text, cdata: false)
115
+ return add_cdata(element, text) if cdata
116
+
117
+ oga_text = ::Oga::XML::Text.new(text: text.to_s)
118
+ if element.is_a?(XmlAdapter::Oga::Document)
119
+ children = element.children
120
+ children.empty? ? children << oga_text : children.last.children << oga_text
121
+ else
122
+ element.children << oga_text
123
+ end
124
+ end
125
+
126
+ def add_cdata(element, value)
127
+ oga_cdata = ::Oga::XML::CData.new(text: value.to_s)
128
+ if element.is_a?(XmlAdapter::Oga::Document)
129
+ element.children.last.children << oga_cdata
130
+ else
131
+ element.children << oga_cdata
132
+ end
133
+ end
134
+
135
+ def add_namespace_prefix(prefix)
136
+ @current_namespace = prefix
137
+ self
138
+ end
139
+
140
+ def parent
141
+ @document
142
+ end
143
+
144
+ def text(value = nil)
145
+ return @current_node.inner_text if value.nil?
146
+
147
+ str = value.is_a?(Array) ? value.join : value
148
+ @current_node.children << ::Oga::XML::Text.new(text: str)
149
+ end
150
+
151
+ def method_missing(method_name, *args)
152
+ if block_given?
153
+ @current_node.public_send(method_name, *args) do
154
+ yield(self)
155
+ end
156
+ else
157
+ @current_node.public_send(method_name, *args)
158
+ end
159
+ end
160
+
161
+ def respond_to_missing?(method_name, include_private = false)
162
+ @current_node.respond_to?(method_name) || super
163
+ end
164
+
165
+ private
166
+
167
+ def element_attributes(oga_element, attributes)
168
+ oga_element.attributes = attributes.map do |name, value|
169
+ ::Oga::XML::Attribute.new(
170
+ name: name,
171
+ value: value,
172
+ element: oga_element,
173
+ )
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
@@ -43,6 +43,7 @@ module Lutaml
43
43
  end
44
44
 
45
45
  def create_and_add_element(element_name, prefix: nil, attributes: {})
46
+ element_name = element_name.first if element_name.is_a?(Array)
46
47
  prefixed_name = if prefix
47
48
  "#{prefix}:#{element_name}"
48
49
  elsif @current_namespace && !element_name.start_with?("#{@current_namespace}:")
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module XmlAdapter
6
+ module Oga
7
+ class Document < ::Oga::XML::Document
8
+ def initialize(options = {})
9
+ super
10
+ end
11
+
12
+ def text(value = nil)
13
+ children << ::Oga::XML::Text.new(text: value)
14
+ self
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lutaml
4
+ module Model
5
+ module XmlAdapter
6
+ module Oga
7
+ class Element < XmlElement
8
+ def initialize(node, parent: nil)
9
+ name = case node
10
+ when ::Oga::XML::Element
11
+ namespace_name = node.namespace_name
12
+ add_namespaces(node)
13
+ children = parse_children(node)
14
+ attributes = node_attributes(node)
15
+ node.name
16
+ when ::Oga::XML::Text
17
+ "text"
18
+ end
19
+ super(
20
+ name,
21
+ Hash(attributes),
22
+ Array(children),
23
+ node.text,
24
+ parent_document: parent,
25
+ namespace_prefix: namespace_name,
26
+ )
27
+ end
28
+
29
+ def text?
30
+ children.empty? && text&.length&.positive?
31
+ end
32
+
33
+ def text
34
+ @text
35
+ end
36
+
37
+ def to_xml(builder = Builder::Oga.build)
38
+ build_xml(builder).to_xml
39
+ end
40
+
41
+ def build_xml(builder = Builder::Oga.build)
42
+ if name == "text"
43
+ builder.add_text(builder.current_node, @text)
44
+ else
45
+ builder.create_element(name, build_attributes(self)) do |xml|
46
+ children.each { |child| child.build_xml(xml) }
47
+ end
48
+ end
49
+
50
+ builder
51
+ end
52
+
53
+ def inner_xml
54
+ children.map(&:to_xml).join
55
+ end
56
+
57
+ private
58
+
59
+ def node_attributes(node)
60
+ node.attributes.each_with_object({}) do |attr, hash|
61
+ next if attr_is_namespace?(attr)
62
+
63
+ name = if attr.namespace
64
+ "#{attr.namespace.name}:#{attr.name}"
65
+ else
66
+ attr.name
67
+ end
68
+ hash[name] = XmlAttribute.new(
69
+ name,
70
+ attr.value,
71
+ namespace: attr.namespace&.uri,
72
+ namespace_prefix: attr.namespace&.name,
73
+ )
74
+ end
75
+ end
76
+
77
+ def parse_children(node)
78
+ node.children.map { |child| self.class.new(child, parent: self) }
79
+ end
80
+
81
+ def add_namespaces(node)
82
+ node.namespaces.each_value do |namespace|
83
+ add_namespace(XmlNamespace.new(namespace.uri, namespace.name))
84
+ end
85
+ end
86
+
87
+ def attr_is_namespace?(attr)
88
+ attribute_is_namespace?(attr.name) ||
89
+ namespaces[attr.name]&.uri == attr.value
90
+ end
91
+
92
+ def build_attributes(node, _options = {})
93
+ attrs = node.attributes.transform_values(&:value)
94
+
95
+ attrs.merge(build_namespace_attributes(node))
96
+ end
97
+
98
+ def build_namespace_attributes(node)
99
+ namespace_attrs = {}
100
+
101
+ node.own_namespaces.each_value do |namespace|
102
+ namespace_attrs[namespace.attr_name] = namespace.uri
103
+ end
104
+
105
+ node.children.each do |child|
106
+ namespace_attrs = namespace_attrs.merge(
107
+ build_namespace_attributes(child),
108
+ )
109
+ end
110
+
111
+ namespace_attrs
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -1,69 +1,102 @@
1
1
  require "oga"
2
2
  require_relative "xml_document"
3
+ require_relative "oga/document"
4
+ require_relative "oga/element"
5
+ require_relative "builder/oga"
3
6
 
4
7
  module Lutaml
5
8
  module Model
6
9
  module XmlAdapter
7
10
  class OgaAdapter < XmlDocument
8
- def self.parse(xml, _options = {})
9
- parsed = Oga.parse_xml(xml)
10
- root = OgaElement.new(parsed)
11
- new(root)
12
- end
13
-
14
- def to_h
15
- { @root.name => parse_element(@root) }
11
+ def self.parse(xml, options = {})
12
+ encoding = options[:encoding] || xml.encoding.to_s
13
+ xml = xml.encode("UTF-16").encode("UTF-8") if encoding && encoding != "UTF-8"
14
+ parsed = ::Oga.parse_xml(xml)
15
+ @root = Oga::Element.new(parsed.children.first)
16
+ new(@root, encoding)
16
17
  end
17
18
 
18
19
  def to_xml(options = {})
19
- builder = Oga::XML::Builder.new
20
- build_element(builder, @root, options)
21
- xml_data = builder.to_xml
20
+ builder_options = {}
21
+
22
+ builder_options[:encoding] = if options.key?(:encoding)
23
+ options[:encoding] || "UTF-8"
24
+ elsif options.key?(:parse_encoding)
25
+ options[:parse_encoding]
26
+ else
27
+ "UTF-8"
28
+ end
29
+ builder = Builder::Oga.build(options) do |xml|
30
+ if @root.is_a?(Oga::Element)
31
+ @root.build_xml(xml)
32
+ else
33
+ build_element(xml, @root, options)
34
+ end
35
+ end
36
+ xml_data = builder.to_xml.encode!(builder_options[:encoding])
22
37
  options[:declaration] ? declaration(options) + xml_data : xml_data
38
+ rescue Encoding::ConverterNotFoundError
39
+ invalid_encoding!(builder_options[:encoding])
23
40
  end
24
41
 
25
42
  private
26
43
 
27
- def build_element(builder, element, options = {})
28
- attributes = build_attributes(element.attributes)
29
- builder.element(element.name, attributes) do
30
- element.children.each do |child|
31
- build_element(builder, child, options)
32
- end
33
- builder.text(element.text) if element.text
34
- end
35
- end
44
+ def build_ordered_element(builder, element, options = {})
45
+ mapper_class = options[:mapper_class] || element.class
46
+ xml_mapping = mapper_class.mappings_for(:xml)
47
+ return xml unless xml_mapping
36
48
 
37
- def build_attributes(attributes)
38
- attributes.transform_values(&:value)
39
- end
49
+ attributes = build_attributes(element, xml_mapping).compact
40
50
 
41
- def parse_element(element)
42
- result = { "_text" => element.text }
43
- element.children.each do |child|
44
- next if child.is_a?(Oga::XML::Text)
51
+ tag_name = options[:tag_name] || xml_mapping.root_element
52
+ builder.create_and_add_element(tag_name,
53
+ attributes: attributes) do |el|
54
+ index_hash = {}
55
+ content = []
45
56
 
46
- result[child.name] ||= []
47
- result[child.name] << parse_element(child)
48
- end
49
- result
50
- end
51
- end
57
+ element.element_order.each do |name|
58
+ index_hash[name] ||= -1
59
+ curr_index = index_hash[name] += 1
60
+
61
+ element_rule = xml_mapping.find_by_name(name)
62
+ next if element_rule.nil?
63
+
64
+ attribute_def = attribute_definition_for(element, element_rule,
65
+ mapper_class: mapper_class)
66
+ value = attribute_value_for(element, element_rule)
67
+
68
+ next if element_rule == xml_mapping.content_mapping && element_rule.cdata && name == "text"
69
+
70
+ if element_rule == xml_mapping.content_mapping
71
+ text = xml_mapping.content_mapping.serialize(element)
72
+ text = text[curr_index] if text.is_a?(Array)
73
+
74
+ next el.add_text(el, text, cdata: element_rule.cdata) if element.mixed?
52
75
 
53
- class OgaElement < XmlElement
54
- def initialize(node)
55
- attributes = node.attributes.each_with_object({}) do |attr, hash|
56
- hash[attr.name] = XmlAttribute.new(attr.name, attr.value)
76
+ content << text
77
+ elsif !value.nil? || element_rule.render_nil?
78
+ value = value[curr_index] if attribute_def.collection?
79
+
80
+ add_to_xml(
81
+ el,
82
+ element,
83
+ nil,
84
+ value,
85
+ options.merge(
86
+ attribute: attribute_def,
87
+ rule: element_rule,
88
+ mapper_class: mapper_class,
89
+ ),
90
+ )
91
+ end
92
+ end
93
+
94
+ el.add_text(el, content.join)
57
95
  end
58
- super(node.name, attributes, parse_children(node), node.text)
59
96
  end
60
97
 
61
- private
62
-
63
- def parse_children(node)
64
- node.children.select do |child|
65
- child.is_a?(Oga::XML::Element)
66
- end.map { |child| OgaElement.new(child) }
98
+ def invalid_encoding!(encoding)
99
+ raise Error, "unknown encoding name - #{encoding}"
67
100
  end
68
101
  end
69
102
  end
@@ -233,20 +233,16 @@ module Lutaml
233
233
  end
234
234
 
235
235
  process_content_mapping(element, xml_mapping.content_mapping,
236
- prefixed_xml)
236
+ prefixed_xml, mapper_class)
237
237
  end
238
238
  end
239
239
 
240
- def process_content_mapping(element, content_rule, xml)
240
+ def process_content_mapping(element, content_rule, xml, mapper_class)
241
241
  return unless content_rule
242
242
 
243
243
  if content_rule.custom_methods[:to]
244
- @root.send(
245
- content_rule.custom_methods[:to],
246
- element,
247
- xml.parent,
248
- xml,
249
- )
244
+ mapper_class.new.send(content_rule.custom_methods[:to], element,
245
+ xml.parent, xml)
250
246
  else
251
247
  text = content_rule.serialize(element)
252
248
  text = text.join if text.is_a?(Array)
@@ -340,7 +336,9 @@ module Lutaml
340
336
  next if options[:except]&.include?(mapping_rule.to)
341
337
  next if mapping_rule.custom_methods[:to]
342
338
 
343
- if mapping_rule.namespace && mapping_rule.prefix && mapping_rule.name != "lang"
339
+ mapping_rule_name = mapping_rule.multiple_mappings? ? mapping_rule.name.first : mapping_rule.name
340
+
341
+ if mapping_rule.namespace && mapping_rule.prefix && mapping_rule_name != "lang"
344
342
  hash["xmlns:#{mapping_rule.prefix}"] = mapping_rule.namespace
345
343
  end
346
344
 
@@ -378,6 +376,10 @@ module Lutaml
378
376
  key = ["xmlns", xml_mapping.namespace_prefix].compact.join(":")
379
377
  { key => xml_mapping.namespace_uri }
380
378
  end
379
+
380
+ def self.type
381
+ Utils.snake_case(self).split("/").last.split("_").first
382
+ end
381
383
  end
382
384
  end
383
385
  end
@@ -92,7 +92,6 @@ module Lutaml
92
92
  nil)
93
93
  )
94
94
  validate!(name, to, with, type: TYPES[:attribute])
95
-
96
95
  rule = XmlMappingRule.new(
97
96
  name,
98
97
  to: to,
@@ -19,7 +19,8 @@ module Lutaml
19
19
  namespace_set: false,
20
20
  prefix_set: false,
21
21
  attribute: false,
22
- default_namespace: nil
22
+ default_namespace: nil,
23
+ id: nil
23
24
  )
24
25
  super(
25
26
  name,
@@ -29,6 +30,7 @@ module Lutaml
29
30
  with: with,
30
31
  delegate: delegate,
31
32
  attribute: attribute,
33
+ id: id
32
34
  )
33
35
 
34
36
  @namespace = if namespace.to_s == "inherit"
@@ -45,6 +47,7 @@ module Lutaml
45
47
 
46
48
  @namespace_set = namespace_set
47
49
  @prefix_set = prefix_set
50
+ @id = id
48
51
  end
49
52
 
50
53
  def namespace_set?
@@ -72,14 +75,23 @@ module Lutaml
72
75
  end
73
76
 
74
77
  def prefixed_name
78
+ rule_name = multiple_mappings? ? name.first : name
75
79
  if prefix
76
- "#{prefix}:#{name}"
80
+ "#{prefix}:#{rule_name}"
81
+ else
82
+ rule_name
83
+ end
84
+ end
85
+
86
+ def namespaced_names(parent_namespace = nil)
87
+ if multiple_mappings?
88
+ name.map { |rule_name| namespaced_name(parent_namespace, rule_name) }
77
89
  else
78
- name
90
+ [namespaced_name(parent_namespace)]
79
91
  end
80
92
  end
81
93
 
82
- def namespaced_name(parent_namespace = nil)
94
+ def namespaced_name(parent_namespace = nil, name = self.name)
83
95
  if name == "lang"
84
96
  "#{prefix}:#{name}"
85
97
  elsif namespace_set? || @attribute
data/spec/address_spec.rb CHANGED
@@ -100,6 +100,7 @@ RSpec.describe Address do
100
100
  expect(address_from_json.post_code).to eq("01001")
101
101
  expect(address_from_json.person.first.first_name).to eq("Tom")
102
102
  expect(address_from_json.person.last.first_name).to eq("Jack")
103
+ expect(address_from_json.person.last.active).to be(false)
103
104
  end
104
105
 
105
106
  it "serializes to XML with a collection of persons" do
@@ -36,5 +36,12 @@ class SampleModel < Lutaml::Model::Serializable
36
36
  yaml do
37
37
  map "name", to: :name
38
38
  map "age", to: :age
39
+ map "balance", to: :balance
40
+ map "tags", to: :tags
41
+ map "preferences", to: :preferences
42
+ map "status", to: :status
43
+ map "large_number", to: :large_number
44
+ map "email", to: :email
45
+ map "role", to: :role
39
46
  end
40
47
  end
@@ -78,7 +78,7 @@ module CustomModelSpecs
78
78
  end
79
79
 
80
80
  class Id
81
- attr_accessor :id
81
+ attr_accessor :id, :prefix
82
82
  end
83
83
 
84
84
  class Docid < Lutaml::Model::Serializable
@@ -147,6 +147,28 @@ module CustomModelSpecs
147
147
  end
148
148
  end
149
149
  end
150
+
151
+ class CustomId < Lutaml::Model::Serializable
152
+ model Id
153
+ attribute :id, :string
154
+ attribute :prefix, :string
155
+
156
+ xml do
157
+ root "custom-id"
158
+ map_attribute "prefix", to: :prefix
159
+ map_content with: { to: :id_to_xml, from: :id_from_xml }
160
+ end
161
+
162
+ def id_to_xml(model, _parent, doc)
163
+ content = "ABC-#{model.id}"
164
+ doc.add_text(doc, content)
165
+ end
166
+
167
+ def id_from_xml(model, value)
168
+ id = value.split("-").last
169
+ model.id = id.to_i
170
+ end
171
+ end
150
172
  end
151
173
 
152
174
  RSpec.describe "CustomModel" do
@@ -407,4 +429,28 @@ RSpec.describe "CustomModel" do
407
429
  end
408
430
  end
409
431
  end
432
+
433
+ context "with custom methods" do
434
+ describe ".xml serialization" do
435
+ it "handles custom content mapping methods" do
436
+ xml = '<custom-id prefix="ABC">ABC-123</custom-id>'
437
+
438
+ instance = CustomModelSpecs::Id.new
439
+ instance.id = 123
440
+ instance.prefix = "ABC"
441
+ result_xml = CustomModelSpecs::CustomId.to_xml(instance)
442
+ expect(result_xml).to eq(xml)
443
+ end
444
+ end
445
+
446
+ describe ".xml deserialization" do
447
+ it "handles custom content mapping methods" do
448
+ xml = '<custom-id prefix="ABC">ABC-123</custom-id>'
449
+ instance = CustomModelSpecs::CustomId.from_xml(xml)
450
+
451
+ expect(instance.id).to eq(123)
452
+ expect(instance.prefix).to eq("ABC")
453
+ end
454
+ end
455
+ end
410
456
  end