uk_postcode 1.0.1 → 2.0.0.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/COPYING.txt +25 -0
- data/README.md +105 -55
- data/lib/uk_postcode/country_finder.rb +15 -0
- data/lib/uk_postcode/country_lookup.rb +10 -0
- data/lib/uk_postcode/geographic_postcode.rb +98 -0
- data/lib/uk_postcode/giro_postcode.rb +61 -0
- data/lib/uk_postcode/invalid_postcode.rb +38 -0
- data/lib/uk_postcode/tree.rb +75 -0
- data/lib/uk_postcode/version.rb +2 -2
- data/lib/uk_postcode.rb +13 -106
- data/test/country_finder_test.rb +33 -0
- data/test/full_postcode_test.rb +35 -0
- data/test/geographic_postcode_test.rb +271 -0
- data/test/giro_postcode_test.rb +100 -0
- data/test/invalid_postcode_test.rb +75 -0
- data/test/special_postcode_test.rb +50 -0
- data/test/tree_test.rb +84 -0
- data/test/uk_postcode_test.rb +17 -265
- metadata +20 -9
- data/Rakefile +0 -8
- data/test/samples/wikipedia.list +0 -35
- data/test/samples_test.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a2918ca3cc4b63e92b62146b63e57897b31166f0
|
4
|
+
data.tar.gz: c6689e38f4f8e4583d74f9bf06c5c617a16f66b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea95753c01bc600400b6223b5da95957170db8d1e39f10b80f045e5379f248235a33e38b558c64f91b9c3548d616414dfac2f35172cc18b1d29da372418df458
|
7
|
+
data.tar.gz: d34d691408d759e967cf626aecec25f92bbff1a10507bff46a082ca96a5ab37e44a2ad185116998a6f4d040c68bcdea1863cbf7487541dbf6928f40ec2b3a8f4
|
data/COPYING.txt
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Copyright (c) 2010-2014 Paul Battley
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
20
|
+
|
21
|
+
The regular expressions in lookup.rb are generated from the ONS Postcode
|
22
|
+
Directory under the terms of the Open Government Licence (OGL). The following
|
23
|
+
source accreditation is given to fulfil the terms of this licence.
|
24
|
+
|
25
|
+
http://www.ons.gov.uk/ons/guide-method/geography/products/postcode-directories/-nspp-/index.html
|
data/README.md
CHANGED
@@ -11,6 +11,7 @@ Features:
|
|
11
11
|
* Allows extraction of fields within postcode.
|
12
12
|
* Validated against 2.5 million postcodes in England, Wales, Scotland, Northern
|
13
13
|
Ireland, the Channel Islands, and the Isle of Man.
|
14
|
+
* Finds the country corresponding to a postcode, where possible.
|
14
15
|
|
15
16
|
## Usage
|
16
17
|
|
@@ -18,52 +19,106 @@ Features:
|
|
18
19
|
require "uk_postcode"
|
19
20
|
```
|
20
21
|
|
21
|
-
|
22
|
+
Parse and extract sections of a full postcode:
|
22
23
|
|
23
24
|
```ruby
|
24
|
-
pc = UKPostcode.
|
25
|
-
pc.valid?
|
26
|
-
pc.full?
|
27
|
-
pc.outcode
|
28
|
-
pc.incode
|
29
|
-
pc.area
|
30
|
-
pc.district
|
31
|
-
pc.sector
|
32
|
-
pc.unit
|
25
|
+
pc = UKPostcode.parse("W1A 2AB")
|
26
|
+
pc.valid? # => true
|
27
|
+
pc.full? # => true
|
28
|
+
pc.outcode # => "W1A"
|
29
|
+
pc.incode # => "2AB"
|
30
|
+
pc.area # => "W"
|
31
|
+
pc.district # => "1A"
|
32
|
+
pc.sector # => "2"
|
33
|
+
pc.unit # => "AB"
|
33
34
|
```
|
34
35
|
|
35
36
|
Or of a partial postcode:
|
36
37
|
|
37
38
|
```ruby
|
38
|
-
pc = UKPostcode.
|
39
|
-
pc.valid?
|
40
|
-
pc.full?
|
41
|
-
pc.outcode
|
42
|
-
pc.incode
|
43
|
-
pc.area
|
44
|
-
pc.district
|
45
|
-
pc.sector
|
46
|
-
pc.unit
|
39
|
+
pc = UKPostcode.parse("W1A")
|
40
|
+
pc.valid? # => true
|
41
|
+
pc.full? # => false
|
42
|
+
pc.outcode # => "W1A"
|
43
|
+
pc.incode # => nil
|
44
|
+
pc.area # => "W"
|
45
|
+
pc.district # => "1A"
|
46
|
+
pc.sector # => nil
|
47
|
+
pc.unit # => nil
|
47
48
|
```
|
48
49
|
|
49
|
-
|
50
|
+
Postcodes are converted to a normal or canonical form:
|
50
51
|
|
51
52
|
```ruby
|
52
|
-
UKPostcode.
|
53
|
+
pc = UKPostcode.parse("w1a1aa")
|
54
|
+
pc.valid? # => true
|
55
|
+
pc.area # => "W"
|
56
|
+
pc.district # => "1A"
|
57
|
+
pc.sector # => "1"
|
58
|
+
pc.unit # => "AA
|
59
|
+
pc.to_s # => "W1A 1AA"
|
53
60
|
```
|
54
61
|
|
55
|
-
|
62
|
+
And mistakes with I/1 and O/0 are corrected:
|
56
63
|
|
57
64
|
```ruby
|
58
|
-
pc = UKPostcode.
|
59
|
-
pc.
|
60
|
-
pc.
|
65
|
+
pc = UKPostcode.parse("WIA OAA")
|
66
|
+
pc.valid? # => true
|
67
|
+
pc.area # => "W"
|
68
|
+
pc.district # => "1A"
|
69
|
+
pc.sector # => "0"
|
70
|
+
pc.unit # => "AA
|
71
|
+
pc.to_s # => "W1A 0AA"
|
61
72
|
```
|
62
73
|
|
63
|
-
|
74
|
+
Find the country of a full or partial postcode (if possible: some outcodes span
|
75
|
+
countries):
|
64
76
|
|
65
|
-
```
|
66
|
-
|
77
|
+
```ruby
|
78
|
+
UKPostcode.parse("W1A 1AA").country # => :england
|
79
|
+
UKPostcode.parse("BT4").country # => :northern_ireland
|
80
|
+
UKPostcode.parse("CA6").country # => :unknown
|
81
|
+
UKPostcode.parse("CA6 5HS").country # => :scotland
|
82
|
+
UKPostcode.parse("CA6 5HT").country # => :england
|
83
|
+
```
|
84
|
+
|
85
|
+
The country returned for a postcode is derived from the [ONS Postcode
|
86
|
+
Directory][onspd] and might not always be correct in a border region:
|
87
|
+
|
88
|
+
> Users should note that postcodes that straddle two geographic areas will be
|
89
|
+
> assigned to the area where the mean grid reference of all the addresses
|
90
|
+
> within the postcode falls.
|
91
|
+
|
92
|
+
Invalid postcodes:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
pc = UKPostcode.parse("Not Valid")
|
96
|
+
pc.valid? # => false
|
97
|
+
pc.full? # => false
|
98
|
+
pc.area # => nil
|
99
|
+
pc.to_s # => "Not valid"
|
100
|
+
pc.country # => :unknown
|
101
|
+
```
|
102
|
+
|
103
|
+
## For users of version 1.x
|
104
|
+
|
105
|
+
The interface has changed significantly, so code that worked with version 1.x
|
106
|
+
will not work with version 2.x without changes.
|
107
|
+
|
108
|
+
Specifically:
|
109
|
+
|
110
|
+
* Use `UKPostcode.parse(str)` where you previously used `UKPostcode.new(str)`.
|
111
|
+
* `parse` will return either a `GeographicPostcode`, a `GiroPostcode`, or an
|
112
|
+
`InvalidPostcode`.
|
113
|
+
* You may prefer to use `GeographicPostcode.parse` directly if you wish to
|
114
|
+
exclude `GIR 0AA` and invalid postcodes.
|
115
|
+
|
116
|
+
## As a gem
|
117
|
+
|
118
|
+
In your `Gemfile`:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
gem "uk_postcode", "~> 2.0.0.alpha"
|
67
122
|
```
|
68
123
|
|
69
124
|
## Testing
|
@@ -71,44 +126,39 @@ $ gem install uk_postcode
|
|
71
126
|
To run the test suite:
|
72
127
|
|
73
128
|
```sh
|
74
|
-
$
|
129
|
+
$ make
|
75
130
|
```
|
76
131
|
|
77
132
|
The full list of UK postcodes is not included in the repository due to its
|
78
|
-
size.
|
79
|
-
Additional sample files under `test/samples` will automatically be used.
|
133
|
+
size, but will be fetched automatically from [mySociety][mys].
|
80
134
|
|
81
|
-
|
82
|
-
|
135
|
+
If you are running an automatic build process, please find a way to cache these
|
136
|
+
files and run the tests via Rake instead:
|
83
137
|
|
84
138
|
```
|
85
|
-
|
86
|
-
BT109AH
|
139
|
+
$ rake
|
87
140
|
```
|
88
141
|
|
89
|
-
|
90
|
-
from [mySociety][mys].
|
142
|
+
## Licensing
|
91
143
|
|
92
|
-
|
144
|
+
You may use this library according to the terms of the MIT License; see
|
145
|
+
COPYING.txt for details.
|
93
146
|
|
94
|
-
|
95
|
-
|
147
|
+
The regular expressions in `lookup.rb` are derived from the ONS Postcode
|
148
|
+
Directory according to the terms of the [Open Government
|
149
|
+
Licence][onspd-lic].
|
96
150
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
### ONS Postcode Directory
|
151
|
+
> Under the terms of the Open Government Licence (OGL) […] anyone wishing to
|
152
|
+
> use or re-use ONS material, whether commercially or privately, may do so
|
153
|
+
> freely without a specific application for a licence, subject to the
|
154
|
+
> conditions of the OGL and the Framework. Users reproducing ONS content must
|
155
|
+
> include a source accreditation to ONS.
|
103
156
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
```sh
|
110
|
-
$ cut -c 2-8 ONSPD_*.csv | grep '[A-Z]' | grep -v ^NPT | \
|
111
|
-
sort -uV > test/samples/large/onspd.list
|
112
|
-
```
|
157
|
+
In order to avoid the restrictive commercial terms of the Northern Ireland
|
158
|
+
data in the ONSPD, this is not used to generate the regular expressions.
|
159
|
+
Fortunately, Northern Ireland postcodes are very simple: they all start with
|
160
|
+
`BT`!
|
113
161
|
|
114
162
|
[mys]: http://parlvid.mysociety.org/os/
|
163
|
+
[onspd]: http://www.ons.gov.uk/ons/guide-method/geography/products/postcode-directories/-nspp-/index.html
|
164
|
+
[onspd-lic]: http://www.ons.gov.uk/ons/guide-method/geography/beginner-s-guide/licences/index.html
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'uk_postcode/country_lookup'
|
2
|
+
|
3
|
+
module UKPostcode
|
4
|
+
module CountryFinder
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def country(postcode)
|
8
|
+
normalized = [postcode.outcode, postcode.incode].compact.join
|
9
|
+
COUNTRY_LOOKUP.each do |name, regexp|
|
10
|
+
return name if normalized.match(regexp)
|
11
|
+
end
|
12
|
+
:unknown
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module UKPostcode
|
2
|
+
COUNTRY_LOOKUP = {
|
3
|
+
england: /^(?:AL|B[123456789ABDHLNRS]|C(?:A(?:1|2|3|4|5|6(?:4|5(?:G|H[TU]|L[GHJLNPQRSTUWXYZ]|N|P|Q|R|S|T|U|W|X|Y)|6|7)|7|8|9)|B|H(?:1(?:1|2|3|4(?:A|B|D|E|F|G|H|J|L|N|P|Q[ABDFGHJNPQRSTUWX]|R|S|W|X|Y|Z)|5|6(?:A|B[DEFGHJU]|D[ABEFGHLNPQRSTUWXYZ]|E[ADEFGHJLNPQRSTUWXYZ]|F|G|H[DEFGJLNQSTUWXYZ]|J|L|N|P|Q[AP]|R|W|X|Y|Z)|9)|2(?:1|2|3|4|5|6|7|89(?:A[ABEFGHJLNPQRSTUWXYZ]|B)|99(?:A[BDEFGHJLNPQRSTUWXYZ]|B|D|E))|3(?:5|6|7|8|9|0|1|2|3|49(?:A[ABDEFGHJLNQRSTUWXYZ]|B|D|E|S|T|Z))|4(?:0(?:EJ|W[BDFGHRU]|Y[HJ]|Z[ADEFGHJLPTUWY])|7|8(?:A|B|D|E|H|J|L[BDEFLNPSUXYZ]|N[DEFGLNPQRSTUWXYZ]|P[ADEFGHJLNPQRSTUWXZ]|Q[ADU]|R[AD]|W[ABDEFGHJLPQRSTUWXYZ]|X|Y|Z|0|1|2|3|4|5|6|7|8|9)|9(?:A[DEFG]|BW|D[EFGHJLNPQRSTUW]|E[BDEFGJLNPQRSTUWXYZ]|F|G[ABJ]|H|J|L|N|P[XZ]|Q|R|T|U|W|X[SUY]|Y|Z|0|1|2|3|4|5|6|7|8|9)|1|2|3|4|5|6)|6(?:5[0123456789]|6[0123456789]|9AB|0|1|2|3|4)|708(?:A[ADEGHJLNPQRSTUWXYZ]|B|D)|883|9)|M|O|R|T|V|W)|D(?:A|E|G165(?:H[TUZ]|J[AB])|H|L|N|T|Y)|E[123456789CNX]|FY|G(?:I|L(?:1(?:1|2|3|4|5|9|0|6(?:7|8(?:A|B|D|E|H|J|L|N|P[ADEFGHJLNPQRSTUWXYZ]|Q|R|S|T|W|Y|Z)|9)|7|8)|2|3|4|5|6|7|8|9)|U)|H(?:A|D|G|P|R(?:1|2|3(?:5(?:AL|B[BDERZ]|E[NPRSTUWXYZ]|FE|H|Q[XYZ]|R[ABDFGNQW]|S[TUXY]|T[ABDEFGP]|W[NUX]|XA|Y[ADFJNQRSTWX])|6(?:A|B|D|E[ABDEFGHJLNPQRSTUWZ]|H[EFGHJPQSTUWXYZ]|J[ABDEFGHLNPQRSTZ]|L|N|P|Q|R|S|W|Y[ABDEFHJLNPQU]|Z))|4|5(?:3(?:A|B|D|E[ABDEFGHJLNQRSTUWXYZ]|F|G|H|J|L|N[ABEFGHJLNQ]|P[BFGHQTUXYZ]|QA|R[BDEFGHJLNPQRSTUWXYZ]|S|T|U|W|X|Y|Z)|8)|6|7|8|9)|U|X)|I[GP]|KT|L(?:1|2|3|4|5|6|7|8|9|A|D(?:71(?:DY|EA|N[BDEF]|T[NPRSUWY]|U[ABDEFW]|Y[SUY]|ZJ)|82(?:H[HLNPRSTUWY]|L[EGHLNPRSTUWY]|N[ABD]|P[RS]))|E|L145(?:B[UY]|D[ABDEFGLNPRSUWY]|E[ABDEFHLNW]|L[JP])|N|S|U)|M[123456789EK]|N(?:1|2|3|4|5|6|7|8|9|E|G|N|P(?:167(?:A|B|D|E|F|H|J|L|N[AFGHJLNPQRSTUW]|P|Q|R|WA|Y[BDEFGJLN]|Z[AB])|25(?:3S[RSTUX]|4(?:AS|L[DHJLNPQRTXYZ]|N[ABDE])|5(?:Q[JL]|R[DGHJLNPQRSTWY]))|5(?:3(?:Q[JL]|R[GHJLNPQRSTWY]|S[RSTUX])|4(?:L[HJLNPQRSTUWXYZ]|N[AB]))|6(?:6SG|7(?:A|B|D|E|F|H|J|L|N[AFGJLNPQRSTUW]|P|Q|R|Y|Z))|78(?:EB|HH))|R|W)|O|P[ELOR]|R|S(?:1|2|3|4|5|6|7|8|9|E|G|K|L|M|N|O|P|R|S|T|W|Y(?:1(?:1|2|3(?:A|B|D|E|F|G|H|J|L|N|P|Q|R|S|T|W|X|Y|Z|1|2(?:A|B|D|E|F|G|H|J[ABDEFGL]|L[FGT]|N|P[ADEFGHPQRSTUXYZ]|Q|R|S|T|W|X|Y|Z)|3(?:A[AB]|J[GHJLNPQRSTUWXYZ]|L|N[EFGHJLNPQRSTUWXY]|P[FG]|W[ADFGHJXYZ]|X|Y)|4(?:A|B|D|E|H|J|L|N|P|Q|R[ABDFG]|W)|9)|4(?:A|B|D|E|H|J|L|N|P|Q|R|S|T|U|W|Y|7(?:A[ABDEFGHJLPQRSTUWXY]|B|D|E|H[ABDEFGHJLNPQRSTUWXZ]|J[ABDEFJLNPRSWXY]|N[EFGHJQ]|P|U|W|Y)|8)|9[ABDEFGHJLSTUWX]|0(?:0(?:W[AHJQZ]|Z)|1|7(?:A|B|D|E|G|H|J[ABDPRSUXYZ]|L|N[ABDGHJLNPQRW]|P[BDEHJLNPQRW]|R|S|T|U|W|X|Y|Z)|8|9(?:A|B[ABDEFGHJLNQSTUXYZ]|D|E|F|H[ABDEFGHJLNPQRSTUWXY]|J[ABDEFQ]|L[SUXYZ]|N|P[BDEFGHJLNPQRSTUWXYZ]|Q|R|W|Y))|56(?:A[XY]|B[DEGHJLNPQSTUWXYZ]|D[ABDEFGPR]|E[DEP]|H[TUY]|S[PYZ]|T[RZ]|U[BDEFGHQ]|Z))|2(?:2(?:P|5WD|6(?:E[ABDEFGHJLQ]|H[BD]|JX|L[GLNQUWXYZ]|N[TW]|W[ABEHJXYZ]))|5[ABDEGHJLNPQRSTUWXYZ]|6|18(?:D[LN]|E[PRW]|J[LNPQRSTUWXY]|LB))|3|4|5(?:0(?:A|B|D|E|F|H|J[ABDEFGHPQWXYZ]|L|N|P|Q|R|S|T|U|W|X|Y|Z)|6|7|8|9(?:A[ABDEFGHJLNPQ]|B[XYZ]|D|E[ABDEFGHJLNPQRSTWXYZ]|FA|G[ABDEFGHJLQXYZ]|H|J|L[ABEFGHJLNPQRSTUWXYZ]|N|P|Q|R|SA|W|X|Y|Z))|6|7(?:0|7|8(?:A|B|D|E|H|J|L|N|P[ABDEFGHJLNPQRW]|Q|W)|9)|8|9(?:5(?:A|B|D|E|F|H|J[GHJLNQRSTUXY]|L|N|P|W)|9)))|T(?:A|D(?:1(?:24(?:Q[ABDEFGHJLNPQRSTW]|R|S|T|U|WB|X[ABDFGHJLNQ])|5(?:1(?:A|B[ABEFGHJLNPQSUWXYZ]|D|E|H|J|L|N|P|Q|R|S[ABDEJLNPWXY]|T[ABQUX]|U[ABDENPQRTUXY]|W[FX]|X[XY]|Y|Z)|2|9))|90T[JPRSTW])|F|N|Q|R|S|W)|U|W|Y)/,
|
4
|
+
scotland: /^(?:AB|CA65(?:A|B|D|E|H[ABDEFGHJLNPQRSWXYZ]|J|L[ABDE])|D(?:D|G(?:1(?:1|2|3|4|9|0|65(?:A|B|D|E|F|G|H[ABDEFGHJLNPQRSWXY]|J[DEFGHJLNPQRSTUWXYZ]|L|N|Q|U|W|X|Y))|2|3|4|5|6|7|8|9))|EH|FK|G[123459678]|HS|IV|K[AWY]|ML|P[AH]|TD(?:1(?:1|2(?:A|B|D|E|H|J|L|N|P|Q|R|S|T|U|W|Y|4(?:A|B|D|E|F|H|J|L|N|QU|W[ADX]|XE|Y)|9)|3|9|0|4|51(?:BT|S[UZ]|T[DEFGHJLNPRSTW]|U[FGHJLSWZ]|W[YZ]|X[ABGHJLNQTUWZ]))|2|3|4|5|6|7|8|9(?:0(?:A|B|D|E|H|J|L|N|P|Q|R|S|T[ABDEFGHLNQUXY]|W|Y|Z)|1|7|8|9))|Z)/,
|
5
|
+
wales: /^(?:C(?:F|H(?:1(?:4QL|6(?:B[ABLPQRSTWXYZ]|D[DJ]|EB|H[ABH]|Q[BEFGQ]))|2(?:89AD|99AA)|349AP|4(?:0(?:A|B|D|E[ABDEFGHLNPQRSTUWXYZ]|F|G|H|J|L|N|P|Q|R|S|T|W[AE]|X|Y[PRSTUXY]|Z[BNQRS])|8(?:G|L[AGHJQRTW]|N[ABH]|P[BY]|Q[BEFGHJLNPQRSTWYZ]|R[BEFGHJLNPQRSTUWXYZ]|S|T|U|WN)|9(?:A[ABHJLNPQRSTUWXYZ]|B[ABDEFGHJLNPQRSTUXYZ]|D[ABDXYZ]|E[AH]|GD|P[ABDFG]|XA))|5|6(?:5[ABDEFGHJLNPQRSTUWXYZ]|6[ABDEFHJLZ]|9(?:A[DEFGJLNPQRSTUWXYZ]|B|D))|7(?:1|2|3|4|5|6|9|08A[BF])|8(?:7|8[ABDEHJLNPQRSTWYZ]|9)))|GL168PB|HR(?:3(?:5(?:A[ABDEFGHJNPQRSTUWXYZ]|B[AGHJLNPQSTUWXY]|D|E[ABDEFGHJLQ]|F[AD]|J|L|N|P|Q[ABDEFGHJLNPQRSTUW]|R[EHJLPRSTUXYZ]|S[ABDEFGHJLNPQRSWZ]|T[HRSTUX]|W[ALPQRSTWYZ]|XZ|Y[BEGHLPU]|Z)|6(?:E[XY]|H[ALN]|J[JUWXY]|YG))|53(?:EP|N[DPRSTUWXYZ]|P[ADEJLNPRW]|Q[BDEFGHJLNPQRSTUWXZ]|RA))|L(?:D(?:1|2|3|4|5|6|7(?:1(?:A|B|D[ABDEFGHJLNPQRSTUW]|E[BDEFGHNPRSTUWY]|H|L|N[AGHJLNPQRSTUWY]|P|R|S|T[ABDET]|U[GHPRTUY]|W|Y[ABDEFGHLNPRTW]|ZY)|9)|82(?:A|B|D|E|G|H[ABDEFG]|L[ABDF]|N[EFGHJLNPQRSTUWXY]|P[ABDEFGHLNPTUWY]|R|S|T|U|W|Y|Z))|L(?:1(?:1|2|3|4(?:1|2|3|4|5(?:A|B[ABDEFGHJLNPQSTWXZ]|D[HJ]|E[PQRSTUYZ]|H|L[ABDEFGHLNQRSTUWY]|N|P|Q|R|S|W|Y|Z)|6)|5|6|7|8|9)|2|3|4|5|6|7))|NP(?:1(?:0|2|4|5|6(?:A|B|D|E|F|G|H|J|L|N|P|Q|R|X|Y|Z|5|6|7(?:NX|W[EJLNPQRSTUWXYZ]|Y[AHPQRSTUWY]|Z[SX])|9)|7|8|9|1|3)|2(?:0|1|2|3|4|5(?:A|B|D|E|H|J|L|N|P|Q|R|X|Y|3(?:A|B|D|E|G|H|J|L|N|P|Q|S[ABDEFGHJLNPQWYZ]|T|U|W|X|Y|Z)|4(?:A[BDEFGHJLNPQRTW]|B|D|E|H|J|L[EGUW]|N[YZ]|P|Q|R|S|T|U|W|X|Y|Z)|5(?:A|B|D|E|F|G|H|J|L|N|P|Q[ABDEFGHQXYZ]|R[ABEFZ]|S|T|U|W|X|Y|Z)|9)|6)|3|4|5(?:1|2|3(?:A|B|D|E|G|H|J|L|N|P|Q[ABDEFGHQXYZ]|R[ABDEFZ]|S[ABDEFGHJLNPQWZ]|T|U|X|Y|Z)|4(?:A|B|D|E|F|H|J|L[ABDEFG]|N[FGHJLNPQRSTUWXYZ]|P|Q|R|S|T|U|X|Y))|6(?:1|2|3|4|5|6(?:A|B|D|E|H|J|L|N|P|Q|R|S[ABDEFHJLNPQRSTUWXYZ]|T|U|X|Y)|7N[HX])|7(?:0|1|5|6|7|8(?:A|B|D|E[ADEFGHLNPQRSTWXY]|H[ABDEFGLNPRSTUWY]|L|N|P|R|S|T|U|W|Y|Z)|9)|8|9)|S(?:A|Y(?:1(?:3(?:2(?:J[HJNPQRSTUWXYZ]|L[ABDEHJLNPQRSUWX]|PB)|3(?:A[DEFGHJLNPQRSTUWXYZ]|B|D|E|G|H|J[AB]|NZ|P[ABDE]|W[BE])|4RE)|47(?:A[NZ]|HY|J[HTUZ]|L|N[ABD])|97|0(?:0(?:A|B|D|E|G|H|J|L|N|P|W[BDEFG])|7(?:J[EFGHJLNQW]|N[EFSTUXYZ]|P[AGSTUXYZ]|Q)|9(?:B[PW]|HZ|J[GHJLNPRSTUWXYZ]|L[ABDEFGHJLNPQRTW]|PA))|56(?:A[ABDEFGHJLNPQRSTUWZ]|B[AB]|D[HJLNQSTUWXYZ]|E[ABFGHJLNQRSTUWXYZ]|H[ABDEFGHJLNPQRSWXZ]|J|L|N|P|Q|R|S[ABDEFGHQRSTUX]|T[ABDEFGHJLNPQWY]|U[AJLNPR]|W|Y)|6|7|8)|2(?:2(?:5(?:A|B|D|E|H|J|L|N|W[ABPZ])|6(?:A|B|D|E[NPRSTUWXYZ]|F|G|H[AEFLNPRSTUWXYZ]|J[ABDEFGHJLNPQRSTWYZ]|L[ABDEFHJPRST]|N[ABDEFGHJLNPQRSXYZ]|P|Q|R|S|T|U|W[DFG]|X|Y))|56|0|1(?:0|1|7|8(?:A|B|D[ABDEFGHJPQRSTUWXYZ]|E[ABDEFGHJLNQSTUXYZ]|F|G|H|J[ABDEFGHJZ]|L[ADEFGHJLNPQRSTUWXYZ]|N|P|Q|R|S|T|W|X|Z)|9)|3|4)|5(?:0J[JLN]|9(?:A[RSTUWXYZ]|B[ABDEFGHJLNPQSTUW]|EU|FB|GN|LD|SB))|78PS|95J[PW])))/,
|
6
|
+
channel_islands: /^(?:GY|J)/,
|
7
|
+
isle_of_man: /^IM/,
|
8
|
+
northern_ireland: /^BT/
|
9
|
+
}
|
10
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "uk_postcode/country_finder"
|
2
|
+
|
3
|
+
module UKPostcode
|
4
|
+
|
5
|
+
# GeographicPostcode models the majority of postcodes, containing an area,
|
6
|
+
# a district, a sector, and a unit.
|
7
|
+
#
|
8
|
+
# Despite the name, it also handles non-geographic postcodes that follow the
|
9
|
+
# geographic format.
|
10
|
+
#
|
11
|
+
class GeographicPostcode
|
12
|
+
PATTERN = %r{
|
13
|
+
\A ( [A-PR-UWYZ01][A-HJ-Z0]? ) # area
|
14
|
+
(?: ( [0-9IO][0-9A-HJKMNPR-YIO]? ) # district
|
15
|
+
(?: \s* ( [0-9IO] ) # sector
|
16
|
+
( [ABD-HJLNPQ-Z]{2} )? )? )? # unit
|
17
|
+
\Z
|
18
|
+
}ix
|
19
|
+
|
20
|
+
# Attempts to parse the postcode given in str, and returns an instance of
|
21
|
+
# GeographicPostcode on success, or nil on failure.
|
22
|
+
#
|
23
|
+
def self.parse(str)
|
24
|
+
matched = PATTERN.match(str.strip)
|
25
|
+
if matched
|
26
|
+
new(*(1..4).map { |i| matched[i] })
|
27
|
+
else
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Initialise a new GeographicPostcode instance with the given area,
|
33
|
+
# district, sector, and unit. Only area is required.
|
34
|
+
#
|
35
|
+
def initialize(area, district = nil, sector = nil, unit = nil)
|
36
|
+
@area = letters(area)
|
37
|
+
@district = digits(district)
|
38
|
+
@sector = digits(sector)
|
39
|
+
@unit = letters(unit)
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :area, :district, :sector, :unit
|
43
|
+
|
44
|
+
# The left-hand part of the postcode, e.g. W1A 1AA -> W1A
|
45
|
+
#
|
46
|
+
def outcode
|
47
|
+
return nil unless district
|
48
|
+
[area, district].join('')
|
49
|
+
end
|
50
|
+
|
51
|
+
# The right-hand part of the postcode, e.g. W1A 1AA -> 1AA
|
52
|
+
#
|
53
|
+
def incode
|
54
|
+
return nil unless sector && unit
|
55
|
+
[sector, unit].join('')
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the canonical string representation of the postcode.
|
59
|
+
#
|
60
|
+
def to_s
|
61
|
+
[area, district, " ", sector, unit].compact.join('').strip
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns true if the postcode is a valid full postcode (e.g. W1A 1AA)
|
65
|
+
#
|
66
|
+
def full?
|
67
|
+
area && district && sector && unit && true
|
68
|
+
end
|
69
|
+
|
70
|
+
# Any geographic postcode is assumed to be valid
|
71
|
+
#
|
72
|
+
def valid?
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
# Find the country associated with the postcode. Possible values are
|
77
|
+
# :england, :scotland, :wales, :northern_ireland, :isle_of_man,
|
78
|
+
# :channel_islands, or :unknown.
|
79
|
+
#
|
80
|
+
# Note that, due to limitations in the underlying data, the country might
|
81
|
+
# not always be correct in border regions.
|
82
|
+
#
|
83
|
+
def country
|
84
|
+
CountryFinder.country(self)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
def letters(str)
|
89
|
+
return nil unless str
|
90
|
+
str.upcase.tr("10", "IO")
|
91
|
+
end
|
92
|
+
|
93
|
+
def digits(str)
|
94
|
+
return nil unless str
|
95
|
+
str.upcase.tr("IO", "10")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
module UKPostcode
|
4
|
+
|
5
|
+
# GiroPostcode models the single, peculiar GIR 0AA postcode originally used
|
6
|
+
# by Girobank.
|
7
|
+
#
|
8
|
+
# area, district, sector, and unit all return nil, because this postcode
|
9
|
+
# does not meaningfully possess these distinctions.
|
10
|
+
#
|
11
|
+
class GiroPostcode
|
12
|
+
include Singleton
|
13
|
+
|
14
|
+
PATTERN = /\A G[I1]R \s* [0O]AA \z/ix
|
15
|
+
|
16
|
+
# Returns an instance of GiroPostcode if str represents GIR 0AA, and nil
|
17
|
+
# otherwise.
|
18
|
+
#
|
19
|
+
def self.parse(str)
|
20
|
+
PATTERN.match(str.strip) ? instance : nil
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :area, :district, :sector, :unit
|
24
|
+
|
25
|
+
# The left-hand part of the postcode, always "GIR".
|
26
|
+
#
|
27
|
+
def outcode
|
28
|
+
"GIR"
|
29
|
+
end
|
30
|
+
|
31
|
+
# The right-hand part of the postcode, always "0AA".
|
32
|
+
#
|
33
|
+
def incode
|
34
|
+
"0AA"
|
35
|
+
end
|
36
|
+
|
37
|
+
# The canonical string representation of the postcode, i.e. "GIR 0AA".
|
38
|
+
#
|
39
|
+
def to_s
|
40
|
+
"GIR 0AA"
|
41
|
+
end
|
42
|
+
|
43
|
+
# GIR 0AA is always valid.
|
44
|
+
#
|
45
|
+
def valid?
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
# GIR 0AA is always full.
|
50
|
+
#
|
51
|
+
def full?
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
# GIR 0AA is in England. (In Bootle, in fact.)
|
56
|
+
#
|
57
|
+
def country
|
58
|
+
:england
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module UKPostcode
|
2
|
+
|
3
|
+
# InvalidPostcode is a singleton null object returned by UKPostcode.parse
|
4
|
+
# when it is unable to parse the supplied postcode.
|
5
|
+
#
|
6
|
+
# The sub-fields of the postcode (outcode, area, etc.) are all nil.
|
7
|
+
#
|
8
|
+
class InvalidPostcode
|
9
|
+
def self.parse(str)
|
10
|
+
new(str)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(input)
|
14
|
+
@input = input
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :area, :district, :sector, :unit, :incode, :outcode
|
18
|
+
|
19
|
+
# Returns the literal string supplied at initialisation. This may be
|
20
|
+
# helpful when returning erroneous input to the user.
|
21
|
+
#
|
22
|
+
def to_s
|
23
|
+
@input
|
24
|
+
end
|
25
|
+
|
26
|
+
def full?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def valid?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def country
|
35
|
+
:unknown
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module UKPostcode
|
2
|
+
class Tree
|
3
|
+
def initialize(tree = {})
|
4
|
+
@root = tree
|
5
|
+
end
|
6
|
+
|
7
|
+
def insert(path, value)
|
8
|
+
path[0..-2].inject(@root) { |n, p| n[p] ||= {} }[path.last] = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def compress
|
12
|
+
self.class.new(compress_node(@root))
|
13
|
+
end
|
14
|
+
|
15
|
+
def filter(leaf_value)
|
16
|
+
self.class.new(filter_node(@root, leaf_value))
|
17
|
+
end
|
18
|
+
|
19
|
+
def regexp
|
20
|
+
Regexp.new('^' + node_regexp(@root))
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
@root
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def leaf?(node)
|
30
|
+
node.is_a?(Symbol)
|
31
|
+
end
|
32
|
+
|
33
|
+
def contains_identical_leaves?(node)
|
34
|
+
node.values.all? { |t| leaf?(t) } && node.values.uniq.length == 1
|
35
|
+
end
|
36
|
+
|
37
|
+
def compress_node(node)
|
38
|
+
if leaf?(node)
|
39
|
+
node
|
40
|
+
else
|
41
|
+
comp = Hash[node.map { |k, v| [k, compress_node(v)] }]
|
42
|
+
contains_identical_leaves?(comp) ? comp.values.first : comp
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def filter_node(node, leaf_value)
|
47
|
+
if node == leaf_value
|
48
|
+
node
|
49
|
+
elsif leaf?(node)
|
50
|
+
nil
|
51
|
+
else
|
52
|
+
h = Hash[node.map { |k, v| [k, filter_node(v, leaf_value)] }.
|
53
|
+
select { |_, v| v }]
|
54
|
+
h.empty? ? nil : h
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def node_regexp(node)
|
59
|
+
if leaf?(node)
|
60
|
+
''
|
61
|
+
else
|
62
|
+
segments = node.map { |k, v| k + node_regexp(v) }
|
63
|
+
if segments.length > 1
|
64
|
+
if segments.all? { |s| s.length == 1 }
|
65
|
+
'[' + segments.join('') + ']'
|
66
|
+
else
|
67
|
+
'(?:' + segments.join('|') + ')'
|
68
|
+
end
|
69
|
+
else
|
70
|
+
segments.first
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/uk_postcode/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = "
|
1
|
+
module UKPostcode
|
2
|
+
VERSION = "2.0.0.alpha"
|
3
3
|
end
|