line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Finance::Bank::Postbank_de::Account; |
2
|
14
|
|
|
14
|
|
5258
|
use 5.006; |
|
14
|
|
|
|
|
50
|
|
3
|
14
|
|
|
14
|
|
71
|
use strict; |
|
14
|
|
|
|
|
33
|
|
|
14
|
|
|
|
|
289
|
|
4
|
14
|
|
|
14
|
|
64
|
use warnings; |
|
14
|
|
|
|
|
29
|
|
|
14
|
|
|
|
|
438
|
|
5
|
14
|
|
|
14
|
|
79
|
use Carp qw(croak); |
|
14
|
|
|
|
|
29
|
|
|
14
|
|
|
|
|
756
|
|
6
|
14
|
|
|
14
|
|
2479
|
use POSIX qw(strftime); |
|
14
|
|
|
|
|
31150
|
|
|
14
|
|
|
|
|
75
|
|
7
|
14
|
|
|
14
|
|
10572
|
use Moo 2; |
|
14
|
|
|
|
|
56641
|
|
|
14
|
|
|
|
|
85
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
our $VERSION = '0.57'; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
has [ |
12
|
|
|
|
|
|
|
'number', |
13
|
|
|
|
|
|
|
'balance', |
14
|
|
|
|
|
|
|
'balance_unavailable', |
15
|
|
|
|
|
|
|
'balance_prev', |
16
|
|
|
|
|
|
|
'transactions_future', |
17
|
|
|
|
|
|
|
'iban', |
18
|
|
|
|
|
|
|
'blz', |
19
|
|
|
|
|
|
|
'account_type', |
20
|
|
|
|
|
|
|
'name', |
21
|
|
|
|
|
|
|
] => ( is => 'rw' ); |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
around BUILDARGS => sub { |
24
|
|
|
|
|
|
|
my ($orig, $class,%args) = @_; |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
if( exists $args{ number } and exists $args{ kontonummer } |
27
|
|
|
|
|
|
|
and $args{ number } ne $args{ kontonummer } ) { |
28
|
|
|
|
|
|
|
croak "'kontonummer' is '$args{kontonummer}' and 'number' is '$args{ number }'"; |
29
|
|
|
|
|
|
|
}; |
30
|
|
|
|
|
|
|
my $num = delete $args{number} || delete $args{kontonummer}; |
31
|
|
|
|
|
|
|
$args{ number } = $num |
32
|
|
|
|
|
|
|
if defined $num; |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
$orig->( $class, %args ); |
35
|
|
|
|
|
|
|
}; |
36
|
|
|
|
|
|
|
|
37
|
14
|
|
|
14
|
|
12589
|
{ no warnings 'once'; |
|
14
|
|
|
|
|
30
|
|
|
14
|
|
|
|
|
33743
|
|
38
|
|
|
|
|
|
|
*kontonummer = *number; |
39
|
|
|
|
|
|
|
} |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
our %safety_check = ( |
42
|
|
|
|
|
|
|
name => 1, |
43
|
|
|
|
|
|
|
kontonummer => 1, |
44
|
|
|
|
|
|
|
); |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
our %tags = ( |
47
|
|
|
|
|
|
|
#Girokonto => [qw(Name BLZ Kontonummer IBAN)], |
48
|
|
|
|
|
|
|
"gebuchte Ums\x{00E4}tze" => [qw(Name BLZ Kontonummer IBAN)], |
49
|
|
|
|
|
|
|
Tagesgeldkonto => [qw(Name BLZ Kontonummer IBAN)], |
50
|
|
|
|
|
|
|
Sparcard => [qw(Name BLZ Kontonummer IBAN)], |
51
|
|
|
|
|
|
|
Sparkonto => [qw(Name BLZ Kontonummer IBAN)], |
52
|
|
|
|
|
|
|
Kreditkarte => [qw(Name BLZ Kontonummer IBAN)], |
53
|
|
|
|
|
|
|
'' => [qw(Name BLZ Kontonummer IBAN)], |
54
|
|
|
|
|
|
|
); |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
our %totals = ( |
57
|
|
|
|
|
|
|
"gebuchte Ums\x{00E4}tze" => [ |
58
|
|
|
|
|
|
|
[qr'^Aktueller Kontostand' => 'balance'], |
59
|
|
|
|
|
|
|
[qr'^Summe vorgemerkter Ums.tze' => 'transactions_future'], |
60
|
|
|
|
|
|
|
[qr'^Davon noch nicht verf.gbar' => 'balance_unavailable'], |
61
|
|
|
|
|
|
|
], |
62
|
|
|
|
|
|
|
Sparcard => [[qr'Aktueller Kontostand' => 'balance'],], |
63
|
|
|
|
|
|
|
Sparkonto => [[qr'Aktueller Kontostand' => 'balance'],], |
64
|
|
|
|
|
|
|
Tagesgeldkonto => [[qr'Aktueller Kontostand' => 'balance'],], |
65
|
|
|
|
|
|
|
"" => [ |
66
|
|
|
|
|
|
|
[qr'^Aktueller Kontostand' => 'balance'], |
67
|
|
|
|
|
|
|
[qr'^Summe der Ums.tze in den n.chsten 14 Tagen' => 'transactions_future'], |
68
|
|
|
|
|
|
|
[qr'^Davon noch nicht verf.gbar' => 'balance_unavailable'], |
69
|
|
|
|
|
|
|
], |
70
|
|
|
|
|
|
|
); |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
our %columns = ( |
73
|
|
|
|
|
|
|
qr'Buchungsdatum' => 'tradedate', |
74
|
|
|
|
|
|
|
qr'Wertstellung' => 'valuedate', |
75
|
|
|
|
|
|
|
qr'Umsatzart' => 'type', |
76
|
|
|
|
|
|
|
qr'Buchungsdetails' => 'comment', |
77
|
|
|
|
|
|
|
qr'Auftraggeber' => 'sender', |
78
|
|
|
|
|
|
|
qr'Empf.nger' => 'receiver', |
79
|
|
|
|
|
|
|
qr"Betrag \((?:\x{20AC}|\x{80})\)" => 'amount', |
80
|
|
|
|
|
|
|
qr"Saldo \((?:\x{20AC}|\x{80})\)" => 'running_total', |
81
|
|
|
|
|
|
|
); |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
sub parse_date { |
84
|
367
|
|
|
367
|
0
|
4480
|
my ($self,$date) = @_; |
85
|
367
|
100
|
|
|
|
1294
|
$date =~ /^(\d{2})\.(\d{2})\.(\d{4})$/ |
86
|
|
|
|
|
|
|
or die "Unknown date format '$date'. A date must be in the format 'DD.MM.YYYY'\n"; |
87
|
364
|
|
|
|
|
1339
|
$3.$2.$1; |
88
|
|
|
|
|
|
|
}; |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
sub parse_amount { |
91
|
404
|
|
|
404
|
0
|
7091
|
my ($self,$amount) = @_; |
92
|
|
|
|
|
|
|
# '¿ 5.314,05' |
93
|
404
|
100
|
|
|
|
2036
|
die "String '$amount' does not look like a number" |
94
|
|
|
|
|
|
|
unless $amount =~ /^(-?)(?:\s*\x{20AC}\s*|\s*\x{80}\s*|\s*\x{A4}\s*)?([0-9]{1,3}(?:\.\d{3})*,\d{2})(?:\s*\x{20AC}|\s*\x{80})?$/; |
95
|
401
|
|
100
|
|
|
1509
|
$amount = ($1||'') . $2; |
96
|
401
|
|
|
|
|
821
|
$amount =~ tr/.//d; |
97
|
401
|
|
|
|
|
1281
|
$amount =~ s/,/./; |
98
|
401
|
|
|
|
|
1302
|
$amount; |
99
|
|
|
|
|
|
|
}; |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
sub slurp_file { |
102
|
3
|
|
|
3
|
0
|
6
|
my ($self,$filename) = @_; |
103
|
3
|
|
|
|
|
13
|
local $/ = undef; |
104
|
3
|
50
|
|
|
|
154
|
open my $fh, "< $filename" |
105
|
|
|
|
|
|
|
or croak "Couldn't read from file '$filename' : $!"; |
106
|
3
|
|
|
|
|
51
|
binmode $fh, ':encoding(UTF-8)'; |
107
|
3
|
|
|
|
|
288
|
<$fh>; |
108
|
|
|
|
|
|
|
}; |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
sub parse_statement { |
111
|
27
|
|
|
27
|
1
|
47813
|
my ($self,%args) = @_; |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
# If $self is just a string, we want to make a new class out of us |
114
|
27
|
100
|
|
|
|
626
|
$self = $self->new |
115
|
|
|
|
|
|
|
unless ref $self; |
116
|
27
|
|
|
|
|
678
|
my $filename = $args{file}; |
117
|
27
|
|
|
|
|
74
|
my $raw_statement = $args{content}; |
118
|
27
|
100
|
|
|
|
125
|
if ($filename) { |
|
|
100
|
|
|
|
|
|
119
|
4
|
|
|
|
|
12
|
$raw_statement = $self->slurp_file($filename); |
120
|
|
|
|
|
|
|
} elsif (! defined $raw_statement) { |
121
|
|
|
|
|
|
|
croak "Need an account number if I have to retrieve the statement online" |
122
|
4
|
100
|
|
|
|
204
|
unless $args{number}; |
123
|
|
|
|
|
|
|
croak "Need a password if I have to retrieve the statement online" |
124
|
3
|
100
|
|
|
|
122
|
unless exists $args{password}; |
125
|
2
|
|
66
|
|
|
16
|
my $login = $args{login} || $args{number}; |
126
|
|
|
|
|
|
|
|
127
|
2
|
|
|
|
|
10
|
require Finance::Bank::Postbank_de; |
128
|
2
|
|
|
|
|
13
|
return Finance::Bank::Postbank_de->new( login => $login, password => $args{password}, past_days => $args{past_days} )->get_account_statement; |
129
|
|
|
|
|
|
|
}; |
130
|
|
|
|
|
|
|
|
131
|
22
|
100
|
|
|
|
342
|
croak "Don't know what to do with empty content" |
132
|
|
|
|
|
|
|
unless $raw_statement; |
133
|
|
|
|
|
|
|
|
134
|
21
|
|
|
|
|
814
|
my @lines = split /\r?\n/, $raw_statement; |
135
|
21
|
100
|
|
|
|
234
|
croak "No valid account statement: '$lines[0]'" |
136
|
|
|
|
|
|
|
unless $lines[0] =~ /^Umsatzauskunft;$/; |
137
|
20
|
|
|
|
|
76
|
shift @lines; |
138
|
|
|
|
|
|
|
|
139
|
20
|
|
|
|
|
56
|
my $account_type = ''; |
140
|
|
|
|
|
|
|
#my $account_type = $1; |
141
|
|
|
|
|
|
|
#if( ! exists $tags{ $account_type }) { |
142
|
|
|
|
|
|
|
# $account_type =~ s!([^\x00-\x7f])!sprintf '%08x', ord($1)!ge; |
143
|
|
|
|
|
|
|
# croak "Unknown account type '$account_type' (" . (join ",",keys %tags) . ")" |
144
|
|
|
|
|
|
|
# unless exists $tags{$account_type}; |
145
|
|
|
|
|
|
|
#}; |
146
|
|
|
|
|
|
|
#$self->account_type($account_type); |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
# Name: PETRA PFIFFIG |
149
|
|
|
|
|
|
|
#for my $tag (@{ $tags{ $self->account_type }||[] }) { |
150
|
20
|
|
|
|
|
66
|
my $sep = ";"; |
151
|
20
|
100
|
|
|
|
96
|
if( $lines[0] =~ /([\t;])/) { |
152
|
19
|
|
|
|
|
62
|
$sep = $1; |
153
|
|
|
|
|
|
|
}; |
154
|
20
|
50
|
|
|
|
44
|
for my $tag (@{ $tags{ $account_type }||[] }) { |
|
20
|
|
|
|
|
119
|
|
155
|
73
|
100
|
|
|
|
2145
|
$lines[0] =~ /^\Q$tag\E$sep(.*?)$sep?$/ |
156
|
|
|
|
|
|
|
or croak "Field '$tag' not found in account statement ($lines[0])"; |
157
|
69
|
|
|
|
|
204
|
my $method = lc($tag); |
158
|
69
|
|
|
|
|
204
|
my $value = $1; |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
# special check for special fields: |
161
|
|
|
|
|
|
|
croak "Wrong/mixed account $method: Got '$value', expected '" . $self->$method . "'" |
162
|
69
|
100
|
100
|
|
|
605
|
if (exists $safety_check{$method} and defined $self->$method and $self->$method ne $value); |
|
|
|
100
|
|
|
|
|
163
|
|
|
|
|
|
|
|
164
|
68
|
|
|
|
|
259
|
$self->$method($value); |
165
|
68
|
|
|
|
|
189
|
shift @lines; |
166
|
|
|
|
|
|
|
}; |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
|
169
|
15
|
|
|
|
|
122
|
while ($lines[0] !~ /^\s*$/) { |
170
|
30
|
|
|
|
|
77
|
my $line = shift @lines; |
171
|
30
|
|
|
|
|
67
|
my ($method,$balance); |
172
|
30
|
50
|
|
|
|
61
|
for my $total (@{ $totals{ $account_type }||[] }) { |
|
30
|
|
|
|
|
138
|
|
173
|
90
|
|
|
|
|
265
|
my ($re,$possible_method) = @$total; |
174
|
90
|
100
|
|
|
|
3170
|
if ($line =~ /$re$sep\s*(?:(?:(\S+)\s*(?:\x{20AC}|\x{80}))|(null))$sep$/) { |
175
|
30
|
|
|
|
|
95
|
$method = $possible_method; |
176
|
30
|
|
33
|
|
|
131
|
$balance = $1 || $2; |
177
|
30
|
50
|
|
|
|
166
|
if ($balance =~ /^(-?[0-9.,]+)\s*$/) { |
|
|
0
|
|
|
|
|
|
178
|
30
|
|
|
|
|
145
|
$self->$method( ['????????',$self->parse_amount($balance)]); |
179
|
|
|
|
|
|
|
} elsif ('null' eq $balance) { |
180
|
0
|
|
|
|
|
0
|
$self->$method( ['????????',$self->parse_amount("0,00")]); |
181
|
|
|
|
|
|
|
} else { |
182
|
0
|
|
|
|
|
0
|
die "Invalid number '$balance' found for $method in '$line'"; |
183
|
|
|
|
|
|
|
}; |
184
|
|
|
|
|
|
|
}; |
185
|
|
|
|
|
|
|
}; |
186
|
30
|
50
|
|
|
|
210
|
if (! $method) { |
187
|
0
|
|
|
|
|
0
|
$account_type =~ s!([^\x00-\x7f])!sprintf '%08x', ord($1)!ge; |
|
0
|
|
|
|
|
0
|
|
188
|
0
|
|
|
|
|
0
|
$line =~ s!([^\x00-\x7f])!sprintf '%08x', ord($1)!ge; |
|
0
|
|
|
|
|
0
|
|
189
|
0
|
|
|
|
|
0
|
croak "No summary found in account '$account_type' statement ($line)"; |
190
|
|
|
|
|
|
|
}; |
191
|
|
|
|
|
|
|
}; |
192
|
|
|
|
|
|
|
|
193
|
15
|
50
|
|
|
|
126
|
$lines[0] =~ m!^\s*$! |
194
|
|
|
|
|
|
|
or croak "Expected an empty line after the account balances, got '$lines[0]'"; |
195
|
15
|
|
|
|
|
38
|
shift @lines; |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
# Now skip over the transactions in the future |
198
|
15
|
|
|
|
|
82
|
while( $lines[0] !~ /^gebuchte Ums.tze/ ) { |
199
|
105
|
|
|
|
|
256
|
shift @lines; |
200
|
|
|
|
|
|
|
}; |
201
|
15
|
|
|
|
|
37
|
shift @lines; |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
# Now parse the lines for each cashflow : |
204
|
15
|
50
|
|
|
|
156
|
$lines[0] =~ /^Buchungsdatum${sep}Wertstellung${sep}Umsatzart/ |
205
|
|
|
|
|
|
|
or croak "Couldn't find start of transactions ($lines[0])"; |
206
|
|
|
|
|
|
|
|
207
|
15
|
|
|
|
|
90
|
my (@fields); |
208
|
|
|
|
|
|
|
COLUMN: |
209
|
15
|
|
|
|
|
149
|
for my $col (split /$sep/, $lines[0]) { |
210
|
120
|
|
|
|
|
441
|
for my $target (keys %columns) { |
211
|
540
|
100
|
|
|
|
10655
|
if ($col =~ m!^["']?$target["']?$!) { |
212
|
120
|
|
|
|
|
467
|
push @fields, $columns{$target}; |
213
|
120
|
|
|
|
|
392
|
next COLUMN; |
214
|
|
|
|
|
|
|
}; |
215
|
|
|
|
|
|
|
}; |
216
|
0
|
|
|
|
|
0
|
die "Unknown column '$col' in '$lines[0]'"; |
217
|
|
|
|
|
|
|
}; |
218
|
15
|
|
|
|
|
60
|
shift @lines; |
219
|
|
|
|
|
|
|
|
220
|
15
|
|
|
|
|
140
|
my (%convert) = ( |
221
|
|
|
|
|
|
|
tradedate => \&parse_date, |
222
|
|
|
|
|
|
|
valuedate => \&parse_date, |
223
|
|
|
|
|
|
|
amount => \&parse_amount, |
224
|
|
|
|
|
|
|
running_total => \&parse_amount, |
225
|
|
|
|
|
|
|
); |
226
|
|
|
|
|
|
|
|
227
|
15
|
|
|
|
|
41
|
my @transactions; |
228
|
|
|
|
|
|
|
my $line; |
229
|
15
|
|
|
|
|
47
|
for $line (@lines) { |
230
|
180
|
50
|
|
|
|
590
|
next if $line =~ /^\s*$/; |
231
|
180
|
|
|
|
|
897
|
my (@row) = split /$sep/, $line; |
232
|
180
|
50
|
|
|
|
450
|
scalar @row == scalar @fields |
233
|
|
|
|
|
|
|
or die "Malformed cashflow ($line): Expected ".scalar(@fields)." entries, got ".scalar(@row); |
234
|
|
|
|
|
|
|
|
235
|
180
|
|
|
|
|
363
|
for (@row) { |
236
|
1440
|
50
|
|
|
|
3448
|
$_ = $1 |
237
|
|
|
|
|
|
|
if /^\s*["']\s*(.*?)\s*["']\s*$/; |
238
|
|
|
|
|
|
|
}; |
239
|
|
|
|
|
|
|
|
240
|
180
|
|
|
|
|
276
|
my (%rec); |
241
|
180
|
|
|
|
|
998
|
@rec{@fields} = @row; |
242
|
180
|
|
|
|
|
507
|
for (keys %convert) { |
243
|
720
|
|
|
|
|
1533
|
$rec{$_} = $convert{$_}->($self,$rec{$_}); |
244
|
|
|
|
|
|
|
}; |
245
|
|
|
|
|
|
|
|
246
|
180
|
|
|
|
|
591
|
push @transactions, \%rec; |
247
|
|
|
|
|
|
|
}; |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
# Filter the transactions |
250
|
15
|
|
|
|
|
92
|
$self->{transactions} = \@transactions; |
251
|
|
|
|
|
|
|
|
252
|
15
|
|
|
|
|
225
|
$self |
253
|
|
|
|
|
|
|
}; |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
sub transactions { |
256
|
84
|
|
|
84
|
1
|
108799
|
my ($self,%args) = @_; |
257
|
|
|
|
|
|
|
|
258
|
84
|
|
|
|
|
179
|
my ($start_date,$end_date); |
259
|
84
|
100
|
|
|
|
202
|
if (exists $args{on}) { |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
croak "Options 'since'+'upto' and 'on' are incompatible" |
262
|
33
|
100
|
100
|
|
|
299
|
if (exists $args{since} and exists $args{upto}); |
263
|
|
|
|
|
|
|
croak "Options 'since' and 'on' are incompatible" |
264
|
32
|
100
|
|
|
|
152
|
if (exists $args{since}); |
265
|
|
|
|
|
|
|
croak "Options 'upto' and 'on' are incompatible" |
266
|
31
|
100
|
|
|
|
146
|
if (exists $args{upto}); |
267
|
|
|
|
|
|
|
$args{on} = strftime('%Y%m%d',localtime()) |
268
|
30
|
100
|
|
|
|
183
|
if ($args{on} eq 'today'); |
269
|
30
|
50
|
|
|
|
145
|
$args{on} =~ /^\d{8}$/ or croak "Argument {on => '$args{on}'} dosen't look like a date to me."; |
270
|
|
|
|
|
|
|
|
271
|
30
|
|
|
|
|
58
|
$start_date = $args{on} -1; |
272
|
30
|
|
|
|
|
52
|
$end_date = $args{on}; |
273
|
|
|
|
|
|
|
} else { |
274
|
51
|
|
100
|
|
|
151
|
$start_date = $args{since} || "00000000"; |
275
|
51
|
|
100
|
|
|
166
|
$end_date = $args{upto} || "99999999"; |
276
|
51
|
100
|
|
|
|
820
|
$start_date =~ /^\d{8}$/ or croak "Argument {since => '$start_date'} dosen't look like a date to me."; |
277
|
44
|
100
|
|
|
|
715
|
$end_date =~ /^\d{8}$/ or croak "Argument {upto => '$end_date'} dosen't look like a date to me."; |
278
|
37
|
100
|
|
|
|
276
|
$start_date < $end_date or croak "The 'since' argument must be less than the 'upto' argument"; |
279
|
|
|
|
|
|
|
}; |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
# Filter the transactions |
282
|
65
|
100
|
|
|
|
95
|
grep { $_->{tradedate} > $start_date and $_->{tradedate} <= $end_date } @{$self->{transactions}}; |
|
780
|
|
|
|
|
2182
|
|
|
65
|
|
|
|
|
177
|
|
283
|
|
|
|
|
|
|
}; |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
sub value_dates { |
286
|
1
|
|
|
1
|
1
|
7
|
my ($self) = @_; |
287
|
1
|
|
|
|
|
2
|
my %dates; |
288
|
1
|
|
|
|
|
3
|
$dates{$_->{valuedate}} = 1 for $self->transactions(); |
289
|
1
|
|
|
|
|
22
|
sort keys %dates; |
290
|
|
|
|
|
|
|
}; |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
sub trade_dates { |
293
|
1
|
|
|
1
|
1
|
1319
|
my ($self) = @_; |
294
|
1
|
|
|
|
|
1
|
my %dates; |
295
|
1
|
|
|
|
|
3
|
$dates{$_->{tradedate}} = 1 for $self->transactions(); |
296
|
1
|
|
|
|
|
12
|
sort keys %dates; |
297
|
|
|
|
|
|
|
}; |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
1; |
300
|
|
|
|
|
|
|
__END__ |