Как анализировать уличный/почтовый адрес свободной формы из текста и в компоненты

Мы ведем бизнес в основном в Соединенных Штатах и ​​стараемся улучшить работу пользователей, объединив все поля адресов в единую текстовую область. Но есть несколько проблем:

  • Адрес, тип пользователя которого может быть неправильным или в стандартном формате
  • Адрес должен быть разделен на части (улица, город, штат и т.д.) для обработки платежей по кредитным картам.
  • Пользователи могут вводить не только их адрес (например, их имя или компанию)
  • Google может это сделать, но условия обслуживания и лимиты запросов являются непомерно высокими, особенно при ограниченном бюджете.

По-видимому, это общий вопрос:

Есть ли способ изолировать адрес от текста вокруг него и разбить его на кусочки? Есть ли регулярное выражение для анализа адресов?

Ответ 1

Я много раз видел этот вопрос, когда работал в компании по проверке адресов. Я отправляю ответ здесь, чтобы сделать его более доступным для программистов, которые ищут один и тот же вопрос. Компания, на которой я работал, обработала миллиарды адресов, и мы многому научились в этом процессе.

Во-первых, нам нужно понять несколько вещей о адресах.

Адреса не regular

Это означает, что регулярные выражения отсутствуют. Я видел все это, начиная с простых регулярных выражений, которые соответствуют адресам в очень специфическом формате:

/\ S + (\ d {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 | \, |.] +)\Ъ (АК |? АЛ | АР | AZ | CA | СО | КТ | DC | DE | FL | Г.А. | ГУ | 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 |\|.] +)/я

... this, где файл с более чем 900 линейным классом генерирует сверхмассивное регулярное выражение "на лету", чтобы соответствовать еще большему. Я не рекомендую их (например, здесь скрипт вышеупомянутого регулярного выражения, что делает много ошибок). Существует не легкая магическая формула, чтобы заставить это работать. Теоретически и по теории невозможно сопоставить адреса с регулярным выражением.

Публикация USPS 28 документирует множество форматов адресов, которые возможны, со всеми их ключевыми словами и вариантами. Хуже всего, адреса часто неоднозначны. Слова могут означать более одного ( "Св" может быть "Святой" или "Улица" ), и есть слова, которые я уверен, что они придумали. (Кто знал, что "Stravenue" был уличным суффиксом?)

Вам нужен код, который действительно понимает адреса, и если этот код существует, это коммерческий секрет. Но вы, вероятно, можете бросить свои собственные, если вы действительно в это вникнете.

Адреса имеют неожиданные формы и размеры

Вот некоторые надуманные (но полные) адреса:

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 Вт Apt 14

Беверли-Хиллз CA 90210-5221

Адресные данные не являются вашими собственными

В большинстве стран, которые предоставляют официальные адресные данные лицензированным поставщикам, сами адресные данные принадлежат к управляющему агентству. В США USPS владеет адресами. То же самое верно для Canada Post, Royal Mail и других, хотя каждая страна вводит в действие или определяет право собственности немного по-другому. Знание этого важно, поскольку оно обычно запрещает обратную разработку адресной базы данных. Вы должны быть осторожны, как приобретать, хранить и использовать данные.

Google Maps является распространенным решением для быстрого исправления адресов, но TOSявляется довольно запретительным; например, вы не можете использовать свои данные или API без отображения Карты Google и только для некоммерческих целей (если вы не платите), и вы не можете хранить данные (за исключением временного кэширования). Имеет смысл. Данные Google являются одними из лучших в мире. Однако Google Maps не проверяет адрес. Если адрес не существует, он все равно покажет вам, где будет адрес, если он существует (попробуйте на своей улице, используйте номер дома, который, как вы знаете, не существует). Иногда это полезно, но имейте это в виду.

Nominatim политика использования также ограничивает, особенно для большого объема и коммерческого использования, а данные в основном извлекаются из бесплатных источников, поэтому это не (как и характер открытых проектов), однако это все равно может удовлетворить ваши потребности. Это поддерживается большим сообществом.

У USPS есть API, но он сильно падает и не имеет никаких гарантий и поддержки. Это также может быть трудно использовать. Некоторые люди используют его без проблем. Но легко пропустить, что USPS требует, чтобы вы использовали свой API только для подтверждения адресов для отправки через них.

Люди ожидают, что адреса будут жесткими

К сожалению, мы вынудили наше общество ожидать, что адреса будут сложными. В этом есть десятки хороших статей UX по всему Интернету, но факт заключается в том, что если у вас есть адресная форма с отдельными полями, то, что ожидают пользователи, даже если это затрудняет адреса в крайнем случае, которые не соответствуют формат формы ожидает, или, может быть, форма требует поля, которое не должно. Или пользователи не знают, где разместить определенную часть своего адреса.

В наши дни я мог бы продолжать и обсуждать плохой UX форм выписки, но вместо этого я просто скажу, что объединение адресов в одно поле будет приятным изменением - люди смогут ввести свой адрес, как они считают нужным, вместо того, чтобы пытаться выяснить вашу длинную форму. Однако это изменение будет неожиданным, и пользователи могут сначала немного разобраться. Просто имейте это в виду.

Часть этой боли можно смягчить, поставив поле страны на фронт, перед адресом. Когда они сначала заполняют поле страны, вы знаете, как сделать свою форму. Возможно, у вас есть хороший способ справиться с однопользовательскими адресами США, поэтому, если они выберут Соединенные Штаты, вы можете уменьшить свою форму до одного поля, иначе отобразите поля компонентов. Просто о чем подумать!

Теперь мы знаем, почему это сложно; что вы можете с этим сделать?

