@sroussey/parse-address
Version:
International street address parser for 220 jurisdictions (US, CA + 218 countries and territories), with SEC EDGAR country-code resolution
153 lines • 7.28 kB
JavaScript
import { keepLastLocality } from "../_eu/localityChain.js";
/**
* Nigeria (NG) address configuration.
*
* Order: house/building NUMBER first, then the street name + trailing English
* type suffix ("12 Adeola Odeku Street") — GB/ZA-style number-first.
* Type: trailing English suffix (Street, Road, Avenue, Close, Crescent, ...).
* Many Nigerian roads are type-less ("Awolowo Way" has one, but a bare
* "Allen" / estate-road may not); type must be optional.
* Locality: an AREA / district (Victoria Island, Ikoyi, Wuse II, GRA, ...)
* usually sits between the street and the routing city. The engine has a
* single city slot, so — as in ZA/IN — the comma chain is kept together
* and the leading area(s) are dropped in postNormalize, keeping the last
* locality (the city) as `city`.
* Postcode: 6-digit NIPOST code, written AFTER the city (after-city), and VERY
* frequently OMITTED (introduced 2000s, low real-world adoption). Optional.
* State: the 36 states + FCT (Lagos, Rivers, Kano, ...), written after the city,
* commonly with a trailing word "State" ("Lagos State"). May appear
* BEFORE the postcode (no code) via the county slot, or AFTER the postcode
* ("... Lagos 101241, Lagos State") via a helper `ng_state` group folded
* in postNormalize. Output state is a 2-letter code (LA, RI, KN, ...).
* PO Box: extremely common ("P.O. Box 1234, Marina, Lagos"); the box replaces
* the thoroughfare.
*
* Sources: NIPOST; UPU S42 NG template; Smarty / PostGrid / GeoPostcodes NG
* address guides; Universal Postal Union postcode note. See research-ng.md.
*/
const TYPES = [
"Street", "Road", "Avenue", "Close", "Crescent", "Boulevard", "Drive",
"Lane", "Way", "Court", "Place", "Terrace", "Walk", "Gardens", "Circle",
"Ring", "Loop", "Link", "Rise", "Grove", "Row", "Park", "Mews", "Hill",
"Highway", "Expressway", "Crescent",
// abbreviations
"St", "Rd", "Ave", "Av", "Dr", "Cl", "Cres", "Blvd", "Hwy",
];
const TYPE_SHORT = {
street: "ST", st: "ST",
road: "RD", rd: "RD",
avenue: "AVE", ave: "AVE", av: "AVE",
close: "CL", cl: "CL",
crescent: "CRES", cres: "CRES",
boulevard: "BLVD", blvd: "BLVD",
drive: "DR", dr: "DR",
lane: "LN",
way: "WAY",
court: "CT",
place: "PL",
terrace: "TER",
walk: "WLK",
gardens: "GDNS",
circle: "CIR",
ring: "RING",
loop: "LOOP",
link: "LINK",
rise: "RISE",
grove: "GR",
row: "ROW",
park: "PARK",
mews: "MEWS",
hill: "HL",
highway: "HWY", hwy: "HWY",
expressway: "EXPY",
};
// 36 states + FCT. `[name]` and `[name] State` are both accepted; the map is
// keyed by both the bare name and the "... state" form so either normalises to
// the 2-letter code.
const STATE_CODES = [
["Abia", "AB"], ["Adamawa", "AD"], ["Akwa Ibom", "AK"], ["Anambra", "AN"],
["Bauchi", "BA"], ["Bayelsa", "BY"], ["Benue", "BE"], ["Borno", "BO"],
["Cross River", "CR"], ["Delta", "DE"], ["Ebonyi", "EB"], ["Edo", "ED"],
["Ekiti", "EK"], ["Enugu", "EN"], ["Gombe", "GO"], ["Imo", "IM"],
["Jigawa", "JI"], ["Kaduna", "KD"], ["Kano", "KN"], ["Katsina", "KT"],
["Kebbi", "KE"], ["Kogi", "KO"], ["Kwara", "KW"], ["Lagos", "LA"],
["Nasarawa", "NA"], ["Niger", "NI"], ["Ogun", "OG"], ["Ondo", "ON"],
["Osun", "OS"], ["Oyo", "OY"], ["Plateau", "PL"], ["Rivers", "RI"],
["Sokoto", "SO"], ["Taraba", "TA"], ["Yobe", "YO"], ["Zamfara", "ZM"],
["FCT", "FC"], ["Abuja", "FC"], ["Federal Capital Territory", "FC"],
];
const REGION_MAP = {};
for (const [name, code] of STATE_CODES) {
REGION_MAP[name.toLowerCase()] = code;
REGION_MAP[`${name.toLowerCase()} state`] = code;
}
// State/county alternation. A bare state name (Lagos, Kano, Rivers) is IDENTICAL
// to a major city name, so matching it as the region would eat the city. We
// therefore only recognise the EXPLICIT region forms — "<Name> State" and the
// capital-territory markers (FCT / Federal Capital Territory) — leaving a bare
// "Lagos" to be kept as the routing city. (A region written as a bare name that
// duplicates the city is a documented failure mode; see research-ng.md.)
const STATE_NAMES = STATE_CODES.map(([n]) => n).filter((n) => n !== "FCT" && n !== "Abuja" && n !== "Federal Capital Territory");
const COUNTY_ALT = [
...STATE_NAMES.map((n) => `${n.replace(/ /g, "\\s+")}\\s+State`),
"Federal\\s+Capital\\s+Territory",
"FCT",
]
.sort((a, b) => b.length - a.length)
.join("|");
export const ngConfig = {
code: "ng",
country: "NG",
countryNames: ["Nigeria", "Federal Republic of Nigeria", "NGA", "NG"],
order: "number-street",
typePlacement: "suffix",
postalPlacement: "after-city",
normalizeTypeCase: false,
// 6-digit NIPOST code, then an OPTIONAL trailing state ("101241, Lagos State"),
// captured in a helper group folded to `state` in postNormalize.
postalPattern: `(?<postal_code>\\d{6})(?:[\\s,]+(?<ng_state>${COUNTY_ALT}))?`,
// number: plain integer or block form ("A3/11" -> keep "3/11" after the
// optional letter is handled elsewhere; here "12", "1/3", "5-7"), optional
// glued letter suffix, optional "No." lead-in.
houseNumberPattern: "(?:No\\.?\\s*|\\#\\s*)?(?<number>\\d+(?:[-/]\\d+)?)(?<civic_number_suffix>[A-Za-z](?![A-Za-z]))?",
types: TYPES,
typeDisplayMap: {},
typeShortCodeMap: TYPE_SHORT,
// Keep the area+city locality chain together; a real state (no postcode form)
// lands in the county slot before the country.
cityAllowsCommas: true,
countyPattern: `(?:${COUNTY_ALT})`,
regionMap: REGION_MAP,
// Flat / Suite / Block / Floor / Shop lead the address (as in GB/ZA).
secUnitPlacement: "before",
secUnitPattern: "(?<sec_unit_type>Flat|Apartment|Apt\\.?|Suite|Block|Floor|Room|Shop|Unit|House)\\.?\\s*(?<sec_unit_num>[\\w-]+)?",
secUnitDisplayMap: {
flat: "Flat", apartment: "Apartment", apt: "Apt", suite: "Suite",
block: "Block", floor: "Floor", room: "Room", shop: "Shop", unit: "Unit",
house: "House",
},
// A leading building/plaza name ("Churchgate Tower, 30 Afribank Street, ...").
buildingKeywords: [
"Plaza", "Tower", "Towers", "House", "Building", "Centre", "Center",
"Complex", "Court", "Mall", "Estate",
],
poBoxNames: ["P.O. Box", "PO Box", "P M B", "P.M.B.", "PMB", "Post Box"],
poBoxDisplayMap: {
"p.o. box": "PO Box", "po box": "PO Box", "p m b": "PMB",
"p.m.b.": "PMB", pmb: "PMB", "post box": "PO Box",
},
// Fold the helper `ng_state` (state after the postcode) into `state`, and
// collapse the "area, city" chain to the routing city + dropped area(s).
postNormalize: (parsed) => {
var _a;
if (typeof parsed.ng_state === "string" && parsed.ng_state) {
const key = parsed.ng_state.toLowerCase().replace(/\s+/g, " ").trim();
if (!parsed.state)
parsed.state = (_a = REGION_MAP[key]) !== null && _a !== void 0 ? _a : parsed.ng_state;
delete parsed.ng_state;
}
keepLastLocality(parsed);
},
};
export default ngConfig;
//# sourceMappingURL=config.js.map