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 }