Поставщики лицензий USPS через процесс под названием CASS ™ Certification для предоставления проверенных адресов клиентам. Эти поставщики имеют доступ к базе данных USPS, обновляемой ежемесячно. Их программное обеспечение должно соответствовать строгим стандартам, которые должны быть сертифицированы, и они не часто требуют согласия с такими предельными условиями, как обсуждалось выше.

Есть много сертифицированных CASS компаний, которые могут обрабатывать списки или иметь API: Melissa Data, Experian QAS и SmartyStreets, чтобы назвать несколько.

(Из-за того, что я получил "рекламу", я урезал свой ответ на данный момент. Вам решать, какое решение вам подходит.)

Правда: На самом деле, ребята, я не работаю ни в одной из этих компаний. Это не реклама.

Ответ 2

libpostal: библиотека с открытым исходным кодом для анализа адресов, обучение работе с данными из OpenStreetMap, OpenAddresses и OpenCage.

https://github.com/openvenues/libpostal (дополнительная информация об этом)

Другие инструменты/услуги:

Ответ 3

Есть много парсеров уличных адресов. Они представлены в двух основных вариантах: те, которые имеют базы данных с названиями мест и названиями улиц, а также те, которые этого не делают.

Анализатор уличного адреса регулярного выражения может получить около 95% успеха без особых проблем. Затем вы начинаете сталкиваться с необычными случаями. Perl один в CPAN, "Geo:: StreetAddress:: US", об этом хорош. Есть порты Python и Javascript этого, все с открытым исходным кодом. У меня улучшенная версия в Python, которая немного улучшает скорость, обрабатывая больше случаев. Однако, чтобы получить последние 3% права, вам нужны базы данных, которые помогут с устранением неоднозначности.

База данных с 3-значными почтовыми индексами и названиями и сокращениями в США является большой помощью. Когда синтаксический анализатор видит последовательный почтовый индекс и имя состояния, он может начать блокировку в формате. Это очень хорошо работает для США и Великобритании.

Правильный анализ уличного адреса начинается с конца и работает назад. Это то, как это делают системы USPS. Адреса наименее двусмысленны в конце, где имена стран, названия городов и почтовые коды относительно легко распознаются. Названия улиц обычно могут быть изолированы. Местоположения на улицах являются наиболее сложными для анализа; там вы сталкиваетесь с такими вещами, как "Пятый этаж" и "Скрепки павильона". Это, когда база данных является большой помощью.

Ответ 4

ОБНОВЛЕНИЕ: Geocode.xyz теперь работает по всему миру. Для примеров смотрите https://geocode.xyz

Для США, Мексики и Канады см. 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, Нью-Йорк

Ответ 5

Нет кода? Стыдно!

Вот простой анализатор адресов 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 &amp; 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>

Ответ 6

Если вы хотите полагаться на данные OSM libpostal, он очень эффективен и обрабатывает множество наиболее распространенных оговорок с адресными входами.

Ответ 7

Для анализа адресов в США,

Я предпочитаю использовать пакет usaddress, который доступен в pip только для адреса usaddress

python3 -m pip install usaddress

Документация
PyPi

Это хорошо сработало для меня в США.

#!/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"}

Ответ 8

В одном из наших проектов мы использовали следующий парсер адресов. Он анализирует адреса для большинства стран мира с хорошей точностью.

http://address-parser.net/

Он доступен как автономная библиотека или как живой API.

Ответ 9

Другой вариант для адресов в США - это YAddress (созданный компанией, в которой я работаю).

Многие ответы на этот вопрос предлагают инструменты геокодирования в качестве решения. Важно не путать разбор адресов и геокодирование; Они не то же самое. Хотя геокодеры могут разбивать адрес на компоненты в качестве дополнительного преимущества, они обычно полагаются на нестандартные наборы адресов. Это означает, что адрес, проанализированный геокодером, может не совпадать с официальным адресом. Например, то, что Google Geocoding API называет "6th Ave" на Манхэттене, USPS называет "Avenue of the Americas".

Ответ 10

Я опаздываю на вечеринку, вот сценарий Excel VBA, который я написал несколько лет назад для Австралии. Его можно легко модифицировать для поддержки других стран. Я сделал GitHub-репозиторий кода С# здесь. Я разместил его на своем сайте, и вы можете скачать его здесь: http://jeremythompson.net/rocks/ParseAddress.xlsm

Стратегия

Для любой страны с PostCode, который числовой или может быть сопоставлен с RegEx, моя стратегия работает очень хорошо:

  1. Сначала мы обнаруживаем Имя и Фамилию, которые считаются верхней строкой. Легко пропустить имя и начать с адреса, сняв флажок (называемый "Имя - верхняя строка", как показано ниже).

  2. Затем можно ожидать, что адрес, состоящий из улицы и номера, будет находиться перед пригородом, а St, Pde, Ave, Av, Rd, Cres, loop и т.д. Являются разделителями.

  3. Обнаружение Пригорода против Штата и даже Страны может обмануть самых сложных парсеров, так как могут быть конфликты. Чтобы преодолеть это, я использую поиск по PostCode, основываясь на том факте, что после удаления номеров улиц и квартир/квартир, а также PoBox, Ph, Fax, Mobile и т.д. Останется только номер PostCode. Это легко сопоставить с RegEx, чтобы затем искать пригород и страну.

Ваша Национальная почтовая служба бесплатно предоставит список почтовых индексов в пригородах и штатах, которые вы можете сохранить в листе Excel, таблице базы данных, файле text/json/xml и т.д.

  1. Наконец, поскольку некоторые почтовые индексы имеют несколько пригородов, мы проверяем, какой пригород появляется в адресе.

Пример

enter image description here

Код 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