// 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);
}
}
}
}