Как разобрать произвольную улицу / почтовый адрес из текста и по компонентам
Мы ведем бизнес в основном в Соединенных Штатах и пытаемся улучшить взаимодействие с пользователем, объединяя все поля адреса в одну текстовую область. Но есть несколько проблем:
- Адрес, который вводит пользователь, может быть неправильным или в стандартном формате.
- Адрес должен быть разделен на части (улица, город, штат и т. Д.) Для обработки платежей по кредитным картам.
- Пользователи могут вводить не только свой адрес (например, свое имя или компанию с ним)
- Google может сделать это, но Условия предоставления услуг и ограничения запросов являются непомерными, особенно при ограниченном бюджете
Видимо, это общий вопрос:
- PHP скрипт для разбора адреса?
- Как мне разобрать адрес в свободном формате для сохранения в базе данных
- парсер почтовых адресов Java
- Более эффективный способ извлечения компонентов адреса
- Как я могу показать предварительно заполненный почтовый адрес на экране контактов с улицы, города, почтовый индекс на Android
- PHP регулярное выражение адреса США
Есть ли способ изолировать адрес от текста вокруг него и разбить его на части? Есть ли регулярное выражение для разбора адресов?
12 ответов
Я много видел этот вопрос, когда работал в компании по проверке адресов. Я публикую ответ здесь, чтобы сделать его более доступным для программистов, которые ищут тот же вопрос. Компания, с которой я работал, обработала миллиарды адресов, и мы многому научились.
Во-первых, нам нужно понять несколько вещей об адресах.
Адреса не регулярные
Это означает, что регулярные выражения отсутствуют. Я видел все, от простых регулярных выражений, которые соответствуют адресам в очень специфическом формате, до этого:
/ \ S +(\ д {2,5}\ S +)([а | р]?! Т \ б)(([A-Za-Z |\ S +]{1,5}){1,2})?([\ S |\ |.]+)?(([A-Za-Z |\ S +]{1,30}){1,4})(суд | кт | улица | й | привод | др | пер | пер | дорога | й | б-р)([\ S |\,| |\;.]? +)(([A-Za-Z |\ S +]{1,30}){1,2})([\ S |\ |.] +) \ б (AK|? AL|AR|AZ|CA|CO|CT|DC|DE|FL|GA|GU|HI|IA|ID|IL|IN|KS|KY|LA|MA|MD|ME|MI|MN| МО |MS|MT|NC|ND|NE|NH|NJ|NM|NV|NY|OH|OK| ИЛИ |PA|RI|SC|SD|TN| ТХ | УТ | ВА |VI| ВТ | ВД |WI|WV| Вайоминг)([\s|\,|.]? +)(\s+\ д {5})([\ s |\ |.]+)/ я
... к этому, где файл класса 900+ генерирует сверхмассивное регулярное выражение на лету, чтобы соответствовать еще больше. Я не рекомендую это (например, вот скрипка вышеупомянутого регулярного выражения, которая делает много ошибок). Нет простой магической формулы, чтобы заставить это работать. Теоретически и теоретически невозможно сопоставить адреса с регулярным выражением.
Публикация USPS 28 документирует множество возможных форматов адресов со всеми их ключевыми словами и вариантами. Хуже всего то, что адреса часто неоднозначны. Слова могут означать больше, чем одно ("Святой" может быть "Святой" или "Улица"), и есть слова, которые, я уверен, они изобрели. (Кто знал, что "Стрэйвен" был уличным суффиксом?)
Вам понадобится код, который действительно понимает адреса, и если этот код существует, это коммерческая тайна. Но вы, вероятно, могли бы свернуть свои собственные, если вы действительно в этом.
Адреса бывают неожиданных форм и размеров
Вот некоторые придуманные (но полные) адреса:
1) 102 main street
Anytown, state
2) 400n 600e #2, 52173
3) p.o. #104 60203
Даже они, возможно, действительны:
4) 829 LKSDFJlkjsdflkjsdljf Bkpw 12345
5) 205 1105 14 90210
Очевидно, что они не стандартизированы. Пунктуация и переносы строк не гарантируются. Вот что происходит:
Номер 1 завершен, потому что он содержит адрес улицы и город и штат. С этой информацией достаточно идентифицировать адрес, и его можно считать "доставляемым" (с некоторой стандартизацией).
Номер 2 завершен, поскольку он также содержит адрес улицы (с дополнительным номером / номером единицы) и 5-значный почтовый индекс, которого достаточно для идентификации адреса.
Номер 3 - это полный формат почтового ящика, так как он содержит почтовый индекс.
Номер 4 также завершен, потому что почтовый индекс является уникальным, что означает, что частное лицо или корпорация приобрели это адресное пространство. Уникальный почтовый индекс предназначен для больших или концентрированных мест доставки. Все, что адресовано почтовому индексу 12345, отправляется в компанию General Electric в Скенектади, штат Нью-Йорк. Этот пример никому конкретно не дойдет, но USPS все равно сможет его доставить.
Номер 5 также завершен, хотите верьте, хотите нет. Только с этими числами полный адрес может быть обнаружен при анализе в базе данных всех возможных адресов. Заполнение пропущенных указателей, вторичного обозначения и кода ZIP+4 тривиально, когда вы видите каждое число как компонент. Вот как это выглядит, полностью расширено и стандартизировано:
205 N 1105 Вт, кв. 14
Беверли Хиллз CA 90210-5221
Данные адреса не ваши
В большинстве стран, которые предоставляют официальные адресные данные лицензированным поставщикам, сами адресные данные принадлежат руководящему органу. В США USPS владеет адресами. То же самое верно для Канадской Почты, Королевской Почты и других, хотя в каждой стране право собственности определяется или определяется по-своему. Знание этого важно, так как обычно оно запрещает обратный инжиниринг базы данных адресов. Вы должны быть осторожны, как получать, хранить и использовать данные.
Карты Google - это обычное средство для быстрого исправления адресов, но TOS довольно запредельный; например, вы не можете использовать их данные или API-интерфейсы без отображения карты Google и только для некоммерческих целей (если вы не платите), а также вы не можете хранить данные (кроме временного кэширования). Имеет смысл. Данные Google являются одними из лучших в мире. Однако Google Maps не проверяет адрес. Если адрес не существует, он все равно покажет вам, где был бы адрес , если бы он существовал (попробуйте на своей улице; используйте номер дома, который, как вы знаете, не существует). Это иногда полезно, но помните об этом.
Политика использования Nominatim также ограничивает, особенно для больших объемов и коммерческого использования, и данные в основном берутся из бесплатных источников, поэтому они не так хорошо поддерживаются (такова природа открытых проектов) - однако, это может все же подойти твои нужды. Это поддерживается большим сообществом.
У самого USPS есть API, но он сильно портится и не имеет никаких гарантий и поддержки. Это также может быть трудно использовать. Некоторые люди используют его экономно, без проблем. Но легко не заметить, что USPS требует, чтобы вы использовали их API только для подтверждения адресов для отправки через них.
Люди ожидают, что адреса будут трудными
К сожалению, мы заставили наше общество ожидать, что адреса будут сложными. В интернете есть десятки хороших статей о UX, но на самом деле, если у вас есть форма адреса с отдельными полями, это то, чего ожидают пользователи, даже если это усложняет работу с адресами в крайнем случае, которые не соответствуют отформатируйте ожидаемую форму, или, возможно, для формы требуется поле, которого не должно быть. Или пользователи не знают, где разместить определенную часть своего адреса.
Я мог бы продолжать и говорить о плохой UX форм оформления заказа в эти дни, но вместо этого я просто скажу, что объединение адресов в одно поле будет долгожданным изменением - люди смогут печатать свои адреса так, как они считают нужным вместо того, чтобы пытаться выяснить вашу длинную форму. Тем не менее, это изменение будет неожиданным, и пользователи могут сначала найти его немного резким. Просто знайте об этом.
Частично эту боль можно облегчить, если указать поле страны перед адресом. Когда они сначала заполняют поле страны, вы знаете, как сделать так, чтобы ваша форма отображалась. Возможно, у вас есть хороший способ справиться с адресами в США из одного поля, поэтому, если они выбирают Соединенные Штаты, вы можете свести форму к одному полю, в противном случае отобразить поля компонента. Просто вещи для размышления!
Теперь мы знаем, почему это сложно; Что вы можете с этим поделать?
USPS лицензирует поставщиков через процесс, называемый CASS™ Certification, для предоставления проверенных адресов клиентам. Эти поставщики имеют доступ к базе данных USPS, обновляемой ежемесячно. Их программное обеспечение должно соответствовать строгим стандартам, чтобы быть сертифицированным, и они не часто требуют согласия с такими ограничительными условиями, как обсуждалось выше.
Существует много компаний, сертифицированных CASS, которые могут обрабатывать списки или иметь API: Melissa Data, Experian QAS и SmartyStreets.
(В связи с тем, что я получил "рекламу", я обрезал свой ответ на этом этапе. Вам решать, какое решение подходит вам.)
Правда: действительно, ребята, я не работаю ни в одной из этих компаний. Это не реклама.
libpostal: библиотека с открытым исходным кодом для анализа адресов, обучение работе с данными из OpenStreetMap, OpenAddresses и OpenCage.
https://github.com/openvenues/libpostal ( дополнительная информация об этом)
Другие инструменты / услуги:
http://www.gisgraphy.com/ Бесплатный веб- сервис с открытым исходным кодом и готовый к использованию геокодер и геолокализация, включая OpenStreetMap, GeoNames и Quattroshapes.
https://github.com/kodapan/osm-common Библиотека для доступа к службам OpenStreetMap, анализа и обработки данных.
Есть много парсеров адресов. Они бывают двух основных видов: те, в которых есть базы данных названий мест и улиц, и те, которые не имеют.
Синтаксический анализатор уличных адресов с регулярным выражением может достигать 95% успеха без особых проблем. Тогда вы начинаете поражать необычные случаи. Perl в CPAN, "Geo::StreetAddress::US", примерно так хорош. Есть порты Python и Javascript, все с открытым исходным кодом. У меня есть улучшенная версия в Python, которая немного увеличивает вероятность успеха, обрабатывая больше случаев. Однако, чтобы получить последние 3%, вам нужны базы данных, чтобы помочь в устранении неоднозначности.
База данных с 3-значными почтовыми индексами, а также названиями и сокращениями штатов США очень помогает. Когда анализатор видит непротиворечивый почтовый индекс и название штата, он может начать привязываться к формату. Это очень хорошо работает для США и Великобритании.
Правильный синтаксический анализ адреса начинается с конца и работает в обратном направлении. Вот как это делают системы USPS. Адреса наименее неоднозначны в конце, где названия стран, названия городов и почтовые индексы относительно легко распознать. Названия улиц обычно могут быть изолированными. Места на улицах наиболее сложны для разбора; там вы встретите такие вещи, как "Пятый этаж" и "Стейплс Павильон". Вот когда база данных очень помогает.
ОБНОВЛЕНИЕ: Geocode.xyz теперь работает по всему миру. Для примеров смотрите https://geocode.xyz/
Для США, Мексики и Канады см. http://geocoder.ca/.
Например:
Входные данные: что-то происходит возле пересечения главной улицы и Артура, убивают Нью-Йорк
Выход:
<geodata> <latt>40.5123510000</latt> <longt>-74.2500500000</longt> <AreaCode>347,718</AreaCode> <TimeZone>America/New_York</TimeZone> <standard> <street1>main</street1> <street2>arthur kill</street2> <stnumber/> <staddress/> <city>STATEN ISLAND</city> <prov>NY</prov> <postal>11385</postal> <confidence>0.9</confidence> </standard> </geodata>
Вы также можете проверить результаты в веб-интерфейсе или получить вывод в виде Json или Jsonp. например. Я ищу рестораны на главной улице 123, Нью-Йорк
Нет кода? Стыдно!
Вот простой анализатор адресов JavaScript. Это довольно ужасно по каждой причине, которую Мэтт приводит в своей вышеупомянутой диссертации (с которой я почти на 100% согласен: адреса - это сложные типы, а люди совершают ошибки; лучше аутсорсировать и автоматизировать это - когда вы можете себе это позволить).
Но вместо того, чтобы плакать, я решил попробовать:
Этот код работает нормально для анализа большинства результатов Esri для findAddressCandidate
а также с некоторыми другими (обратными) геокодерами, которые возвращают однострочный адрес, где улица / город / штат разделены запятыми. Вы можете расширить, если хотите, или написать парсер для конкретной страны. Или просто используйте это как пример того, насколько сложным может быть это упражнение или насколько я паршив в JavaScript. Я признаю, что потратил на это всего около тридцати минут (будущие итерации могут добавить кеши, проверку zip и поиск состояний, а также контекст местоположения пользователя), но это сработало для моего варианта использования: конечный пользователь видит форму, которая анализирует ответ на поиск геокода в 4 Textboxes. Если синтаксический анализ адресов происходит неправильно (что бывает редко, если исходные данные были плохими), это не имеет большого значения - пользователь может проверить и исправить это! (Но для автоматизированных решений может либо отбросить / игнорировать, либо пометить как ошибку, поэтому dev может либо поддержать новый формат, либо исправить исходные данные.)
/*
address assumptions:
- US addresses only (probably want separate parser for different countries)
- No country code expected.
- if last token is a number it is probably a postal code
-- 5 digit number means more likely
- if last token is a hyphenated string it might be a postal code
-- if both sides are numeric, and in form #####-#### it is more likely
- if city is supplied, state will also be supplied (city names not unique)
- zip/postal code may be omitted even if has city & state
- state may be two-char code or may be full state name.
- commas:
-- last comma is usually city/state separator
-- second-to-last comma is possibly street/city separator
-- other commas are building-specific stuff that I don't care about right now.
- token count:
-- because units, street names, and city names may contain spaces token count highly variable.
-- simplest address has at least two tokens: 714 OAK
-- common simple address has at least four tokens: 714 S OAK ST
-- common full (mailing) address has at least 5-7:
--- 714 OAK, RUMTOWN, VA 59201
--- 714 S OAK ST, RUMTOWN, VA 59201
-- complex address may have a dozen or more:
--- MAGICICIAN SUPPLY, LLC, UNIT 213A, MAGIC TOWN MALL, 13 MAGIC CIRCLE DRIVE, LAND OF MAGIC, MA 73122-3412
*/
var rawtext = $("textarea").val();
var rawlist = rawtext.split("\n");
function ParseAddressEsri(singleLineaddressString) {
var address = {
street: "",
city: "",
state: "",
postalCode: ""
};
// tokenize by space (retain commas in tokens)
var tokens = singleLineaddressString.split(/[\s]+/);
var tokenCount = tokens.length;
var lastToken = tokens.pop();
if (
// if numeric assume postal code (ignore length, for now)
!isNaN(lastToken) ||
// if hyphenated assume long zip code, ignore whether numeric, for now
lastToken.split("-").length - 1 === 1) {
address.postalCode = lastToken;
lastToken = tokens.pop();
}
if (lastToken && isNaN(lastToken)) {
if (address.postalCode.length && lastToken.length === 2) {
// assume state/province code ONLY if had postal code
// otherwise it could be a simple address like "714 S OAK ST"
// where "ST" for "street" looks like two-letter state code
// possibly this could be resolved with registry of known state codes, but meh. (and may collide anyway)
address.state = lastToken;
lastToken = tokens.pop();
}
if (address.state.length === 0) {
// check for special case: might have State name instead of State Code.
var stateNameParts = [lastToken.endsWith(",") ? lastToken.substring(0, lastToken.length - 1) : lastToken];
// check remaining tokens from right-to-left for the first comma
while (2 + 2 != 5) {
lastToken = tokens.pop();
if (!lastToken) break;
else if (lastToken.endsWith(",")) {
// found separator, ignore stuff on left side
tokens.push(lastToken); // put it back
break;
} else {
stateNameParts.unshift(lastToken);
}
}
address.state = stateNameParts.join(' ');
lastToken = tokens.pop();
}
}
if (lastToken) {
// here is where it gets trickier:
if (address.state.length) {
// if there is a state, then assume there is also a city and street.
// PROBLEM: city may be multiple words (spaces)
// but we can pretty safely assume next-from-last token is at least PART of the city name
// most cities are single-name. It would be very helpful if we knew more context, like
// the name of the city user is in. But ignore that for now.
// ideally would have zip code service or lookup to give city name for the zip code.
var cityNameParts = [lastToken.endsWith(",") ? lastToken.substring(0, lastToken.length - 1) : lastToken];
// assumption / RULE: street and city must have comma delimiter
// addresses that do not follow this rule will be wrong only if city has space
// but don't care because Esri formats put comma before City
var streetNameParts = [];
// check remaining tokens from right-to-left for the first comma
while (2 + 2 != 5) {
lastToken = tokens.pop();
if (!lastToken) break;
else if (lastToken.endsWith(",")) {
// found end of street address (may include building, etc. - don't care right now)
// add token back to end, but remove trailing comma (it did its job)
tokens.push(lastToken.endsWith(",") ? lastToken.substring(0, lastToken.length - 1) : lastToken);
streetNameParts = tokens;
break;
} else {
cityNameParts.unshift(lastToken);
}
}
address.city = cityNameParts.join(' ');
address.street = streetNameParts.join(' ');
} else {
// if there is NO state, then assume there is NO city also, just street! (easy)
// reasoning: city names are not very original (Portland, OR and Portland, ME) so if user wants city they need to store state also (but if you are only ever in Portlan, OR, you don't care about city/state)
// put last token back in list, then rejoin on space
tokens.push(lastToken);
address.street = tokens.join(' ');
}
}
// when parsing right-to-left hard to know if street only vs street + city/state
// hack fix for now is to shift stuff around.
// assumption/requirement: will always have at least street part; you will never just get "city, state"
// could possibly tweak this with options or more intelligent parsing&sniffing
if (!address.city && address.state) {
address.city = address.state;
address.state = '';
}
if (!address.street) {
address.street = address.city;
address.city = '';
}
return address;
}
// get list of objects with discrete address properties
var addresses = rawlist
.filter(function(o) {
return o.length > 0
})
.map(ParseAddressEsri);
$("#output").text(JSON.stringify(addresses));
console.log(addresses);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea>
27488 Stanford Ave, Bowden, North Dakota
380 New York St, Redlands, CA 92373
13212 E SPRAGUE AVE, FAIR VALLEY, MD 99201
1005 N Gravenstein Highway, Sebastopol CA 95472
A. P. Croll & Son 2299 Lewes-Georgetown Hwy, Georgetown, DE 19947
11522 Shawnee Road, Greenwood, DE 19950
144 Kings Highway, S.W. Dover, DE 19901
Intergrated Const. Services 2 Penns Way Suite 405, New Castle, DE 19720
Humes Realty 33 Bridle Ridge Court, Lewes, DE 19958
Nichols Excavation 2742 Pulaski Hwy, Newark, DE 19711
2284 Bryn Zion Road, Smyrna, DE 19904
VEI Dover Crossroads, LLC 1500 Serpentine Road, Suite 100 Baltimore MD 21
580 North Dupont Highway, Dover, DE 19901
P.O. Box 778, Dover, DE 19903
714 S OAK ST
714 S OAK ST, RUM TOWN, VA, 99201
3142 E SPRAGUE AVE, WHISKEY VALLEY, WA 99281
27488 Stanford Ave, Bowden, North Dakota
380 New York St, Redlands, CA 92373
</textarea>
<div id="output">
</div>
Другой вариант для адресов в США - это YAddress (созданный компанией, в которой я работаю).
Многие ответы на этот вопрос предлагают инструменты геокодирования в качестве решения. Важно не путать разбор адресов и геокодирование; Они не то же самое. Хотя геокодеры могут разбивать адрес на компоненты в качестве дополнительного преимущества, они обычно полагаются на нестандартные наборы адресов. Это означает, что адрес, проанализированный геокодером, может не совпадать с официальным адресом. Например, то, что Google Geocoding API называет "6th Ave" на Манхэттене, USPS называет "Avenue of the Americas".
Если вы хотите полагаться на данные OSM, libpostal является очень мощным и обрабатывает множество наиболее распространенных предостережений с помощью адресного ввода.
Для анализа адресов в США,
Я предпочитаю использовать пакет usaddress, который доступен в pip только для адреса usaddress
python3 -m pip install usaddress
Это хорошо сработало для меня в США.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# address_parser.py
import sys
from usaddress import tag
from json import dumps, loads
if __name__ == '__main__':
tag_mapping = {
'Recipient': 'recipient',
'AddressNumber': 'addressStreet',
'AddressNumberPrefix': 'addressStreet',
'AddressNumberSuffix': 'addressStreet',
'StreetName': 'addressStreet',
'StreetNamePreDirectional': 'addressStreet',
'StreetNamePreModifier': 'addressStreet',
'StreetNamePreType': 'addressStreet',
'StreetNamePostDirectional': 'addressStreet',
'StreetNamePostModifier': 'addressStreet',
'StreetNamePostType': 'addressStreet',
'CornerOf': 'addressStreet',
'IntersectionSeparator': 'addressStreet',
'LandmarkName': 'addressStreet',
'USPSBoxGroupID': 'addressStreet',
'USPSBoxGroupType': 'addressStreet',
'USPSBoxID': 'addressStreet',
'USPSBoxType': 'addressStreet',
'BuildingName': 'addressStreet',
'OccupancyType': 'addressStreet',
'OccupancyIdentifier': 'addressStreet',
'SubaddressIdentifier': 'addressStreet',
'SubaddressType': 'addressStreet',
'PlaceName': 'addressCity',
'StateName': 'addressState',
'ZipCode': 'addressPostalCode',
}
try:
address, _ = tag(' '.join(sys.argv[1:]), tag_mapping=tag_mapping)
except:
with open('failed_address.txt', 'a') as fp:
fp.write(sys.argv[1] + '\n')
print(dumps({}))
else:
print(dumps(dict(address)))
Выполнение address_parser.py
python3 address_parser.py 9757 East Arcadia Ave. Saugus MA 01906
{"addressStreet": "9757 East Arcadia Ave.", "addressCity": "Saugus", "addressState": "MA", "addressPostalCode": "01906"}
Позвольте USPS сделать всю работу за вас
Если это за транзакцию, а также для целей доставки и рассылки USPS. вы можете использовать API USPS для выполнения всей работы по анализу. Это также БЕСПЛАТНО.
Я опаздываю на вечеринку, вот сценарий Excel VBA, который я написал несколько лет назад для Австралии. Его можно легко модифицировать для поддержки других стран. Я сделал GitHub-репозиторий кода C# здесь. Я разместил его на своем сайте, и вы можете скачать его здесь: http://jeremythompson.net/rocks/ParseAddress.xlsm
стратегия
Для любой страны с PostCode, который является числовым или может соответствовать RegEx, моя стратегия работает очень хорошо:
Сначала мы обнаруживаем Имя и Фамилию, которые считаются верхней строкой. Легко пропустить имя и начать с адреса, сняв флажок (называемый "Имя - верхний ряд", как показано ниже).
Затем можно ожидать, что Адрес, состоящий из улицы и номера, будет находиться перед Пригородом, а St, Pde, Ave, Av, Rd, Cres, loop и т. Д. Являются разделителями.
Обнаружение Пригорода против Штата и даже Страны может обмануть самых сложных парсеров, так как могут быть конфликты. Чтобы преодолеть это, я использую поиск по PostCode, основываясь на том факте, что после удаления номеров улиц и квартир / квартир, а также PoBox, Ph, Fax, Mobile и т. Д. Останется только номер PostCode. Это легко сопоставить с regEx, чтобы потом искать пригород (ы) и страну.
Ваша Национальная почтовая служба бесплатно предоставит список почтовых индексов в пригородах и штатах, которые вы можете сохранить в листе Excel, таблице базы данных, файле text / json / xml и т. Д.
- Наконец, поскольку некоторые почтовые индексы имеют несколько пригородов, мы проверяем, какой пригород появляется в адресе.
пример
Код VBA
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ, я знаю, что этот код не идеален или даже написан не очень хорошо, однако его очень легко преобразовать в любой язык программирования и запустить в любом типе приложений. Стратегия - это ответ в зависимости от вашей страны и правил, возьмем этот код в качестве примера:
Option Explicit
Private Const TopRow As Integer = 0
Public Sub ParseAddress()
Dim strArr() As String
Dim sigRow() As String
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim Stat As String
Dim SpaceInName As Integer
Dim Temp As String
Dim PhExt As String
On Error Resume Next
Temp = ActiveSheet.Range("Address")
'Split info into array
strArr = Split(Temp, vbLf)
'Trim the array
For i = 0 To UBound(strArr)
strArr(i) = VBA.Trim(strArr(i))
Next i
'Remove empty items/rows
ReDim sigRow(LBound(strArr) To UBound(strArr))
For i = LBound(strArr) To UBound(strArr)
If Trim(strArr(i)) <> "" Then
sigRow(j) = strArr(i)
j = j + 1
End If
Next i
ReDim Preserve sigRow(LBound(strArr) To j)
'Find the name (MUST BE ON THE FIRST ROW UNLESS CHECKBOX UNTICKED)
i = TopRow
If ActiveSheet.Shapes("chkFirst").ControlFormat.Value = 1 Then
SpaceInName = InStr(1, sigRow(i), " ", vbTextCompare) - 1
If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
ActiveSheet.Range("FirstName") = VBA.Left(sigRow(i), SpaceInName)
Else
If MsgBox("First Name: " & VBA.Mid$(sigRow(i), 1, SpaceInName), vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("FirstName") = VBA.Left(sigRow(i), SpaceInName)
End If
If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
ActiveSheet.Range("Surname") = VBA.Mid(sigRow(i), SpaceInName + 2)
Else
If MsgBox("Surame: " & VBA.Mid(sigRow(i), SpaceInName + 2), vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("Surname") = VBA.Mid(sigRow(i), SpaceInName + 2)
End If
sigRow(i) = ""
End If
'Find the Street by looking for a "St, Pde, Ave, Av, Rd, Cres, loop, etc"
For i = 1 To UBound(sigRow)
If Len(sigRow(i)) > 0 Then
For j = 0 To 8
If InStr(1, VBA.UCase(sigRow(i)), Street(j), vbTextCompare) > 0 Then
'Find the position of the street in order to get the suburb
SpaceInName = InStr(1, VBA.UCase(sigRow(i)), Street(j), vbTextCompare) + Len(Street(j)) - 1
'If its a po box then add 5 chars
If VBA.Right(Street(j), 3) = "BOX" Then SpaceInName = SpaceInName + 5
If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
ActiveSheet.Range("Street") = VBA.Mid(sigRow(i), 1, SpaceInName)
Else
If MsgBox("Street Address: " & VBA.Mid(sigRow(i), 1, SpaceInName), vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("Street") = VBA.Mid(sigRow(i), 1, SpaceInName)
End If
'Trim the Street, Number leaving the Suburb if its exists on the same line
sigRow(i) = VBA.Mid(sigRow(i), SpaceInName) + 2
sigRow(i) = Replace(sigRow(i), VBA.Mid(sigRow(i), 1, SpaceInName), "")
GoTo PastAddress:
End If
Next j
End If
Next i
PastAddress:
'Mobile
For i = 1 To UBound(sigRow)
If Len(sigRow(i)) > 0 Then
For j = 0 To 3
Temp = Mb(j)
If VBA.Left(VBA.UCase(sigRow(i)), Len(Temp)) = Temp Then
If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
ActiveSheet.Range("Mobile") = VBA.Mid(sigRow(i), Len(Temp) + 2)
Else
If MsgBox("Mobile: " & VBA.Mid(sigRow(i), Len(Temp) + 2), vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("Mobile") = VBA.Mid(sigRow(i), Len(Temp) + 2)
End If
sigRow(i) = ""
GoTo PastMobile:
End If
Next j
End If
Next i
PastMobile:
'Phone
For i = 1 To UBound(sigRow)
If Len(sigRow(i)) > 0 Then
For j = 0 To 1
Temp = Ph(j)
If VBA.Left(VBA.UCase(sigRow(i)), Len(Temp)) = Temp Then
'TODO: Detect the intl or national extension here.. or if we can from the postcode.
If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
ActiveSheet.Range("Phone") = VBA.Mid(sigRow(i), Len(Temp) + 3)
Else
If MsgBox("Phone: " & VBA.Mid(sigRow(i), Len(Temp) + 3), vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("Phone") = VBA.Mid(sigRow(i), Len(Temp) + 3)
End If
sigRow(i) = ""
GoTo PastPhone:
End If
Next j
End If
Next i
PastPhone:
'Email
For i = 1 To UBound(sigRow)
If Len(sigRow(i)) > 0 Then
'replace with regEx search
If InStr(1, sigRow(i), "@", vbTextCompare) And InStr(1, VBA.UCase(sigRow(i)), ".CO", vbTextCompare) Then
Dim email As String
email = sigRow(i)
email = Replace(VBA.UCase(email), "EMAIL:", "")
email = Replace(VBA.UCase(email), "E-MAIL:", "")
email = Replace(VBA.UCase(email), "E:", "")
email = Replace(VBA.UCase(Trim(email)), "E ", "")
email = VBA.LCase(email)
If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
ActiveSheet.Range("Email") = email
Else
If MsgBox("Email: " & email, vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("Email") = email
End If
sigRow(i) = ""
Exit For
End If
End If
Next i
'Now the only remaining items will be the postcode, suburb, country
'there shouldn't be any numbers (eg. from PoBox,Ph,Fax,Mobile) except for the Post Code
'Join the string and filter out the Post Code
Temp = Join(sigRow, vbCrLf)
Temp = Trim(Temp)
For i = 1 To Len(Temp)
Dim postCode As String
postCode = VBA.Mid(Temp, i, 4)
'In Australia PostCodes are 4 digits
If VBA.Mid(Temp, i, 1) <> " " And IsNumeric(postCode) Then
If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
ActiveSheet.Range("PostCode") = postCode
Else
If MsgBox("Post Code: " & postCode, vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("PostCode") = postCode
End If
'Lookup the Suburb and State based on the PostCode, the PostCode sheet has the lookup
Dim mySuburbArray As Range
Set mySuburbArray = Sheets("PostCodes").Range("A2:B16670")
Dim suburbs As String
For j = 1 To mySuburbArray.Columns(1).Cells.Count
If mySuburbArray.Cells(j, 1) = postCode Then
'Check if the suburb is listed in the address
If InStr(1, UCase(Temp), mySuburbArray.Cells(j, 2), vbTextCompare) > 0 Then
'Set the Suburb and State
ActiveSheet.Range("Suburb") = mySuburbArray.Cells(j, 2)
Stat = mySuburbArray.Cells(j, 3)
ActiveSheet.Range("State") = Stat
'Knowing the State - for Australia we can get the telephone Ext
PhExt = PhExtension(VBA.UCase(Stat))
ActiveSheet.Range("PhExt") = PhExt
'remove the phone extension from the number
Dim prePhone As String
prePhone = ActiveSheet.Range("Phone")
prePhone = Replace(prePhone, PhExt & " ", "")
prePhone = Replace(prePhone, "(" & PhExt & ") ", "")
prePhone = Replace(prePhone, "(" & PhExt & ")", "")
ActiveSheet.Range("Phone") = prePhone
Exit For
End If
End If
Next j
Exit For
End If
Next i
End Sub
Private Function PhExtension(ByVal State As String) As String
Select Case State
Case Is = "NSW"
PhExtension = "02"
Case Is = "QLD"
PhExtension = "07"
Case Is = "VIC"
PhExtension = "03"
Case Is = "NT"
PhExtension = "04"
Case Is = "WA"
PhExtension = "05"
Case Is = "SA"
PhExtension = "07"
Case Is = "TAS"
PhExtension = "06"
End Select
End Function
Private Function Ph(ByVal Num As Integer) As String
Select Case Num
Case Is = 0
Ph = "PH"
Case Is = 1
Ph = "PHONE"
'Case Is = 2
'Ph = "P"
End Select
End Function
Private Function Mb(ByVal Num As Integer) As String
Select Case Num
Case Is = 0
Mb = "MB"
Case Is = 1
Mb = "MOB"
Case Is = 2
Mb = "CELL"
Case Is = 3
Mb = "MOBILE"
'Case Is = 4
'Mb = "M"
End Select
End Function
Private Function Fax(ByVal Num As Integer) As String
Select Case Num
Case Is = 0
Fax = "FAX"
Case Is = 1
Fax = "FACSIMILE"
'Case Is = 2
'Fax = "F"
End Select
End Function
Private Function State(ByVal Num As Integer) As String
Select Case Num
Case Is = 0
State = "NSW"
Case Is = 1
State = "QLD"
Case Is = 2
State = "VIC"
Case Is = 3
State = "NT"
Case Is = 4
State = "WA"
Case Is = 5
State = "SA"
Case Is = 6
State = "TAS"
End Select
End Function
Private Function Street(ByVal Num As Integer) As String
Select Case Num
Case Is = 0
Street = " ST"
Case Is = 1
Street = " RD"
Case Is = 2
Street = " AVE"
Case Is = 3
Street = " AV"
Case Is = 4
Street = " CRES"
Case Is = 5
Street = " LOOP"
Case Is = 6
Street = "PO BOX"
Case Is = 7
Street = " STREET"
Case Is = 8
Street = " ROAD"
Case Is = 9
Street = " AVENUE"
Case Is = 10
Street = " CRESENT"
Case Is = 11
Street = " PARADE"
Case Is = 12
Street = " PDE"
Case Is = 13
Street = " LANE"
Case Is = 14
Street = " COURT"
Case Is = 15
Street = " BLVD"
Case Is = 16
Street = "P.O. BOX"
Case Is = 17
Street = "P.O BOX"
Case Is = 18
Street = "PO BOX"
Case Is = 19
Street = "POBOX"
End Select
End Function
Разбор адресов
Для синтаксического анализа адреса вы можете использовать Deepparse, современную библиотеку для анализа многонациональных уличных адресов с использованием глубокого обучения. Решение создано для 20 стран (таких как США, Канада и многие другие) и может относительно хорошо работать в других странах (см. Документ).
В одном из наших проектов мы использовали следующий анализатор адресов. Он анализирует адреса для большинства стран мира с хорошей точностью.
Он доступен как отдельная библиотека или как живой API.