// Spludlow Software // Copyright © Samuel P. Ludlow 2020 All Rights Reserved // Distributed under the terms of the GNU General Public License version 3 // Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE // https://www.spludlow.co.uk/LICENCE.TXT // The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission // v1.14 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data; namespace Spludlow { /// /// UK Post code stuff /// public class Postcodes { private static string CacheName = "Spludlow.Postcodes"; private class State { public List Formats; public Dictionary AreaNames; public Dictionary> AreasDistricts; public List Districts; } private static State _State; static Postcodes() { _State = (State)Spludlow.Caching.Get(CacheName); if (_State == null) { _State = new State(); _State.Formats = new List(new string[] { "A9 9AA", "A99 9AA", "AA9 9AA", "AA99 9AA", "A9A 9AA", "AA9A 9AA", }); _State.AreaNames = new Dictionary(); _State.AreasDistricts = new Dictionary>(); _State.Districts = new List(); DataTable areasTable = Spludlow.Data.TextTable.ReadFile(Spludlow.Config.ProgramData + @"\Data\PostCodeAreas.txt"); foreach (DataRow row in areasTable.Rows) { string areaCode = (string)row["AreaCode"]; string postTown = (string)row["PostTown"]; _State.AreaNames.Add(areaCode, postTown); } DataTable districtsTable = Spludlow.Data.TextTable.ReadFile(Spludlow.Config.ProgramData + @"\Data\PostCodeDistricts.txt"); foreach (DataRow row in districtsTable.Rows) { string districtCode = (string)row["DistrictCode"]; string areaCode = AreaCode(districtCode); if (_State.AreasDistricts.ContainsKey(areaCode) == false) _State.AreasDistricts.Add(areaCode, new List()); _State.AreasDistricts[areaCode].Add(districtCode); _State.Districts.Add(districtCode); } Spludlow.Caching.Set(CacheName, _State); } } public static string[] AreaCodes { get { return _State.AreaNames.Keys.ToArray(); } } public static string[] DistrictCodes { get { return _State.Districts.ToArray(); } } public static string[] DistrictsInArea(string areaCode) { if (_State.AreasDistricts.ContainsKey(areaCode) == false) throw new ApplicationException("Postcodes, DistrictsInArea; Area does not exist: " + areaCode); return _State.AreasDistricts[areaCode].ToArray(); } public static string AreaName(string areaCode) { if (_State.AreaNames.ContainsKey(areaCode) == false) throw new ApplicationException("Postcodes, AreaName; Area does not exist: " + areaCode); return _State.AreaNames[areaCode]; } public static bool AreaExists(string areaCode) { return (_State.AreaNames.ContainsKey(areaCode) == true); } public static bool DistrictExists(string districtCode) { return (_State.Districts.Contains(districtCode) == true); } public static string Tidy(string postCode) { string postCodeNew = postCode.Trim().ToUpper().Replace(" ", ""); int length = postCodeNew.Length; if (length < 5 || length > 7) return postCode; StringBuilder result = new StringBuilder(); result.Append(postCodeNew.Substring(0, length - 3)); result.Append(" "); result.Append(postCodeNew.Substring(length - 3, 3)); return result.ToString(); } public static bool Valid(string postCode) { if (Validation(postCode) == "") return true; return false; } public static string ValidReason(string postCode) { return Validation(postCode); } private static string Validation(string postCode) { int length = postCode.Length; if (length < 6 || length > 8) return "Length"; StringBuilder template = new StringBuilder(); foreach (char ch in postCode) { if (ch == ' ') { template.Append(' '); continue; } if (Char.IsDigit(ch) == true) { template.Append('9'); continue; } if (Char.IsLetter(ch) == true) { template.Append('A'); continue; } return "Character"; } if (_State.Formats.Contains(template.ToString()) == false) return "Format"; string areaCode = AreaCode(postCode); if (areaCode == null) return "Area"; string districtCode = DistrictCode(postCode); if (districtCode == null) return "District"; return ""; } /// /// area code is the starting letters of outward code (before the space) /// you can pass just the outward code /// area code is validated to check it exists /// returns null if any problems /// public static string AreaCode(string postcode) { if (postcode.Length < 2) throw new ApplicationException("Postcodes, AreaCode; Postcode or outward code too short: " + postcode); string areaCode; if (Char.IsDigit(postcode[1]) == true) areaCode = postcode.Substring(0, 1); else areaCode = postcode.Substring(0, 2); if (AreaExists(areaCode) == false) return null; return areaCode; } /// /// starting letters and numbers (outward code) before space /// district code is validated to check it exists /// returns null if any problems /// public static string DistrictCode(string postcode) // outward code { string districtCode = OutwardCode(postcode); if (DistrictExists(districtCode) == false) return null; return districtCode; } public static string OutwardCode(string postCode) // before space { int index = postCode.IndexOf(' '); if (index == -1) return null; return postCode.Substring(0, index); } public static string InwardCode(string postCode) // after space { int index = postCode.LastIndexOf(' '); if (index == -1 || (index + 1) == postCode.Length) return null; return postCode.Substring(index + 1); } public static void Test(string[] postcodes) { Spludlow.Log.Report("AreaCodes", Spludlow.Postcodes.AreaCodes); Spludlow.Log.Report("DistrictCodes", Spludlow.Postcodes.DistrictCodes); DataTable table = Spludlow.Data.TextTable.ReadText(new string[] { "AreaCode AreaName Districts", "String String String", }); foreach (string areaCode in Spludlow.Postcodes.AreaCodes) { StringBuilder districts = new StringBuilder(); foreach (string district in Spludlow.Postcodes.DistrictsInArea(areaCode)) { if (districts.Length > 0) districts.Append(", "); districts.Append(district); } table.Rows.Add(areaCode, Spludlow.Postcodes.AreaName(areaCode), districts.ToString()); } Spludlow.Log.Report("Areas", table); if (postcodes != null) { table = Spludlow.Data.TextTable.ReadText(new string[] { "RawPostcode Postcode Valid Fail AreaCode AreaName", "String String Boolean String String String", }); foreach (string rawPostcode in postcodes) { string postcode = Spludlow.Postcodes.Tidy(rawPostcode); bool valid = Spludlow.Postcodes.Valid(postcode); string fail = ""; string areaName = ""; string areaCode = ""; if (valid == true) areaCode = Spludlow.Postcodes.AreaCode(postcode); else fail = Spludlow.Postcodes.ValidReason(postcode); if (areaCode != "") areaName = Spludlow.Postcodes.AreaName(areaCode); table.Rows.Add(rawPostcode, postcode, valid, fail, areaCode, areaName); } DataView view; view = new DataView(table); view.RowFilter = "Valid = false"; view.Sort = "Fail"; Spludlow.Log.Report("Postcodes Not Valid", view); view = new DataView(table); view.RowFilter = "Valid = true"; view.Sort = "Postcode"; Spludlow.Log.Report("Postcodes Valid", view); } } } }