| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
# -*- cperl; cperl-indent-level: 4 -*- |
|
2
|
|
|
|
|
|
|
# Copyright (C) 2008-2020, Roland van Ipenburg |
|
3
|
|
|
|
|
|
|
package Date::Extract::P800Picture v1.1.6; |
|
4
|
|
|
|
|
|
|
|
|
5
|
8
|
|
|
8
|
|
1449905
|
use strict; |
|
|
8
|
|
|
|
|
83
|
|
|
|
8
|
|
|
|
|
244
|
|
|
6
|
8
|
|
|
8
|
|
43
|
use warnings; |
|
|
8
|
|
|
|
|
17
|
|
|
|
8
|
|
|
|
|
208
|
|
|
7
|
|
|
|
|
|
|
|
|
8
|
8
|
|
|
8
|
|
43
|
use utf8; |
|
|
8
|
|
|
|
|
16
|
|
|
|
8
|
|
|
|
|
78
|
|
|
9
|
8
|
|
|
8
|
|
342
|
use 5.014000; |
|
|
8
|
|
|
|
|
29
|
|
|
10
|
|
|
|
|
|
|
|
|
11
|
8
|
|
|
8
|
|
5108
|
use Moose; |
|
|
8
|
|
|
|
|
3901517
|
|
|
|
8
|
|
|
|
|
61
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
8
|
|
|
8
|
|
62856
|
use POSIX (); |
|
|
8
|
|
|
|
|
21
|
|
|
|
8
|
|
|
|
|
298
|
|
|
14
|
8
|
|
|
8
|
|
6705
|
use English qw( -no_match_vars); |
|
|
8
|
|
|
|
|
32737
|
|
|
|
8
|
|
|
|
|
56
|
|
|
15
|
8
|
|
|
8
|
|
10059
|
use DateTime (); |
|
|
8
|
|
|
|
|
3520078
|
|
|
|
8
|
|
|
|
|
448
|
|
|
16
|
|
|
|
|
|
|
|
|
17
|
8
|
|
|
8
|
|
4602
|
use Date::Extract::P800Picture::Exceptions (); |
|
|
8
|
|
|
|
|
27
|
|
|
|
8
|
|
|
|
|
193
|
|
|
18
|
|
|
|
|
|
|
|
|
19
|
8
|
|
|
8
|
|
4797
|
use Readonly (); |
|
|
8
|
|
|
|
|
33570
|
|
|
|
8
|
|
|
|
|
5156
|
|
|
20
|
|
|
|
|
|
|
## no critic (ProhibitCallsToUnexportedSubs) |
|
21
|
|
|
|
|
|
|
Readonly::Scalar my $EPOCH_YEAR => 2000; |
|
22
|
|
|
|
|
|
|
Readonly::Scalar my $MONTHS_IN_YEAR => 12; |
|
23
|
|
|
|
|
|
|
Readonly::Scalar my $MAX_DAYS_IN_MONTH => 31; |
|
24
|
|
|
|
|
|
|
Readonly::Scalar my $HOURS_IN_DAY => 24; |
|
25
|
|
|
|
|
|
|
Readonly::Scalar my $BASE_N => 36; |
|
26
|
|
|
|
|
|
|
Readonly::Scalar my $TZ => 'UTC'; |
|
27
|
|
|
|
|
|
|
Readonly::Hash my %ERR => ( |
|
28
|
|
|
|
|
|
|
'MISSING_DATE' => q{No date found in filename '%s'}, |
|
29
|
|
|
|
|
|
|
'MISSING_FILENAME' => q{Filename is not set, nothing to extract}, |
|
30
|
|
|
|
|
|
|
); |
|
31
|
|
|
|
|
|
|
## use critic |
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
## no critic (ProhibitComplexRegexes) |
|
34
|
|
|
|
|
|
|
my $PATTERN = qr{ |
|
35
|
|
|
|
|
|
|
^ |
|
36
|
|
|
|
|
|
|
(?<year> [[:alnum:]] ) # max 36 years: $EPOCH_YEAR 2000 to 2035 |
|
37
|
|
|
|
|
|
|
(?<month> [[:digit:]AB] ) # max 12 months |
|
38
|
|
|
|
|
|
|
(?<day> [[:digit:]A-U]) # max 31 days |
|
39
|
|
|
|
|
|
|
(?<hour> [[:digit:]A-N]) # max 24 hours: 0 to 23 |
|
40
|
|
|
|
|
|
|
(?<serial> [[:digit:]]{4}) # max unique up to 10000 pictures per hour |
|
41
|
|
|
|
|
|
|
(?<suffix> [.]JPG ) # JPEG extension |
|
42
|
|
|
|
|
|
|
$ |
|
43
|
|
|
|
|
|
|
}aixsm; |
|
44
|
|
|
|
|
|
|
## use critic |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
## no critic qw(ProhibitCallsToUndeclaredSubs) |
|
47
|
|
|
|
|
|
|
has 'filename' => ( |
|
48
|
|
|
|
|
|
|
## use critic |
|
49
|
|
|
|
|
|
|
'is' => 'rw', |
|
50
|
|
|
|
|
|
|
'isa' => 'Str', |
|
51
|
|
|
|
|
|
|
); |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
## no critic qw(ProhibitCallsToUndeclaredSubs) |
|
54
|
|
|
|
|
|
|
has 'datetime' => ( |
|
55
|
|
|
|
|
|
|
## use critic |
|
56
|
|
|
|
|
|
|
'is' => 'rw', |
|
57
|
|
|
|
|
|
|
'isa' => 'DateTime', |
|
58
|
|
|
|
|
|
|
'default' => sub { |
|
59
|
|
|
|
|
|
|
DateTime->new( |
|
60
|
|
|
|
|
|
|
'year' => $EPOCH_YEAR, |
|
61
|
|
|
|
|
|
|
'time_zone' => $TZ, |
|
62
|
|
|
|
|
|
|
); |
|
63
|
|
|
|
|
|
|
}, |
|
64
|
|
|
|
|
|
|
); |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
sub extract { |
|
67
|
2454
|
|
|
2454
|
1
|
1754305
|
my ( $self, $filename ) = @_; |
|
68
|
2454
|
100
|
|
|
|
97511
|
( defined $filename ) && $self->filename($filename); |
|
69
|
2454
|
100
|
|
|
|
71998
|
if ( defined $self->filename ) { |
|
70
|
2453
|
|
|
|
|
69753
|
$self->filename =~ $PATTERN; |
|
71
|
|
|
|
|
|
|
my ( $year, $month, $day, $hour ) = ( |
|
72
|
|
|
|
|
|
|
$LAST_PAREN_MATCH{'year'}, $LAST_PAREN_MATCH{'month'}, |
|
73
|
2453
|
|
|
|
|
29151
|
$LAST_PAREN_MATCH{'day'}, $LAST_PAREN_MATCH{'hour'}, |
|
74
|
|
|
|
|
|
|
); |
|
75
|
|
|
|
|
|
|
|
|
76
|
2453
|
100
|
|
|
|
9016
|
if ( defined $year ) { |
|
77
|
2449
|
|
|
|
|
7850
|
$self->_parse( \$year, $BASE_N ); |
|
78
|
2449
|
|
|
|
|
6892
|
$self->_parse( \$month, $MONTHS_IN_YEAR ); |
|
79
|
2449
|
|
|
|
|
6828
|
$self->_parse( \$day, $MAX_DAYS_IN_MONTH ); |
|
80
|
2449
|
|
|
|
|
6470
|
$self->_parse( \$hour, $HOURS_IN_DAY ); |
|
81
|
2449
|
|
|
|
|
77689
|
$self->datetime->set( |
|
82
|
|
|
|
|
|
|
'year' => $year + $EPOCH_YEAR, |
|
83
|
|
|
|
|
|
|
'month' => $month + 1, |
|
84
|
|
|
|
|
|
|
'day' => $day + 1, |
|
85
|
|
|
|
|
|
|
'hour' => $hour, |
|
86
|
|
|
|
|
|
|
); |
|
87
|
|
|
|
|
|
|
} |
|
88
|
|
|
|
|
|
|
else { |
|
89
|
|
|
|
|
|
|
## no critic (RequireExplicitInclusion) |
|
90
|
|
|
|
|
|
|
DateExtractP800PictureException->throw( |
|
91
|
|
|
|
|
|
|
## use critic |
|
92
|
4
|
|
|
|
|
123
|
'error' => sprintf $ERR{'MISSING_DATE'}, |
|
93
|
|
|
|
|
|
|
$self->filename, |
|
94
|
|
|
|
|
|
|
); |
|
95
|
|
|
|
|
|
|
} |
|
96
|
|
|
|
|
|
|
} |
|
97
|
|
|
|
|
|
|
else { |
|
98
|
|
|
|
|
|
|
## no critic (RequireExplicitInclusion) |
|
99
|
|
|
|
|
|
|
DateExtractP800PictureException->throw( |
|
100
|
|
|
|
|
|
|
## use critic |
|
101
|
1
|
|
|
|
|
22
|
'error' => $ERR{'MISSING_FILENAME'}, |
|
102
|
|
|
|
|
|
|
); |
|
103
|
|
|
|
|
|
|
} |
|
104
|
2442
|
|
|
|
|
1259785
|
return $self->datetime; |
|
105
|
|
|
|
|
|
|
} |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
# Converts a character to a number given base. Changes the referenced part. |
|
108
|
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
sub _parse { |
|
110
|
9796
|
|
|
9796
|
|
16551
|
my ( $self, $sr_part, $base ) = @_; |
|
111
|
9796
|
|
|
|
|
13540
|
my $n_unparsed = 0; |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
## no critic (ProhibitCallsToUnexportedSubs) |
|
114
|
9796
|
|
|
|
|
13936
|
return ( ${$sr_part}, $n_unparsed ) = POSIX::strtol( ${$sr_part}, $base ); |
|
|
9796
|
|
|
|
|
18586
|
|
|
|
9796
|
|
|
|
|
21541
|
|
|
115
|
|
|
|
|
|
|
## use critic |
|
116
|
|
|
|
|
|
|
} |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
1; |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
__END__ |
|
121
|
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
=encoding utf8 |
|
123
|
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=for stopwords Bitbucket Ericsson Filename MERCHANTABILITY POSIX filename timestamp jpg JPG |
|
125
|
|
|
|
|
|
|
YMDH DateTime undef perl Readonly P800 P900 P910 perls Ipenburg |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
=head1 NAME |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
Date::Extract::P800Picture - extract the date from Sony Ericsson P800 pictures |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
=head1 VERSION |
|
132
|
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
This document describes Date::Extract::P800Picture version C<v1.1.6>. |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
use Date::Extract::P800Picture; |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
$filename = "8B360001.JPG"; # 2008-12-04T6:00:00 |
|
140
|
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
$parser = new Date::Extract::P800Picture(); |
|
142
|
|
|
|
|
|
|
$parser = new Date::Extract::P800Picture('filename' => $filename); |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
$datetime = $parser->extract(); |
|
145
|
|
|
|
|
|
|
$datetime = $parser->extract($filename); |
|
146
|
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
The Sony Ericsson L<P800|https://en.wikipedia.org/wiki/Sony_Ericsson_P800>, |
|
150
|
|
|
|
|
|
|
L<P900|https://en.wikipedia.org/wiki/Sony_Ericsson_P900> and |
|
151
|
|
|
|
|
|
|
L<P910|https://en.wikipedia.org/wiki/Sony_Ericsson_P910> camera phones store |
|
152
|
|
|
|
|
|
|
pictures taken with the camera on the device with a filename consisting of the |
|
153
|
|
|
|
|
|
|
date and the hour the picture was taken, followed by a four digit number and |
|
154
|
|
|
|
|
|
|
the .JPG extension. The format of the date and the hour is YMDH, in which the |
|
155
|
|
|
|
|
|
|
single characters are base 36 to fit a range of about 36 years, 12 months, 31 |
|
156
|
|
|
|
|
|
|
days and 24 hours since the year 2000 in a case insensitive US-ASCII |
|
157
|
|
|
|
|
|
|
representation. |
|
158
|
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
A L<web implementation of this parser|https://rolandvanipenburg.com/p800/> can |
|
160
|
|
|
|
|
|
|
be used without installing this module. |
|
161
|
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=head1 SUBROUTINES/METHODS |
|
163
|
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
=over 4 |
|
165
|
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
=item Date::Extract::P800Picture-E<gt>new() |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
=item Date::Extract::P800Picture-E<gt>new('filename' => $filename) |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
Constructs a new Date::Extract::P800Picture object. |
|
171
|
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=item $parser->filename($filename); |
|
173
|
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
Sets the filename to extract the date and hour from. |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=item $obj-E<gt>extract() |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
Extract date and hour from the string and returns it as L<DateTime|DateTime> |
|
179
|
|
|
|
|
|
|
object. Returns undef if no valid date could be extracted. |
|
180
|
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
=back |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=head1 CONFIGURATION AND ENVIRONMENT |
|
184
|
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
No configuration and environment settings are used. |
|
186
|
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
=head1 DEPENDENCIES |
|
188
|
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=over 4 |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
=item * perl 5.14 |
|
192
|
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
=item * L<POSIX|POSIX> |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=item * L<English|English> |
|
196
|
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=item * L<DateTime|DateTime> |
|
198
|
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=item * L<Readonly|Readonly> |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
=item * L<Moose|Moose> |
|
202
|
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
=item * L<Test::More|Test::More> |
|
204
|
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=back |
|
206
|
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
=head1 INCOMPATIBILITIES |
|
208
|
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
=over 4 |
|
210
|
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=item * To avoid ambiguity between more common date notations and the |
|
212
|
|
|
|
|
|
|
Sony Ericsson P800's date notation this is a separate module. It's highly |
|
213
|
|
|
|
|
|
|
unlikely that in any other setting "2000" means the first of January 2002. |
|
214
|
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
=item * For perls earlier than 5.14 version 0.04 of this module provides the |
|
216
|
|
|
|
|
|
|
same functionality in a perl 5.6 compatible way. |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
=back |
|
219
|
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
=head1 DIAGNOSTICS |
|
221
|
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
An exception in the form of an L<Exception::Class|Exception::Class> named |
|
223
|
|
|
|
|
|
|
C<DateExtractP800PictureException> is thrown when a date can not be extracted |
|
224
|
|
|
|
|
|
|
from the string: |
|
225
|
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
=over 4 |
|
227
|
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
=item * No date found in filename '%s' |
|
229
|
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
=item * Filename is not set, nothing to extract |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
=back |
|
233
|
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
=head1 BUGS AND LIMITATIONS |
|
235
|
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
=over 4 |
|
237
|
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
=item * The date could be from another timezone, based on the device settings |
|
239
|
|
|
|
|
|
|
and when and where the picture was taken. |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=item * Usually the files are transferred from the P800 to other systems in a |
|
242
|
|
|
|
|
|
|
way that has not completely preserved the timestamp of the file, so there is |
|
243
|
|
|
|
|
|
|
no reliable way to double check the results by comparing the date extracted |
|
244
|
|
|
|
|
|
|
from the filename with the timestamp of the file. |
|
245
|
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
=item * There are no error values to provide different exit statuses for |
|
247
|
|
|
|
|
|
|
different failure reasons |
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
=back |
|
250
|
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
Please report any bugs or feature requests at |
|
252
|
|
|
|
|
|
|
L<Bitbucket| |
|
253
|
|
|
|
|
|
|
https://bitbucket.org/rolandvanipenburg/date-extract-p800picture/issues>. |
|
254
|
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
=head1 AUTHOR |
|
256
|
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
Roland van Ipenburg, E<lt>roland@rolandvanipenburg.comE<gt> |
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
|
260
|
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
Copyright (C) 2008-2021, Roland van Ipenburg |
|
262
|
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify |
|
264
|
|
|
|
|
|
|
it under the same terms as Perl itself, either Perl version 5.14.0 or, |
|
265
|
|
|
|
|
|
|
at your option, any later version of Perl 5 you may have available. |
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
=head1 DISCLAIMER OF WARRANTY |
|
268
|
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY |
|
270
|
|
|
|
|
|
|
FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN |
|
271
|
|
|
|
|
|
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES |
|
272
|
|
|
|
|
|
|
PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER |
|
273
|
|
|
|
|
|
|
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
274
|
|
|
|
|
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE |
|
275
|
|
|
|
|
|
|
ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH |
|
276
|
|
|
|
|
|
|
YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL |
|
277
|
|
|
|
|
|
|
NECESSARY SERVICING, REPAIR, OR CORRECTION. |
|
278
|
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
|
280
|
|
|
|
|
|
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR |
|
281
|
|
|
|
|
|
|
REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENSE, BE |
|
282
|
|
|
|
|
|
|
LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, |
|
283
|
|
|
|
|
|
|
OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE |
|
284
|
|
|
|
|
|
|
THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING |
|
285
|
|
|
|
|
|
|
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A |
|
286
|
|
|
|
|
|
|
FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF |
|
287
|
|
|
|
|
|
|
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |
|
288
|
|
|
|
|
|
|
SUCH DAMAGES. |
|
289
|
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
=cut |