1 module postal;
2 
3 import std.stdio;
4 
5 struct PostalAddress {
6 	string house; /// venue name e.g. "Brooklyn Academy of Music", and building names e.g. "Empire State Building"
7 	string category; /// for category queries like "restaurants", etc.
8 	string near; /// phrases like "in", "near", etc. used after a category phrase to help with parsing queries like "restaurants in Brooklyn"
9 	string houseNumber; /// usually refers to the external (street-facing) building number. In some countries this may be a compount, hyphenated number which also includes an apartment number, or a block number (a la Japan), but libpostal will just call it the house_number for simplicity.
10 	string road; /// street name(s)
11 	string unit; /// an apartment, unit, office, lot, or other secondary unit designator
12 	string level; /// expressions indicating a floor number e.g. "3rd Floor", "Ground Floor", etc.
13 	string staircase; /// numbered/lettered staircase
14 	string entrance; /// numbered/lettered entrance
15 	string poBox; /// post office box: typically found in non-physical (mail-only) addresses
16 	string postcode; /// postal codes used for mail sorting
17 	string suburb; /// usually an unofficial neighborhood name like "Harlem", "South Bronx", or "Crown Heights"
18 	string cityDistrict; /// these are usually boroughs or districts within a city that serve some official purpose e.g. "Brooklyn" or "Hackney" or "Bratislava IV"
19 	string city; /// any human settlement including cities, towns, villages, hamlets, localities, etc.
20 	string island; /// named islands e.g. "Maui"
21 	string stateDistrict; /// usually a second-level administrative division or county.
22 	string state; /// a first-level administrative division. Scotland, Northern Ireland, Wales, and England in the UK are mapped to "state" as well (convention used in OSM, GeoPlanet, etc.)
23 	string countryRegion; /// informal subdivision of a country without any political status
24 	string country; /// sovereign nations and their dependent territories, anything with an ISO-3166 code.
25 	string worldRegion; /// currently only used for appending “West Indies” after the country name, a pattern frequently used in the English-speaking Caribbean e.g. “Jamaica, West Indies”
26 }
27 
28 struct Postal {
29 @safe:
30 	import std.array : empty, front;
31 	import std.string : fromStringz, toStringz;
32 	import libpostal;
33 	static bool hasBeenInited;
34 
35 	private static void checkInit() {
36 		if(!hasBeenInited) {
37 			bool initResult = () @trusted {
38 				return !libpostal_setup() || !libpostal_setup_parser();
39 			}();
40 
41 			if(initResult) {
42 				throw new Exception("Failed to init libpostal");
43 			}
44 			hasBeenInited = true;
45 		}
46 	}	
47 
48 	static PostalAddress parse(string input, string language = "", 
49 			string country = "") @trusted
50 	{
51 		import std.format : format;
52 		import std.conv : to;
53 		checkInit();
54 		libpostal_address_parser_options opts = libpostal_get_address_parser_default_options();
55 		if(!language.empty) {
56 			opts.language = cast(char*)language.toStringz();
57 		}
58 		if(!country.empty) {
59 			opts.country = cast(char*)country.toStringz();
60 		}
61 
62 		libpostal_address_parser_response_t* parsed = libpostal_parse_address(
63 				cast(char*)input.toStringz(), opts);
64 
65 		PostalAddress ret;
66 
67 		for(size_t i = 0; i < parsed.num_components; i++) {
68 			string label = to!string(parsed.labels[i].fromStringz());
69 			string comp = to!string(parsed.components[i].fromStringz());
70 			switch(label) {
71 				case "house": ret.house = comp; break;
72 				case "category": ret.category = comp; break;
73 				case "near": ret.near = comp; break;
74 				case "house_number": ret.houseNumber = comp; break;
75 				case "roat": ret.road = comp; break;
76 				case "unit": ret.unit = comp; break;
77 				case "level": ret.level = comp; break;
78 				case "staircase": ret.staircase = comp; break;
79 				case "entrance": ret.entrance = comp; break;
80 				case "po_box": ret.poBox = comp; break;
81 				case "postcode": ret.postcode = comp; break;
82 				case "road": ret.road = comp; break;
83 				case "suburb": ret.suburb = comp; break;
84 				case "city_district": ret.cityDistrict = comp; break;
85 				case "city": ret.city = comp; break;
86 				case "island": ret.island = comp; break;
87 				case "state_district": ret.stateDistrict = comp; break;
88 				case "state": ret.state = comp; break;
89 				case "country_region": ret.countryRegion = comp; break;
90 				case "country": ret.country = comp; break;
91 				case "world_region": ret.worldRegion = comp; break;
92 				default:
93 					throw new Exception(format(
94 						"Unhandled address label '%s' with value '%s'", label,
95 						comp));
96 
97 			}
98 		}
99 
100     	libpostal_address_parser_response_destroy(parsed);
101 		return ret;
102 	}
103 }
104 
105 unittest {
106 	auto adr = Postal.parse("Quatre vingt douze Ave des Champs-Élysées, Paris,
107 			France", "French", "France");
108 	writeln(adr);
109 }
110 
111 unittest {
112 	auto adr = Postal.parse("66-34 Eskdale Cl, Wembley, London");
113 	writeln(adr);
114 }