私たちは主に米国でビジネスを行っており、すべての住所フィールドを単一のテキスト領域に結合することにより、ユーザーエクスペリエンスを改善しようとしています。しかし、いくつかの問題があります。
どうやら、これはよくある質問です:
住所を周囲のテキストから分離し、それを断片に分割する方法はありますか?アドレスを解析するための正規表現はありますか?
この質問は、住所確認会社で働いていたときによく見ました。同じ質問で探し回っているプログラマーによりアクセスしやすくするために、ここに答えを投稿しています。私は数十億の住所を処理していましたが、その過程で多くのことを学びました。
まず、アドレスに関するいくつかのことを理解する必要があります。
これは、正規表現が使用できないことを意味します。非常に特定の形式のアドレスに一致する単純な正規表現から、これまですべてを見てきました。
/\s +(\ d {2,5}\s +)(?![a | p] m\b)(([a-zA-Z |\s +] {1,5}){1,2}) ?([\ s | \、|。] +)?(([[a-zA-Z |\s +] {1,30}){1,4})(court | ct | street | st | drive | dr | lane | ln | road | rd | blvd)([\ s | \、|。| \;] +)?(([[a-zA-Z |\s +] {1,30}){1,2} )([\ s | \、|。] +)?\ b(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 | MO | MS | MT | NC | ND | NE | NH | NJ | NM | NV | NY | OH | OK | OR | PA | RI | SC | SD | TN | TX | UT | VA | VI | VT | WA | WI | WV | WY)([\ s | \、|。] +)?(\ s +\d {5})?([\ s | \、|。] +)/ i
... to this 900+の行クラスファイルは、さらに一致するようにその場で超大規模な正規表現を生成します。これらはお勧めしません(たとえば、 上記の正規表現のフィドルは、多くの間違いを犯します )。これを機能させる簡単な魔法の公式はありません。理論およびby理論では、アドレスを正規表現と照合することはできません。
SPS Publication 28 は、可能性のある多くの形式の住所を、それらのすべてのキーワードとバリエーションとともに文書化しています。最悪なことに、アドレスはあいまいです。言葉は複数のことを意味する場合があり(「St」は「Saint」または「Street」になる場合があります)、それらが発明したと確信している言葉があります。 (「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は、住所(2次/ユニット番号)と住所を識別するのに十分な5桁の郵便番号も含まれているため、完全です。
番号3は郵便番号が含まれているため、完全な私書箱形式です。
番号4も完全です。これは、 郵便番号が一意 であるため、プライベートエンティティまたは企業がそのアドレススペースを購入したことを意味します。ユニークな郵便番号は、大容量または集中配送スペース用です。郵便番号12345に宛てられたものはすべて、ニューヨーク州スケネクタディのGeneral Electricに送られます。この例は特に誰にも届きませんが、USPSはそれを配信できます。
5番も完全です。信じられないかもしれません。これらの数字だけで、すべての可能なアドレスのデータベースに対して解析されたときに、完全なアドレスを発見できます。各番号をコンポーネントとして見ると、欠落している方向、二次指定子、Zip + 4コードを入力するのは簡単です。完全に拡張され、標準化された外観は次のとおりです。
205 N 1105 Wアプト14
ビバリーヒルズCA 90210-5221
認可されたベンダーに公式の住所データを提供するほとんどの国では、住所データ自体は管理機関に属します。米国では、USPSが住所を所有しています。同じことがカナダ郵政公社、ロイヤル・メール、その他にも当てはまりますが、各国は所有権を少しずつ強制または定義します。通常、アドレスデータベースのリバースエンジニアリングが禁止されているため、これを知ることは重要です。データの取得、保存、使用方法に注意する必要があります。
Googleマップは、迅速な住所修正の一般的な手段ですが、 TOS はかなり禁止されています。たとえば、Googleマップを表示せずにデータやAPIを使用することはできません。非営利目的でのみ(支払いを行わない限り)、データを保存することはできません(一時キャッシュを除く)。理にかなっています。 Googleのデータは世界でも最高のものです。ただし、Googleマップは住所を確認しません。アドレスが存在しない場合でも、アドレスdidが存在する場合would自分の道路、存在しないことがわかっている家番号を使用してください)。これは時々役に立ちますが、それに注意してください。
Nominatimの sage policy は、特に大量および商用の使用の場合、同様に制限されており、データはほとんど無料ソースから取得されるため、十分に維持されていません(オープンプロジェクトの性質)- -ただし、これはまだあなたのニーズに合っているかもしれません。それは素晴らしいコミュニティによってサポートされています。
USPS自体にはAPIがありますが、 それは大幅に低下します であり、保証もサポートもありません。使いにくいかもしれません。一部の人々は問題なくそれを控えめに使用します。しかし、USPSでは、住所を確認するためにのみAPIを使用する必要があることをUSPSが要求することは簡単です。
残念ながら、アドレスが複雑になると期待するように、社会を条件付けました。これについては、インターネット上に多数の優れたUX記事がありますが、実際には、個々のフィールドを持つアドレスフォームがある場合、それはユーザーが期待するものです。フォームが想定している形式、またはフォームに必要でないフィールドが必要な場合があります。または、ユーザーは自分のアドレスの特定の部分をどこに置くべきかわかりません。
最近、チェックアウトフォームの悪いUXについて続けることができますが、代わりに、アドレスを1つのフィールドに結合することはwelcome変更であると言います-長いフォームを見つけようとするのではなく、人々は自分の住所を自分の住所に合わせて入力することができます。ただし、この変更はunexpectedになり、ユーザーは最初は少し不快に感じるかもしれません。気をつけてください。
この痛みの一部は、住所の前に国フィールドを前面に置くことで軽減できます。最初に国のフィールドに入力すると、フォームを表示する方法がわかります。たぶん、単一フィールドの米国住所を処理する良い方法があるので、米国を選択した場合、フォームを単一フィールドに縮小できます。そうでなければ、コンポーネントフィールドを表示できます。考えてみてください!
USPSは、CASS™認定と呼ばれるプロセスを通じてベンダーにライセンスを付与し、検証済みの住所を顧客に提供します。これらのベンダーは、毎月更新されるUSPSデータベースにアクセスできます。それらのソフトウェアは、認証を受けるために厳しい基準に準拠している必要があり、多くの場合、上記のような制限条件への同意を必要としません。
リストを処理したり、APIを使用したりできるCASS認定企業は、Melissa Data、Experian QAS、SmartyStreetsなどがあります。
(「広告」の軽視のため、この時点で回答を省略しました。あなたに役立つ解決策を見つけるのはあなた次第です。)
真実:本当に、私はこれらの会社で働いていません。広告ではありません。
libpostal:アドレスを解析するためのオープンソースライブラリ、OpenStreetMap、OpenAddresses、およびOpenCageのデータを使用したトレーニング。
https://github.com/openvenues/libpostal ( それについての詳細 )
その他のツール/サービス:
http://www.gisgraphy.com OpenStreetMap、GeoNames、Quattroshapesを統合した、無料のオープンソースのジオコーダーおよびジオローカリゼーションWebサービスをすぐに使用できます。
https://github.com/kodapan/osm-common OpenStreetMapサービスにアクセスし、データを解析および処理するためのライブラリ。
多くの番地パーサーがあります。地名と街路名のデータベースを持つものと持たないものの2つの基本的なフレーバーがあります。
正規表現の番地パーサーは、ほとんど問題なく最大95%の成功率を達成できます。次に、異常なケースをヒットし始めます。 CPANのPerlの1つである "Geo :: StreetAddress :: US"は、それで十分です。 PythonおよびJavascriptポートがあり、すべてオープンソースです。 Pythonには改善されたバージョンがあり、より多くのケースを処理することで成功率がわずかに上がります。ただし、最後の3%を正しくするには、曖昧さ回避に役立つデータベースが必要です。
3桁の郵便番号と米国の州名と略語を含むデータベースは大きな助けになります。パーサーは、一貫した郵便番号と州名を見つけると、形式のロックを開始できます。これは、米国と英国で非常に有効です。
適切な番地の解析は、最後から始まり、逆方向に機能します。それがUSPSシステムのやり方です。住所は、国名、都市名、および郵便番号が比較的簡単に認識できる最後のあいまいさが最も少なくなります。通常、通りの名前は分離できます。道路上の場所は、解析するのが最も複雑です。そこでは、「5階」や「ステープルズパビリオン」などに遭遇します。それは、データベースが大きな助けになるときです。
更新:Geocode.xyzは世界中で機能するようになりました。例については、 https://geocode.xyzを参照してください
米国、メキシコ、カナダについては、 geocoder.ca を参照してください。
例えば:
入力: mainとarthur kill rd new yorkの交差点近くで起こっていること
出力:
<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>
Webインターフェースで結果を確認するか、JsonまたはJsonpとして出力を取得することもできます。例えば。 私はニューヨークのメインストリート123番地周辺のレストランを探しています
コードなし?恥のために!
簡単なJavaScriptアドレスパーサーを次に示します。マットが上記の論文で述べているすべての理由については非常にひどいです(私はほぼ100%に同意します:アドレスは複雑なタイプであり、人間はミスを犯します。これをアウトソーシングし、自動化する方が良い場合-余裕がある場合)。
しかし、泣くのではなく、試してみることにしました。
このコードは、findAddressCandidate
のEsriの結果のほとんどと、ストリート/市/州がコンマで区切られた単一行の住所を返す他の(逆)ジオコーダーで解析するのに問題なく動作します。必要に応じて拡張したり、国固有のパーサーを作成したりできます。または、このエクササイズがいかに困難であるか、または私がJavaScriptをどれほどひどくしているかのケーススタディとして使用してください。私はこれに約30分しか費やしていないことを認めています(将来の反復ではキャッシュ、Zip検証、状態ルックアップ、ユーザーロケーションコンテキストを追加できます)が、私のユースケースではうまくいきました:エンドユーザーはジオコード検索応答を4に解析するフォームを見ますテキストボックス。アドレスの解析が間違っている場合(ソースデータが貧弱でない限りまれです)、それは大したことではありません-ユーザーはそれを確認して修正することができます! (ただし、自動化されたソリューションの場合、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>
米国の住所解析の場合、
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 __== '__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"}
OSMデータに依存したい場合 libpostal は非常に強力であり、アドレス入力に関する最も一般的な警告の多くを処理します。
米国ベースの住所の別のオプションは、 YAddress (私が勤務する会社によって作成された)です。
この質問に対する多くの回答は、解決策としてジオコーディングツールを提案しています。住所の解析とジオコーディングを混同しないことが重要です。それらは同じではありません。ジオコーダーは、副次的な利点として住所をコンポーネントに分解する場合がありますが、通常は非標準の住所セットに依存しています。これは、ジオコーダーで解析された住所が公式の住所と異なる場合があることを意味します。たとえば、GoogleジオコーディングAPIがマンハッタンで「6th Ave」と呼ぶものは、USPSは「Avenue of the Americas」と呼びます。
私はパーティーに遅刻しました。ここにオーストラリアのために数年前に書いたExcel VBAスクリプトがあります。他の国をサポートするために簡単に変更できます。ここでは、C#コードのGitHubリポジトリを作成しました。私は自分のサイトでそれをホストしており、ここからダウンロードできます: http://jeremythompson.net/rocks/ParseAddress.xlsm
PostCodeが数値であるか、RegExと一致する可能性のある国では、私の戦略は非常にうまく機能します。
最初に、トップラインと見なされるFirstとSurnameを検出します。チェックボックスのチェックを外すと、名前をスキップしてアドレスから簡単に開始できます(下に示すように、「名前は一番上の行」と呼ばれます)。
次に、StreetとNumberで構成されるAddressがSuburbの前に来て、St、Pde、Ave、Av、Rd、Cres、loopなどがセパレータであると期待するのが安全です。
郊外と州、さらには国を検出すると、競合が発生する可能性があるため、最も洗練されたパーサーをだますことができます。これを克服するために、ストリートとアパートメント/ユニット番号、PoBox、Ph、Fax、Mobileなどを削除した後、PostCodeのみを削除するという事実に基づいてPostCode検索を使用します番号は残ります。これは、regExと簡単に照合して、郊外と国を検索できます。
National Post Office Serviceは、郊外と州の郵便番号のリストを無料で提供し、Excelシート、dbテーブル、text/json/xmlファイルなどに保存できます。
免責事項、私はこのコードが完璧ではないこと、またはうまく書かれていることを知っていますが、プログラミング言語に変換してあらゆる種類のアプリケーションで実行するのは非常に簡単です。 :
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
プロジェクトの1つでは、次のアドレスパーサーを使用しました。世界のほとんどの国の住所を正確に解析します。
スタンドアロンライブラリまたはライブAPIとして利用できます。