uk_postcode 1.0.1 → 2.0.0.alpha
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.
- 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
|