schematic 0.0.8 → 0.1.2
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.
- data/.rspec +1 -0
- data/README.rdoc +1 -1
- data/TODO +5 -0
- data/lib/schematic.rb +4 -1
- data/lib/schematic/generator/column.rb +47 -0
- data/lib/schematic/generator/names.rb +27 -0
- data/lib/schematic/generator/namespaces.rb +11 -0
- data/lib/schematic/generator/restrictions/base.rb +24 -0
- data/lib/schematic/generator/restrictions/enumeration.rb +21 -0
- data/lib/schematic/generator/restrictions/length.rb +19 -0
- data/lib/schematic/generator/restrictions/pattern.rb +20 -0
- data/lib/schematic/generator/types.rb +29 -0
- data/lib/schematic/generator/xsd.rb +111 -0
- data/lib/schematic/serializers/xsd.rb +10 -132
- data/lib/schematic/version.rb +1 -1
- data/schematic.gemspec +1 -0
- data/spec/schematic/generator/restrictions/enumeration_spec.rb +53 -0
- data/spec/schematic/generator/restrictions/length_spec.rb +181 -0
- data/spec/schematic/generator/restrictions/pattern_spec.rb +52 -0
- data/spec/schematic/serializers/xsd_extend_spec.rb +37 -0
- data/spec/schematic/serializers/xsd_validation_presence_spec.rb +95 -0
- data/spec/schematic/serializers/xsd_xsd_ignore_methods_spec.rb +25 -0
- data/spec/schematic/serializers/xsd_xsd_methods_spec.rb +116 -0
- data/spec/schematic/serializers/xsd_xsd_minimum_occurrences_for_spec.rb +47 -0
- data/spec/schematic_serializers_xsd_spec.rb +67 -356
- data/spec/spec_helper.rb +100 -0
- metadata +40 -2
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/README.rdoc
CHANGED
@@ -31,7 +31,7 @@ You can include additional elements by defining a ".xsd_methods" on your class:
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
You can exclude elements
|
34
|
+
You can exclude elements by defining a ".xsd_ignore_methods" on your class:
|
35
35
|
|
36
36
|
class Post < ActiveRecord::Base
|
37
37
|
def self.xsd_ignore_methods
|
data/TODO
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
* Support numericality validation as pattern restriction
|
2
|
+
* Support exclusion validation as a pattern restriction
|
3
|
+
* Support exact length validation with length? or is min/max okay?
|
4
|
+
* Support uniqueness constraint on primary keys and uniqness validations using unique or key tag?
|
5
|
+
|
data/lib/schematic.rb
CHANGED
@@ -10,6 +10,9 @@ require "builder"
|
|
10
10
|
|
11
11
|
require 'active_support/inflector/inflections'
|
12
12
|
require 'active_support/inflections'
|
13
|
-
|
13
|
+
|
14
|
+
Dir[File.join(File.dirname(__FILE__), "schematic/**/*.rb")].each do |file|
|
15
|
+
require file.gsub(/\/.rb$/,'')
|
16
|
+
end
|
14
17
|
|
15
18
|
ActiveRecord::Base.send(:extend, Schematic::Serializers::Xsd)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Schematic
|
2
|
+
module Generator
|
3
|
+
class Column
|
4
|
+
|
5
|
+
def initialize(klass, column, additional_methods = {}, ignored_methods = {})
|
6
|
+
@klass = klass
|
7
|
+
@column = column
|
8
|
+
@additional_methods = additional_methods
|
9
|
+
@ignored_methods = ignored_methods
|
10
|
+
end
|
11
|
+
|
12
|
+
def generate(builder)
|
13
|
+
return if skip_generation?
|
14
|
+
|
15
|
+
builder.xs :element, "name" => @column.name.dasherize, "minOccurs" => minimum_occurrences_for_column, "maxOccurs" => "1" do |field|
|
16
|
+
field.xs :complexType do |complex_type|
|
17
|
+
complex_type.xs :simpleContent do |simple_content|
|
18
|
+
simple_content.xs :restriction, "base" => map_type(@column) do |restriction|
|
19
|
+
Restrictions::Length.new(@klass, @column).generate(restriction)
|
20
|
+
Restrictions::Enumeration.new(@klass, @column).generate(restriction)
|
21
|
+
Restrictions::Pattern.new(@klass, @column).generate(restriction)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def minimum_occurrences_for_column
|
29
|
+
@klass._validators[@column.name.to_sym].each do |column_validation|
|
30
|
+
next unless column_validation.is_a? ActiveModel::Validations::PresenceValidator
|
31
|
+
return "1" if column_validation.options[:allow_blank] != true && column_validation.options[:if].nil?
|
32
|
+
end
|
33
|
+
"0"
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def map_type(column)
|
38
|
+
Types::COMPLEX[column.type][:complex_type]
|
39
|
+
end
|
40
|
+
|
41
|
+
def skip_generation?
|
42
|
+
@additional_methods.keys.map(&:to_s).include?(@column.name) ||
|
43
|
+
@ignored_methods.map(&:to_s).include?(@column.name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Schematic
|
2
|
+
module Generator
|
3
|
+
class Names
|
4
|
+
|
5
|
+
def initialize(klass)
|
6
|
+
@klass = klass
|
7
|
+
end
|
8
|
+
|
9
|
+
def type
|
10
|
+
@klass.name.demodulize
|
11
|
+
end
|
12
|
+
|
13
|
+
def element
|
14
|
+
type.underscore.dasherize
|
15
|
+
end
|
16
|
+
|
17
|
+
def element_collection
|
18
|
+
element.pluralize
|
19
|
+
end
|
20
|
+
|
21
|
+
def collection_type
|
22
|
+
type.pluralize
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Schematic
|
2
|
+
module Generator
|
3
|
+
module Restrictions
|
4
|
+
class Base
|
5
|
+
def initialize(klass, column)
|
6
|
+
@klass = klass
|
7
|
+
@column = column
|
8
|
+
end
|
9
|
+
|
10
|
+
def for_validator(validator_klass)
|
11
|
+
@klass._validators[@column.name.to_sym].each do |column_validation|
|
12
|
+
next unless column_validation.is_a? validator_klass
|
13
|
+
next unless column_validation.options[:if].nil?
|
14
|
+
yield(column_validation)
|
15
|
+
return
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Schematic
|
2
|
+
module Generator
|
3
|
+
module Restrictions
|
4
|
+
class Enumeration < Base
|
5
|
+
def initialize(klass, column)
|
6
|
+
@klass = klass
|
7
|
+
@column = column
|
8
|
+
end
|
9
|
+
|
10
|
+
def generate(builder)
|
11
|
+
for_validator ActiveModel::Validations::InclusionValidator do |validator|
|
12
|
+
validator.options[:in].each do |value|
|
13
|
+
builder.xs(:enumeration, "value" => value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Schematic
|
2
|
+
module Generator
|
3
|
+
module Restrictions
|
4
|
+
class Length < Base
|
5
|
+
def initialize(klass, column)
|
6
|
+
@klass = klass
|
7
|
+
@column = column
|
8
|
+
end
|
9
|
+
|
10
|
+
def generate(builder)
|
11
|
+
for_validator ActiveModel::Validations::LengthValidator do |validator|
|
12
|
+
builder.xs(:maxLength, "value" => validator.options[:maximum]) if validator.options[:maximum]
|
13
|
+
builder.xs(:minLength, "value" => validator.options[:minimum]) if validator.options[:minimum]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Schematic
|
2
|
+
module Generator
|
3
|
+
module Restrictions
|
4
|
+
class Pattern < Base
|
5
|
+
def initialize(klass, column)
|
6
|
+
@klass = klass
|
7
|
+
@column = column
|
8
|
+
end
|
9
|
+
|
10
|
+
def generate(builder)
|
11
|
+
for_validator ActiveModel::Validations::FormatValidator do |validator|
|
12
|
+
builder.xs(:pattern, "value" => validator.options[:with].source) if validator.options[:with]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Schematic
|
2
|
+
module Generator
|
3
|
+
class Types
|
4
|
+
COMPLEX = {
|
5
|
+
:integer => { :complex_type => "Integer", :xsd_type => "xs:integer" },
|
6
|
+
:float => { :complex_type => "Float", :xsd_type => "xs:float" },
|
7
|
+
:string => { :complex_type => "String", :xsd_type => "xs:string" },
|
8
|
+
:text => { :complex_type => "Text", :xsd_type => "xs:string" },
|
9
|
+
:datetime => { :complex_type => "DateTime", :xsd_type => "xs:dateTime" },
|
10
|
+
:date => { :complex_type => "Date", :xsd_type => "xs:date" },
|
11
|
+
:boolean => { :complex_type => "Boolean", :xsd_type => "xs:boolean" },
|
12
|
+
}
|
13
|
+
|
14
|
+
def self.xsd(builder)
|
15
|
+
Types::COMPLEX.each do |key, value|
|
16
|
+
complex_type_name = value[:complex_type]
|
17
|
+
xsd_type = value[:xsd_type]
|
18
|
+
builder.xs :complexType, "name" => complex_type_name do |complex_type|
|
19
|
+
complex_type.xs :simpleContent do |simple_content|
|
20
|
+
simple_content.xs :extension, "base" => xsd_type do |extension|
|
21
|
+
extension.xs :attribute, "name" => "type", "type" => "xs:string", "use" => "optional"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Schematic
|
2
|
+
module Generator
|
3
|
+
class Xsd
|
4
|
+
attr_reader :output, :names
|
5
|
+
attr_accessor :options
|
6
|
+
|
7
|
+
def initialize(klass, options = {})
|
8
|
+
@klass = klass
|
9
|
+
@names = Names.new(klass)
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def header(builder)
|
14
|
+
builder.instruct!
|
15
|
+
end
|
16
|
+
|
17
|
+
def schema(builder)
|
18
|
+
builder.xs :schema, ns("xs", :w3, :schema) do |schema|
|
19
|
+
Types.xsd(schema)
|
20
|
+
element_for_klass(schema)
|
21
|
+
generate(schema, @klass)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def element_for_klass(builder)
|
26
|
+
builder.xs :element, "name" => @names.element_collection, "type" => @names.collection_type
|
27
|
+
end
|
28
|
+
|
29
|
+
def generate(builder, klass)
|
30
|
+
nested_attributes.each do |nested_attribute|
|
31
|
+
next if nested_attribute.klass == klass || nested_attribute.klass == klass.superclass
|
32
|
+
|
33
|
+
nested_attribute.klass.generate_xsd(builder, klass, @options)
|
34
|
+
end
|
35
|
+
|
36
|
+
generate_complex_type_for_collection(builder)
|
37
|
+
generate_complex_type_for_model(builder)
|
38
|
+
end
|
39
|
+
|
40
|
+
def generate_complex_type_for_collection(builder)
|
41
|
+
builder.xs :complexType, "name" => @names.collection_type do |complex_type|
|
42
|
+
complex_type.xs :sequence do |sequence|
|
43
|
+
sequence.xs :element, "name" => @names.element, "type" => @names.type, "minOccurs" => "0", "maxOccurs" => "unbounded"
|
44
|
+
end
|
45
|
+
complex_type.xs :attribute, "name" => "type", "type" => "xs:string", "fixed" => "array"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def generate_complex_type_for_model(builder)
|
50
|
+
builder.xs :complexType, "name" => @names.type do |complex_type|
|
51
|
+
additional_methods = @klass.xsd_methods.merge(@options[:methods] || {})
|
52
|
+
ignored_methods = @klass.xsd_ignore_methods | (@options[:exclude] || [])
|
53
|
+
complex_type.xs :all do |all|
|
54
|
+
generate_column_elements(all, additional_methods, ignored_methods)
|
55
|
+
|
56
|
+
nested_attributes.each do |nested_attribute|
|
57
|
+
all.xs :element, "name" => "#{nested_attribute.name.to_s.dasherize}-attributes", "type" => nested_attribute.klass.xsd_generator.names.collection_type, "minOccurs" => "0", "maxOccurs" => "1"
|
58
|
+
end
|
59
|
+
|
60
|
+
generate_additional_methods(all, additional_methods)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def generate_column_elements(builder, additional_methods, ignored_methods)
|
66
|
+
@klass.columns.each do |column|
|
67
|
+
Column.new(@klass, column, additional_methods, ignored_methods).generate(builder)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def generate_additional_methods(builder, additional_methods)
|
72
|
+
additional_methods.each do |method_name, values|
|
73
|
+
method_xsd_name = method_name.to_s.dasherize
|
74
|
+
if values.present?
|
75
|
+
builder.xs :element, "name" => method_xsd_name, "minOccurs" => "0", "maxOccurs" => "1" do |element|
|
76
|
+
element.xs :complexType do |complex_type|
|
77
|
+
if values.is_a?(Array)
|
78
|
+
complex_type.xs :sequence do |nested_sequence|
|
79
|
+
values.each do |value|
|
80
|
+
nested_sequence.xs :element, "name" => value.to_s.dasherize, "minOccurs" => "0", "maxOccurs" => "unbounded"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
elsif values.is_a?(Hash)
|
84
|
+
complex_type.xs :all do |nested_all|
|
85
|
+
generate_additional_methods(nested_all, values)
|
86
|
+
end
|
87
|
+
else
|
88
|
+
raise "Additional methods must be a hash of hashes or hash of arrays"
|
89
|
+
end
|
90
|
+
complex_type.xs :attribute, "name" => "type", "type" => "xs:string", "fixed" => "array", "use" => "optional"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
else
|
94
|
+
builder.xs :element, "name" => method_xsd_name, "minOccurs" => "0", "maxOccurs" => "1"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def nested_attributes
|
100
|
+
@klass.reflect_on_all_associations.select do |association|
|
101
|
+
@klass.instance_methods.include?("#{association.name}_attributes=".to_sym) && association.options[:polymorphic] != true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def ns(ns, provider, key)
|
106
|
+
{ "xmlns:#{ns}" => Namespaces::PROVIDERS[provider][key] }
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -7,133 +7,22 @@ module Schematic
|
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
|
+
def xsd_generator
|
11
|
+
@xsd_generator ||= Schematic::Generator::Xsd.new(self)
|
12
|
+
end
|
13
|
+
|
10
14
|
def to_xsd(options = {})
|
11
15
|
output = ""
|
12
16
|
builder = Builder::XmlMarkup.new(:target => output, :indent => 2)
|
13
|
-
|
14
|
-
builder
|
15
|
-
|
16
|
-
generate_xsd(options, schema, self)
|
17
|
-
end
|
17
|
+
xsd_generator.options = options
|
18
|
+
xsd_generator.header(builder)
|
19
|
+
xsd_generator.schema(builder)
|
18
20
|
output
|
19
21
|
end
|
20
22
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
:float => "xs:float",
|
25
|
-
:string => "xs:string",
|
26
|
-
:text => "xs:string",
|
27
|
-
:datetime => "xs:dateTime",
|
28
|
-
:date => "xs:date",
|
29
|
-
:boolean => "xs:boolean"
|
30
|
-
}[column.type]
|
31
|
-
end
|
32
|
-
|
33
|
-
def xsd_minimum_occurrences_for_column(column)
|
34
|
-
self._validators[column.name.to_sym].each do |column_validation|
|
35
|
-
next unless column_validation.is_a? ActiveModel::Validations::PresenceValidator
|
36
|
-
return "1" if column_validation.options[:allow_blank] != true && column_validation.options[:if].nil?
|
37
|
-
end
|
38
|
-
"0"
|
39
|
-
end
|
40
|
-
|
41
|
-
def xsd_element_collection_name
|
42
|
-
xsd_element_name.pluralize
|
43
|
-
end
|
44
|
-
|
45
|
-
def xsd_type_collection_name
|
46
|
-
xsd_type_name.pluralize
|
47
|
-
end
|
48
|
-
|
49
|
-
def xsd_type_name
|
50
|
-
self.name.demodulize
|
51
|
-
end
|
52
|
-
|
53
|
-
def xsd_element_name
|
54
|
-
xsd_type_name.underscore.dasherize
|
55
|
-
end
|
56
|
-
|
57
|
-
def generate_xsd(options, builder, klass)
|
58
|
-
xsd_nested_attributes.each do |nested_attribute|
|
59
|
-
next if nested_attribute.klass == klass || nested_attribute.klass == klass.superclass
|
60
|
-
|
61
|
-
nested_attribute.klass.generate_xsd(options, builder, klass)
|
62
|
-
end
|
63
|
-
|
64
|
-
generate_xsd_complex_type_for_collection(builder)
|
65
|
-
generate_xsd_complex_type_for_model(options, builder)
|
66
|
-
end
|
67
|
-
|
68
|
-
private
|
69
|
-
|
70
|
-
def generate_xsd_complex_type_for_collection(builder)
|
71
|
-
builder.xs :complexType, "name" => xsd_type_collection_name do |complex_type|
|
72
|
-
complex_type.xs :sequence do |sequence|
|
73
|
-
sequence.xs :element, "name" => xsd_element_name, "type" => xsd_type_name, "minOccurs" => "0", "maxOccurs" => "unbounded"
|
74
|
-
end
|
75
|
-
complex_type.xs :attribute, "name" => "type", "type" => "xs:string", "fixed" => "array"
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def generate_xsd_complex_type_for_model(options, builder)
|
80
|
-
builder.xs :complexType, "name" => xsd_type_name do |complex_type|
|
81
|
-
additional_methods = xsd_methods.merge(options[:methods] || {})
|
82
|
-
ignored_methods = xsd_ignore_methods | (options[:exclude] || [])
|
83
|
-
complex_type.xs :all do |all|
|
84
|
-
generate_xsd_column_elements(all, additional_methods, ignored_methods)
|
85
|
-
|
86
|
-
xsd_nested_attributes.each do |nested_attribute|
|
87
|
-
all.xs :element, "name" => "#{nested_attribute.name.to_s.dasherize}-attributes", "type" => nested_attribute.klass.xsd_type_collection_name, "minOccurs" => "0", "maxOccurs" => "1"
|
88
|
-
end
|
89
|
-
|
90
|
-
generate_xsd_additional_methods(all, additional_methods)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def generate_xsd_column_elements(builder, additional_methods, ignored_methods)
|
96
|
-
xsd_columns.each do |column|
|
97
|
-
next if additional_methods.keys.map(&:to_s).include?(column.name) || ignored_methods.map(&:to_s).include?(column.name)
|
98
|
-
|
99
|
-
builder.xs :element, "name" => column.name.dasherize, "minOccurs" => xsd_minimum_occurrences_for_column(column), "maxOccurs" => "1" do |field|
|
100
|
-
field.xs :complexType do |complex_type|
|
101
|
-
complex_type.xs :simpleContent do |simple_content|
|
102
|
-
simple_content.xs :extension, "base" => map_column_type_to_xsd_type(column) do |extension|
|
103
|
-
extension.xs :attribute, "name" => "type", "type" => "xs:string", "use" => "optional"
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
def generate_xsd_additional_methods(builder, additional_methods)
|
112
|
-
additional_methods.each do |method_name, values|
|
113
|
-
method_xsd_name = method_name.to_s.dasherize
|
114
|
-
if values.present?
|
115
|
-
builder.xs :element, "name" => method_xsd_name, "minOccurs" => "0", "maxOccurs" => "1" do |element|
|
116
|
-
element.xs :complexType do |complex_type|
|
117
|
-
if values.is_a?(Array)
|
118
|
-
complex_type.xs :sequence do |nested_sequence|
|
119
|
-
values.each do |value|
|
120
|
-
nested_sequence.xs :element, "name" => value.to_s.dasherize, "minOccurs" => "0", "maxOccurs" => "unbounded"
|
121
|
-
end
|
122
|
-
end
|
123
|
-
elsif values.is_a?(Hash)
|
124
|
-
complex_type.xs :all do |nested_all|
|
125
|
-
generate_xsd_additional_methods(nested_all, values)
|
126
|
-
end
|
127
|
-
else
|
128
|
-
raise "Additional methods must be a hash of hashes or hash of arrays"
|
129
|
-
end
|
130
|
-
complex_type.xs :attribute, "name" => "type", "type" => "xs:string", "fixed" => "array", "use" => "optional"
|
131
|
-
end
|
132
|
-
end
|
133
|
-
else
|
134
|
-
builder.xs :element, "name" => method_xsd_name, "minOccurs" => "0", "maxOccurs" => "1"
|
135
|
-
end
|
136
|
-
end
|
23
|
+
def generate_xsd(builder, klass, options)
|
24
|
+
xsd_generator.options = options
|
25
|
+
xsd_generator.generate(builder, klass)
|
137
26
|
end
|
138
27
|
|
139
28
|
def xsd_methods
|
@@ -144,17 +33,6 @@ module Schematic
|
|
144
33
|
[]
|
145
34
|
end
|
146
35
|
|
147
|
-
def xsd_nested_attributes
|
148
|
-
self.reflect_on_all_associations.select do |association|
|
149
|
-
self.instance_methods.include?("#{association.name}_attributes=".to_sym) && association.options[:polymorphic] != true
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def xsd_columns
|
154
|
-
self.columns
|
155
|
-
end
|
156
|
-
|
157
|
-
|
158
36
|
end
|
159
37
|
end
|
160
38
|
end
|