xmlconv 1.0.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.
- data/.gemtest +0 -0
- data/History.txt +6 -0
- data/LICENSE +339 -0
- data/README.txt +29 -0
- data/Rakefile +37 -0
- data/bin/admin +68 -0
- data/bin/xmlconvd +38 -0
- data/data/grammar/i2.grammar +73 -0
- data/lib/xmlconv/config.rb +61 -0
- data/lib/xmlconv/custom/lookandfeel.rb +50 -0
- data/lib/xmlconv/i2/address.rb +35 -0
- data/lib/xmlconv/i2/date.rb +41 -0
- data/lib/xmlconv/i2/document.rb +27 -0
- data/lib/xmlconv/i2/header.rb +32 -0
- data/lib/xmlconv/i2/order.rb +61 -0
- data/lib/xmlconv/i2/parser.rb +23 -0
- data/lib/xmlconv/i2/position.rb +45 -0
- data/lib/xmlconv/i2/record.rb +11 -0
- data/lib/xmlconv/model/address.rb +20 -0
- data/lib/xmlconv/model/agreement.rb +10 -0
- data/lib/xmlconv/model/bdd.rb +37 -0
- data/lib/xmlconv/model/bsr.rb +16 -0
- data/lib/xmlconv/model/delivery.rb +15 -0
- data/lib/xmlconv/model/delivery_item.rb +24 -0
- data/lib/xmlconv/model/document.rb +25 -0
- data/lib/xmlconv/model/freetext_container.rb +26 -0
- data/lib/xmlconv/model/id_container.rb +22 -0
- data/lib/xmlconv/model/invoice.rb +18 -0
- data/lib/xmlconv/model/invoice_item.rb +11 -0
- data/lib/xmlconv/model/item.rb +19 -0
- data/lib/xmlconv/model/item_container.rb +15 -0
- data/lib/xmlconv/model/name.rb +27 -0
- data/lib/xmlconv/model/part_info.rb +10 -0
- data/lib/xmlconv/model/part_info_container.rb +15 -0
- data/lib/xmlconv/model/party.rb +20 -0
- data/lib/xmlconv/model/party_container.rb +20 -0
- data/lib/xmlconv/model/price.rb +10 -0
- data/lib/xmlconv/model/price_container.rb +18 -0
- data/lib/xmlconv/model/transaction.rb +28 -0
- data/lib/xmlconv/state/global.rb +28 -0
- data/lib/xmlconv/state/global_predefine.rb +11 -0
- data/lib/xmlconv/state/login.rb +39 -0
- data/lib/xmlconv/state/transaction.rb +13 -0
- data/lib/xmlconv/state/transactions.rb +130 -0
- data/lib/xmlconv/util/application.rb +142 -0
- data/lib/xmlconv/util/autoload.rb +35 -0
- data/lib/xmlconv/util/destination.rb +249 -0
- data/lib/xmlconv/util/invoicer.rb +71 -0
- data/lib/xmlconv/util/known_user.rb +16 -0
- data/lib/xmlconv/util/mail.rb +31 -0
- data/lib/xmlconv/util/polling_manager.rb +198 -0
- data/lib/xmlconv/util/session.rb +23 -0
- data/lib/xmlconv/util/transaction.rb +133 -0
- data/lib/xmlconv/util/validator.rb +20 -0
- data/lib/xmlconv/view/foot.rb +27 -0
- data/lib/xmlconv/view/head.rb +13 -0
- data/lib/xmlconv/view/login.rb +36 -0
- data/lib/xmlconv/view/navigation.rb +30 -0
- data/lib/xmlconv/view/navigationlink.rb +21 -0
- data/lib/xmlconv/view/pager.rb +73 -0
- data/lib/xmlconv/view/preformatted.rb +54 -0
- data/lib/xmlconv/view/template.rb +17 -0
- data/lib/xmlconv/view/transaction.rb +42 -0
- data/lib/xmlconv/view/transactions.rb +90 -0
- data/lib/xmlconv.rb +3 -0
- data/test/config.rb +12 -0
- data/test/mock.rb +149 -0
- data/test/suite.rb +16 -0
- data/test/test_i2/address.rb +88 -0
- data/test/test_i2/date.rb +50 -0
- data/test/test_i2/document.rb +62 -0
- data/test/test_i2/header.rb +39 -0
- data/test/test_i2/order.rb +94 -0
- data/test/test_i2/parser.rb +312 -0
- data/test/test_i2/position.rb +65 -0
- data/test/test_model/address.rb +35 -0
- data/test/test_model/agreement.rb +22 -0
- data/test/test_model/bdd.rb +55 -0
- data/test/test_model/bsr.rb +38 -0
- data/test/test_model/delivery.rb +79 -0
- data/test/test_model/delivery_item.rb +52 -0
- data/test/test_model/freetext_container.rb +45 -0
- data/test/test_model/invoice.rb +65 -0
- data/test/test_model/invoice_item.rb +41 -0
- data/test/test_model/name.rb +57 -0
- data/test/test_model/part_info.rb +24 -0
- data/test/test_model/party.rb +96 -0
- data/test/test_model/price.rb +24 -0
- data/test/test_util/application.rb +168 -0
- data/test/test_util/destination.rb +415 -0
- data/test/test_util/invoicer.rb +42 -0
- data/test/test_util/polling_manager.rb +299 -0
- data/test/test_util/transaction.rb +130 -0
- metadata +178 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# PartyContainer -- xmlconv2 -- 01.06.2004 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
module XmlConv
|
5
|
+
module Model
|
6
|
+
module PartyContainer
|
7
|
+
attr_accessor :employee, :ship_to, :bill_to, :seller, :customer
|
8
|
+
def add_party(party)
|
9
|
+
if((role = party.role) && !role.empty?)
|
10
|
+
role = role.gsub(/\B[A-Z]/, '_\&')
|
11
|
+
instance_variable_set("@#{role.downcase}", party)
|
12
|
+
self.parties.push(party)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
def parties
|
16
|
+
@parties ||= []
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# PriceContainer -- xmlconv2 -- 22.06.2004 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
module XmlConv
|
5
|
+
module Model
|
6
|
+
module PriceContainer
|
7
|
+
def add_price(price)
|
8
|
+
self.prices.push(price)
|
9
|
+
end
|
10
|
+
def get_price(purpose)
|
11
|
+
self.prices.find { |price| price.purpose == purpose }
|
12
|
+
end
|
13
|
+
def prices
|
14
|
+
@prices ||= []
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Model::Transaction -- xmlconv2 -- 23.06.2004 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'xmlconv/model/freetext_container'
|
5
|
+
require 'xmlconv/model/id_container'
|
6
|
+
require 'xmlconv/model/item_container'
|
7
|
+
require 'xmlconv/model/party_container'
|
8
|
+
require 'xmlconv/model/price_container'
|
9
|
+
|
10
|
+
module XmlConv
|
11
|
+
module Model
|
12
|
+
class Transaction
|
13
|
+
include IdContainer
|
14
|
+
include ItemContainer
|
15
|
+
include FreeTextContainer
|
16
|
+
include PartyContainer
|
17
|
+
include PriceContainer
|
18
|
+
attr_accessor :agreement, :status, :status_date,
|
19
|
+
:transport_cost
|
20
|
+
def customer_id
|
21
|
+
self.id_table['customer']
|
22
|
+
end
|
23
|
+
def reference_id
|
24
|
+
self.id_table['acc']
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# State::Global -- xmlconv2 -- 09.06.2004 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'xmlconv/state/login'
|
5
|
+
require 'xmlconv/state/transaction'
|
6
|
+
require 'xmlconv/state/transactions'
|
7
|
+
|
8
|
+
module XmlConv
|
9
|
+
module State
|
10
|
+
class Global < SBSM::State
|
11
|
+
def logout
|
12
|
+
@session.logout
|
13
|
+
Login.new(@session, @model)
|
14
|
+
end
|
15
|
+
def transaction
|
16
|
+
if((id = @session.user_input(:transaction_id)) \
|
17
|
+
&& (transaction = @session.transaction(id)))
|
18
|
+
Transaction.new(@session, transaction)
|
19
|
+
else
|
20
|
+
self
|
21
|
+
end
|
22
|
+
end
|
23
|
+
def home
|
24
|
+
Transactions.new(@session, @session.transactions)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# State::Login -- xmlconv2 -- 09.06.2004 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'sbsm/state'
|
5
|
+
require 'xmlconv/view/login'
|
6
|
+
require 'xmlconv/state/transactions'
|
7
|
+
|
8
|
+
module XmlConv
|
9
|
+
module State
|
10
|
+
class Login < SBSM::State
|
11
|
+
VIEW = View::Login
|
12
|
+
def login
|
13
|
+
if(@session.login)
|
14
|
+
Transactions.new(@session, @session.transactions)
|
15
|
+
else
|
16
|
+
self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
def transaction
|
20
|
+
if((id = @session.user_input(:transaction_id)) \
|
21
|
+
&& (transaction = @session.transaction(id)))
|
22
|
+
TransactionLogin.new(@session, transaction)
|
23
|
+
else
|
24
|
+
self
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
class TransactionLogin < SBSM::State
|
29
|
+
VIEW = View::Login
|
30
|
+
def login
|
31
|
+
if(@session.login)
|
32
|
+
Transaction.new(@session, @model)
|
33
|
+
else
|
34
|
+
self
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# State::Transaction -- xmlconv2 -- 09.06.2004 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'xmlconv/state/global_predefine'
|
5
|
+
require 'xmlconv/view/transaction'
|
6
|
+
|
7
|
+
module XmlConv
|
8
|
+
module State
|
9
|
+
class Transaction < Global
|
10
|
+
VIEW = View::Transaction
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# State::Transactions -- xmlconv2 -- 09.06.2004 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'xmlconv/state/global_predefine'
|
5
|
+
require 'xmlconv/view/transactions'
|
6
|
+
|
7
|
+
module XmlConv
|
8
|
+
module State
|
9
|
+
class Transactions < Global
|
10
|
+
class PageFacade
|
11
|
+
attr_accessor :model, :pages
|
12
|
+
def initialize(int)
|
13
|
+
@int = int
|
14
|
+
end
|
15
|
+
def next
|
16
|
+
PageFacade.new(@int.next)
|
17
|
+
end
|
18
|
+
def pages
|
19
|
+
@pages[[[@int - (PAGER_SIZE / 2), total - PAGER_SIZE].min,
|
20
|
+
0].max, PAGER_SIZE]
|
21
|
+
end
|
22
|
+
def previous
|
23
|
+
PageFacade.new(@int-1)
|
24
|
+
end
|
25
|
+
def total
|
26
|
+
@pages.size
|
27
|
+
end
|
28
|
+
def to_i
|
29
|
+
@int
|
30
|
+
end
|
31
|
+
def to_s
|
32
|
+
@int.next.to_s
|
33
|
+
end
|
34
|
+
end
|
35
|
+
DIRECT_EVENT = :home
|
36
|
+
PAGE_SIZE = 20
|
37
|
+
PAGER_SIZE = 10
|
38
|
+
REVERSE_MAP = {
|
39
|
+
:commit_time => true,
|
40
|
+
}
|
41
|
+
VIEW = View::Transactions
|
42
|
+
def init
|
43
|
+
@transactions = @model
|
44
|
+
@model = @transactions.reverse
|
45
|
+
setup_pages
|
46
|
+
@filter = Proc.new {
|
47
|
+
page
|
48
|
+
}
|
49
|
+
super
|
50
|
+
end
|
51
|
+
def compare_entries(a, b)
|
52
|
+
@sortby.each { |sortby|
|
53
|
+
aval, bval = nil
|
54
|
+
begin
|
55
|
+
aval = a.send(sortby)
|
56
|
+
bval = b.send(sortby)
|
57
|
+
rescue
|
58
|
+
next
|
59
|
+
end
|
60
|
+
res = if(aval.nil? && bval.nil?)
|
61
|
+
0
|
62
|
+
elsif(aval.nil?)
|
63
|
+
1
|
64
|
+
elsif(bval.nil?)
|
65
|
+
-1
|
66
|
+
else
|
67
|
+
aval <=> bval
|
68
|
+
end
|
69
|
+
return res unless(res == 0)
|
70
|
+
}
|
71
|
+
0
|
72
|
+
end
|
73
|
+
def get_sortby!
|
74
|
+
@sortby ||= []
|
75
|
+
sortvalue = @session.user_input(:sortvalue)
|
76
|
+
if(sortvalue.is_a? String)
|
77
|
+
sortvalue = sortvalue.intern
|
78
|
+
end
|
79
|
+
if(@sortby.first == sortvalue)
|
80
|
+
@sort_reverse = !@sort_reverse
|
81
|
+
else
|
82
|
+
@sort_reverse = self.class::REVERSE_MAP[sortvalue]
|
83
|
+
end
|
84
|
+
@sortby.delete_if { |sortby|
|
85
|
+
sortby == sortvalue
|
86
|
+
}
|
87
|
+
@sortby.unshift(sortvalue)
|
88
|
+
end
|
89
|
+
def page
|
90
|
+
if(pge = @session.user_input(:page))
|
91
|
+
@page = @pages[pge.to_i]
|
92
|
+
else
|
93
|
+
@page ||= @pages.first
|
94
|
+
end
|
95
|
+
end
|
96
|
+
def page_size
|
97
|
+
self::class::PAGE_SIZE
|
98
|
+
end
|
99
|
+
def self
|
100
|
+
self
|
101
|
+
end
|
102
|
+
def setup_pages
|
103
|
+
@pages = []
|
104
|
+
@page = nil
|
105
|
+
pages = [(@model.size / PAGE_SIZE.to_f).ceil, 1].max
|
106
|
+
pages.times { |pnum|
|
107
|
+
page = PageFacade.new(pnum)
|
108
|
+
page.model = @model[pnum * PAGE_SIZE, PAGE_SIZE]
|
109
|
+
page.pages = @pages
|
110
|
+
@pages.push(page)
|
111
|
+
}
|
112
|
+
end
|
113
|
+
def size
|
114
|
+
@model.size
|
115
|
+
end
|
116
|
+
def sort
|
117
|
+
return self unless @model.is_a? Array
|
118
|
+
get_sortby!
|
119
|
+
@model = @transactions.dup
|
120
|
+
@model.sort! { |a, b| compare_entries(a, b) }
|
121
|
+
@model.reverse! if(@sort_reverse)
|
122
|
+
setup_pages
|
123
|
+
self
|
124
|
+
end
|
125
|
+
def transactions
|
126
|
+
self
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# XmlConv::Application -- xmlconv2 -- 07.06.2004 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
require 'sbsm/drbserver'
|
5
|
+
require 'xmlconv/state/global'
|
6
|
+
require 'xmlconv/util/invoicer'
|
7
|
+
require 'xmlconv/util/polling_manager'
|
8
|
+
require 'xmlconv/util/session'
|
9
|
+
require 'xmlconv/util/transaction'
|
10
|
+
require 'xmlconv/util/validator'
|
11
|
+
require 'thread'
|
12
|
+
require 'odba'
|
13
|
+
|
14
|
+
module XmlConv
|
15
|
+
module Util
|
16
|
+
class Application
|
17
|
+
attr_reader :transactions, :failed_transactions
|
18
|
+
include ODBA::Persistable
|
19
|
+
ODBA_EXCLUDE_VARS = ['@next_transaction_id', '@id_mutex']
|
20
|
+
def initialize
|
21
|
+
@transactions = []
|
22
|
+
@failed_transactions = []
|
23
|
+
end
|
24
|
+
def init
|
25
|
+
@id_mutex = Mutex.new
|
26
|
+
end
|
27
|
+
def execute(transaction)
|
28
|
+
_execute(transaction)
|
29
|
+
transaction.notify
|
30
|
+
rescue Exception => error
|
31
|
+
## survive notification failure
|
32
|
+
end
|
33
|
+
def _execute(transaction)
|
34
|
+
transaction.transaction_id = next_transaction_id
|
35
|
+
transaction.execute
|
36
|
+
transaction.postprocess
|
37
|
+
rescue Exception => error
|
38
|
+
transaction.error = error
|
39
|
+
ensure
|
40
|
+
@transactions.push(transaction)
|
41
|
+
ODBA.transaction {
|
42
|
+
@transactions.odba_store
|
43
|
+
}
|
44
|
+
end
|
45
|
+
def next_transaction_id
|
46
|
+
@id_mutex.synchronize {
|
47
|
+
@next_transaction_id ||= @transactions.collect { |transaction|
|
48
|
+
transaction.transaction_id.to_i
|
49
|
+
}.max.to_i
|
50
|
+
@next_transaction_id += 1
|
51
|
+
}
|
52
|
+
end
|
53
|
+
def transaction(transaction_id)
|
54
|
+
transaction_id = transaction_id.to_i
|
55
|
+
if((last_id = @transactions.last.transaction_id) \
|
56
|
+
&& (last_id >= transaction_id))
|
57
|
+
start = (transaction_id - last_id - 1)
|
58
|
+
if(start + @transactions.size < 0)
|
59
|
+
start = 0
|
60
|
+
end
|
61
|
+
@transactions[start..-1].each { |trans|
|
62
|
+
return trans if(trans.transaction_id == transaction_id)
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
def send_invoice(time_range, date = Date.today)
|
67
|
+
transactions = @transactions.select { |trans|
|
68
|
+
time_range.include?(trans.commit_time)
|
69
|
+
}
|
70
|
+
Util::Invoicer.run(time_range, transactions, date)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class XmlConvApp < SBSM::DRbServer
|
77
|
+
ENABLE_ADMIN = true
|
78
|
+
SESSION = XmlConv::Util::Session
|
79
|
+
VALIDATOR = XmlConv::Util::Validator
|
80
|
+
POLLING_INTERVAL = 60 #* 15
|
81
|
+
attr_reader :polling_thread, :dispatch_queue, :dispatcher_thread
|
82
|
+
def initialize
|
83
|
+
@system = ODBA.cache.fetch_named('XmlConv', self) {
|
84
|
+
XmlConv::Util::Application.new
|
85
|
+
}
|
86
|
+
@system.init
|
87
|
+
@dispatch_queue = Queue.new
|
88
|
+
if(self::class::POLLING_INTERVAL)
|
89
|
+
start_polling
|
90
|
+
end
|
91
|
+
start_dispatcher
|
92
|
+
start_invoicer if XmlConv::CONFIG.run_invoicer
|
93
|
+
super(@system)
|
94
|
+
end
|
95
|
+
def dispatch(transaction)
|
96
|
+
@dispatch_queue.push(transaction)
|
97
|
+
end
|
98
|
+
def execute_with_response(transaction)
|
99
|
+
begin
|
100
|
+
@system.execute(transaction)
|
101
|
+
rescue Exception => e
|
102
|
+
puts "rescued #{e.class}"
|
103
|
+
end
|
104
|
+
transaction.response.to_s
|
105
|
+
end
|
106
|
+
def start_dispatcher
|
107
|
+
@dispatcher_thread = Thread.new {
|
108
|
+
Thread.current.abort_on_exception = true
|
109
|
+
loop {
|
110
|
+
@system.execute(@dispatch_queue.pop)
|
111
|
+
}
|
112
|
+
}
|
113
|
+
end
|
114
|
+
def start_invoicer
|
115
|
+
@invoicer_thread = Thread.new {
|
116
|
+
Thread.current.abort_on_exception = true
|
117
|
+
loop {
|
118
|
+
this_month = Date.today
|
119
|
+
next_month = this_month >> 1
|
120
|
+
strt = Time.local(this_month.year, this_month.month)
|
121
|
+
stop = Time.local(next_month.year, next_month.month)
|
122
|
+
sleep(stop - Time.now)
|
123
|
+
@system.send_invoice(strt...stop)
|
124
|
+
}
|
125
|
+
}
|
126
|
+
end
|
127
|
+
def start_polling
|
128
|
+
@polling_thread = Thread.new {
|
129
|
+
Thread.current.abort_on_exception = true
|
130
|
+
loop {
|
131
|
+
begin
|
132
|
+
XmlConv::Util::PollingManager.new(@system).poll_sources
|
133
|
+
rescue Exception => exc
|
134
|
+
XmlConv::LOGGER.error(XmlConv::CONFIG.program_name) {
|
135
|
+
[exc.class, exc.message].concat(exc.backtrace).join("\n")
|
136
|
+
}
|
137
|
+
end
|
138
|
+
sleep(self::class::POLLING_INTERVAL)
|
139
|
+
}
|
140
|
+
}
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Util#autoload -- xmlconv2 -- 28.08.2006 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
module XmlConv
|
5
|
+
module Util
|
6
|
+
def Util.autoload(dir, type)
|
7
|
+
config = XmlConv::CONFIG
|
8
|
+
logger = XmlConv::LOGGER
|
9
|
+
dir = File.expand_path(dir)
|
10
|
+
prefix = File.basename(dir)
|
11
|
+
search_path = File.dirname(dir)
|
12
|
+
$:.push(search_path) unless $:.include?(search_path)
|
13
|
+
logger.debug(config.program_name) {
|
14
|
+
"checking directory '#{dir}' for #{type}s"
|
15
|
+
}
|
16
|
+
Dir.glob(File.join(dir, '*')) { |entry|
|
17
|
+
if(/\.(rb|so)$/.match(entry))
|
18
|
+
keyword = File.basename(entry)
|
19
|
+
keyword.slice!(/#{File.extname(keyword)}$/)
|
20
|
+
rpath = File.join(prefix, keyword)
|
21
|
+
logger.debug(config.program_name) {
|
22
|
+
"loading #{type}: '#{rpath}' (#{File.basename(entry)})"
|
23
|
+
}
|
24
|
+
begin
|
25
|
+
require rpath #File.basename(keyword)
|
26
|
+
rescue
|
27
|
+
logger.warn(config.program_name) {
|
28
|
+
"loading #{type} '#{rpath}' failed!"
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|