| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
2
|
|
|
2
|
|
32638
|
use strict; |
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
73
|
|
|
2
|
2
|
|
|
2
|
|
12
|
use warnings; |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
118
|
|
|
3
|
|
|
|
|
|
|
package Business::PLZ; |
|
4
|
|
|
|
|
|
|
{ |
|
5
|
|
|
|
|
|
|
$Business::PLZ::VERSION = '0.11'; |
|
6
|
|
|
|
|
|
|
} |
|
7
|
|
|
|
|
|
|
#ABSTRACT: Validate German postal codes and map them to states |
|
8
|
|
|
|
|
|
|
|
|
9
|
2
|
|
|
2
|
|
2138
|
use Tree::Binary::Search 1.0; |
|
|
2
|
|
|
|
|
13913
|
|
|
|
2
|
|
|
|
|
100
|
|
|
10
|
2
|
|
|
2
|
|
1497
|
use overload '""' => sub { ${$_[0]} }; |
|
|
2
|
|
|
117
|
|
1028
|
|
|
|
2
|
|
|
|
|
25
|
|
|
|
117
|
|
|
|
|
241
|
|
|
|
117
|
|
|
|
|
446
|
|
|
11
|
2
|
|
|
2
|
|
155
|
use Carp 'croak'; |
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
926
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our $STATES; |
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
# http://web.archive.org/web/*/http://www.uni-koeln.de/~arcd2/3d.htm |
|
16
|
|
|
|
|
|
|
BEGIN { |
|
17
|
2
|
|
|
2
|
|
95
|
my %RANGES = ( |
|
18
|
|
|
|
|
|
|
BW => [qw(68000-68309 68520-68549 68700-69234 69240-69429 69435-69469 |
|
19
|
|
|
|
|
|
|
69489-69502 69510-69514 70000-76709 77600-79879 88000-88099 |
|
20
|
|
|
|
|
|
|
88180-89198 89300-89619 97860-97999)], |
|
21
|
|
|
|
|
|
|
BY => [qw(63700-63939 80000-87490 87493-87561 87570-87789 88100-88179 |
|
22
|
|
|
|
|
|
|
89200-89299 90000-96489 97000-97859)], |
|
23
|
|
|
|
|
|
|
BE => [qw(10000-12527 12531-14199)], |
|
24
|
|
|
|
|
|
|
BB => [qw(01940-01998 03000-03253 04890-04938 12529 |
|
25
|
|
|
|
|
|
|
14400-16949 17260-17291 19340-19357)], |
|
26
|
|
|
|
|
|
|
HB => [qw(27500-27580 28000-28779)], |
|
27
|
|
|
|
|
|
|
HH => [qw(20000-21149 22000-22769 27499)], |
|
28
|
|
|
|
|
|
|
HE => [qw(34000-34329 34356-34399 34440-36399 37195 37200-37299 |
|
29
|
|
|
|
|
|
|
55240-55252 59969 60000-63699 64200-65556 65583-65620 65627 |
|
30
|
|
|
|
|
|
|
65700-65936 68501-68519 68600-68649 69235-69239 69430-69434 |
|
31
|
|
|
|
|
|
|
69479-69488 69503-69509 69515-69518)], |
|
32
|
|
|
|
|
|
|
MV => [qw(17000-17259 17300-19260 19280-19339 19360-19417 23920-23999)], |
|
33
|
|
|
|
|
|
|
NI => [qw(19270-19273 21202-21449 21522 21600-21789 26000-27478 27607-27809 |
|
34
|
|
|
|
|
|
|
28784-29399 29430-31868 34330-34355 37000-37194 37197-37199 |
|
35
|
|
|
|
|
|
|
37400-37649 37689-37691 37697-38479 38500-38729 48442-48465 |
|
36
|
|
|
|
|
|
|
48478-48480 48486-48488 48497-48531 49000-49459 49550-49849)], |
|
37
|
|
|
|
|
|
|
NW => [qw(32000-33829 34400-34439 37650-37688 37692-37696 40000-48432 |
|
38
|
|
|
|
|
|
|
48466-48477 48481-48485 48489-48496 48541-48739 49461-49549 |
|
39
|
|
|
|
|
|
|
50100-51597 51600-53359 53580-53604 53620-53949 57000-57489 |
|
40
|
|
|
|
|
|
|
58000-59968)], |
|
41
|
|
|
|
|
|
|
RP => [qw(51598 53400-53579 53614-53619 54200-55239 55253-56869 57500-57648 |
|
42
|
|
|
|
|
|
|
65558-65582 65621-65626 65629 66460-66509 66840-67829 76710-76891)], |
|
43
|
|
|
|
|
|
|
SL => [qw(66000-66459 66510-66839)], |
|
44
|
|
|
|
|
|
|
SN => [qw(01000-01936 02600-02999 04000-04579 04640-04889 07917-07919 |
|
45
|
|
|
|
|
|
|
07951-07952 07982-07985 08000-09669)], |
|
46
|
|
|
|
|
|
|
ST => [qw(06000-06548 06600-06928 29400-29416 38480-38489 38800-39649)], |
|
47
|
|
|
|
|
|
|
SH => [qw(21450-21521 21524-21529 22801-23919 24000-25999 27483-27498)], |
|
48
|
|
|
|
|
|
|
TH => [qw(04580-04639 06550-06578 07300-07907 07920-07950 07953-07980 |
|
49
|
|
|
|
|
|
|
07987-07989 36400-36469 37300-37359 96500-96529 98500-99998)], |
|
50
|
|
|
|
|
|
|
8 => [qw(87567-87569)], # Kleinwalsertal, Vorarlberg |
|
51
|
|
|
|
|
|
|
7 => [87491], # Jungholz, Tirol |
|
52
|
|
|
|
|
|
|
); |
|
53
|
|
|
|
|
|
|
|
|
54
|
2
|
|
|
|
|
13
|
$STATES = Tree::Binary::Search->new; |
|
55
|
|
|
|
|
|
|
$STATES->setComparisonFunction(sub { |
|
56
|
3238
|
|
|
|
|
83931
|
my ($a1,$a2) = split '-', $_[0]; |
|
57
|
3238
|
|
|
|
|
6397
|
my ($b1,$b2) = split '-', $_[1]; |
|
58
|
3238
|
100
|
|
|
|
6378
|
$a2 = $a1 unless defined $a2; |
|
59
|
3238
|
100
|
|
|
|
5330
|
$b2 = $b1 unless defined $b2; |
|
60
|
3238
|
100
|
|
|
|
7196
|
return -1 if $a2 < $b1; |
|
61
|
2721
|
100
|
|
|
|
8535
|
return +1 if $a1 > $b2; |
|
62
|
12
|
|
|
|
|
32
|
return 0; |
|
63
|
2
|
|
|
|
|
47
|
}); |
|
64
|
2
|
|
|
|
|
22
|
while (my ($state,$ranges) = each(%RANGES)) { |
|
65
|
36
|
|
|
|
|
1127
|
foreach my $plz (@$ranges) { |
|
66
|
280
|
|
|
|
|
7402
|
$STATES->insert($plz,$state); |
|
67
|
|
|
|
|
|
|
} |
|
68
|
|
|
|
|
|
|
} |
|
69
|
|
|
|
|
|
|
} |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
# TODO: see http://anchje.de/inv_rep2.htm for more expections |
|
72
|
|
|
|
|
|
|
# 21039 SH and HH |
|
73
|
|
|
|
|
|
|
# 37194 HE and NE |
|
74
|
|
|
|
|
|
|
# 59969 HE and NW |
|
75
|
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
sub new { |
|
77
|
17
|
|
|
17
|
0
|
3980
|
my ($class, $code) = @_; |
|
78
|
17
|
|
33
|
|
|
76
|
$class = ref $class || $class; |
|
79
|
|
|
|
|
|
|
|
|
80
|
17
|
100
|
66
|
|
|
1026
|
croak 'invalid postal code' unless $code and $code =~ qr/^\d{5}$/; |
|
81
|
|
|
|
|
|
|
|
|
82
|
10
|
|
|
|
|
81
|
bless \$code, $class; |
|
83
|
|
|
|
|
|
|
} |
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub state { |
|
86
|
9
|
|
|
9
|
1
|
25
|
my $plz = shift; |
|
87
|
9
|
100
|
66
|
|
|
60
|
$plz = Business::PLZ->new( $plz ) |
|
88
|
|
|
|
|
|
|
unless ref $plz and $plz->isa('Business::PLZ'); |
|
89
|
|
|
|
|
|
|
# Tree::Binary throws on exception if key does not exist :-( |
|
90
|
9
|
100
|
|
|
|
36
|
return $STATES->exists($plz) ? $STATES->select($plz) : undef; |
|
91
|
|
|
|
|
|
|
} |
|
92
|
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
sub exists { |
|
94
|
2
|
|
|
2
|
1
|
295
|
my $state = state(shift); |
|
95
|
2
|
100
|
|
|
|
42
|
return defined $state ? 1 : 0; |
|
96
|
|
|
|
|
|
|
} |
|
97
|
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
sub iso_state { |
|
99
|
3
|
|
100
|
3
|
1
|
995
|
my $state = state(shift) || return; |
|
100
|
1
|
50
|
|
|
|
23
|
return ($state =~ /[A-Z][A-Z]/) ? "DE-$state" : "AT-$state"; |
|
101
|
|
|
|
|
|
|
} |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
1; |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
=pod |
|
108
|
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
=head1 NAME |
|
110
|
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
Business::PLZ - Validate German postal codes and map them to states |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=head1 VERSION |
|
114
|
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
version 0.11 |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
118
|
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
use Business::PLZ; |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
my $plz = Business::PLZ->new('12345'); # croaks on invalid code |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
print "$plz"; # stringify |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
$plz->state; # state or undef if not exist |
|
126
|
|
|
|
|
|
|
$plz->iso_state; # state as full ISO code |
|
127
|
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
129
|
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
This module validates German postal codes and maps them to states. |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=head1 METHODS |
|
133
|
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=head2 state |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
Returns the state ("Bundesland") of a postal code as ISO 3166-2 subdivision |
|
137
|
|
|
|
|
|
|
code. The country prefix 'DE-' (or 'AT-') is not included. Some postal codes |
|
138
|
|
|
|
|
|
|
belong to more than one state - in this case only one state is returned. A |
|
139
|
|
|
|
|
|
|
future version of this module may also return multiple states. |
|
140
|
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
If no state was found (so the postal code likely does not exists), this |
|
142
|
|
|
|
|
|
|
method returns undef. The method 'exists' is based on this lookup. |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
To get more information about a state, you can use L: |
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
$state_code = $plz->state; |
|
147
|
|
|
|
|
|
|
$state_name = Locale::SubCountry->new('DE')->full_name( $state_code ); |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
=head2 iso_state |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
Returns the state of a postal code as ISO 3166-2 subdivision code, including |
|
152
|
|
|
|
|
|
|
country prefix. |
|
153
|
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=head2 exists |
|
155
|
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
Returns whether the postal code is assigned. This is exactely the case if |
|
157
|
|
|
|
|
|
|
it can be mapped to a state. |
|
158
|
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
There are some country-specific modules to handle postal codes, for instance |
|
162
|
|
|
|
|
|
|
L and L. L contains |
|
163
|
|
|
|
|
|
|
regular expressions for postal codes of almost every country. |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
=head1 AUTHOR |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
Jakob Voß |
|
168
|
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
170
|
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
This software is copyright (c) 2013 by Jakob Voß. |
|
172
|
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
|
174
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=cut |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
__END__ |