I know this is pretty late, but maybe someone else will see this and find it useful.
I wrote my own class to do just this. It assumes that the addresses are primarily US addresses (as is the case for the org I wrote it in), and then made adjustments for Canadian and UK zip codes. I know that it won't work for Somalia or Montserrat because of their zip codes.
It's a surprisingly difficult thing to do because of the varied ways that addresses are written. But this has worked correctly for every address in my org.
public class addressParser {
public static Map<string, string> parseAddress(String address){
address.replace('\r\n', '\n').replace('\r', '\n');
Map<String, String> addressMap = new Map<String, String>();
String tempAddress = address.trim();
Boolean International = false;
String Country = '';
if (checkForCountryString(address) != ''){
country = checkForCountryString(address).trim();
tempAddress = address.removeEnd(country).trim();
}
String zipAddress = tempAddress;//zip should be after a comma or after a line break if there is none
if (tempaddress.contains(','))
zipAddress = tempAddress.remove(tempAddress.split(',')[0]);
else if (tempaddress.contains('\n'))
zipAddress = tempAddress.remove(tempAddress.split('\n')[0]);
Pattern zipPattern = Pattern.compile('\\d{5}(?:[-\\s]\\d{4})?'); //US Zip code pattern, accounts for 12345, 12345-6789 or 12345 6789 patterns at the end of the address string
Matcher zipMatcher = zipPattern.matcher(zipAddress);
String zip = '';
while (zipMatcher.find()){
if (zipMatcher.group() != null && zipMatcher.group() != '')
zip = zipMatcher.group(); //zip code that matches us pattern
}
if (zip == null || zip == ''){
International = true;
addressMap = getInternationalZip(zipAddress);
zip = addressMap.get('Postal Code');
if (addressMap.containsKey('Country'))//&& tempAddress.contains(addressMap.get('Country'))
tempAddress = address.removeEnd(addressMap.get('Country')).trim();
}
else
addressMap.put('Postal Code', zip);
if (country != ''){
addressMap.put('Country', country);
tempAddress = tempAddress.remove(Country).trim();
}
tempAddress = tempAddress.trim().remove(zip).trim();
if (tempAddress.substringAfterLast(',').trim() == '' )//If comma is between state & zip or zip and country, remove it
tempAddress = tempAddress.removeEnd(',');
String state = tempAddress.substringAfterLast(',').trim(); // state is block after comma that isn't zip
if ((zip != null && zip != '') && (state == null || state == '') && International == false){
state = tempAddress.substringAfterLast(' ').trim(); //if user didn't add comma, only space
String temp2 = tempAddress.removeEnd(state).trim();
String stringBeforeState = temp2.substringAfterLast(' ').trim();
if (stringBeforeState.contains('New') || stringBeforeState.contains('North') || stringBeforeState.contains('South') || stringBeforeState.contains('East') || stringBeforeState.contains('West'))
state = stringBeforeState + ' '+ state;
}
addressMap.put('State', state);
tempAddress = tempAddress.removeEnd(state);
tempAddress = tempAddress.trim().removeEnd(',');
String city = tempAddress.trim().substringAfterLast('\n').trim();//in between line break and comma
if (city == null || city == '')
city = tempAddress.substringAfterLast(','); //If comma was used in place of a line break
addressMap.put('City', city);
tempAddress = tempAddress.remove(city);
String street = tempAddress;
addressMap.put('Street', street);
if (addressMap.get('Country') != '' && !International)
addressMap.put('Country', 'USA');
return addressMap;
}
public static Map<String, String> getInternationalZip(String Address){
Map<String, String> addressMap = new Map<String, String>();
String zip = '';
Map<string, string> regexMap = new Map<String, String>();
regexMap.put('Canada', '[ABCEGHJKLMNPRSTVXY]{1}\\d{1}[A-Z]{1} *-* *\\d{1}[A-Z]{1}\\d{1}');
regexMap.put('UK', '(GIR 0AA)|((([A-Z-[QVX]][0-9][0-9]?)|(([A-Z-[QVX]][A-Z-[IJZ]][0-9][0-9]?)|(([A-Z-[QVX]][0-9][A-HJKPSTUW])|([A-Z-[QVX]][A-Z-[IJZ]][0-9][ABEHMNPRVWXY])))) [0-9][A-Z-[CIKMOV]]{2})');// Official UK Regex
regexMap.put('Netherlands', '[0-9]{4}\\s*[A-Za-z]{2}');
for (String s: regexMap.keyset()){
Pattern zipMatcher = Pattern.compile(regexMap.get(s));
Matcher zipMatch = zipMatcher.matcher(Address);
while (zipMatch.find())
zip = zipMatch.group();
if (zip != '' && zip != null){
addressMap.put('Postal Code', zip);
addressMap.put('Country', s);
return addressMap;
}
}
if (zip == '' || zip == null)
addressMap = getZip(address);
//Not included -- Somalia AA NNNNN Montserrat MSR 1110-1350
return addressMap;
}
public static Map<String, String> getZip(String address){ //If all else fails, get the string containing numbers from the lowest line available
Map<String, String> addressMap = new Map<String, String>();
Boolean foundZip = false;
Pattern containsNumbers = Pattern.compile('.*[0-9].*');
String zip = '';
List<String> lines = address.split('\r\n');
for(Integer i = (lines.size()-1); i >= 0; i--){
String s = lines[i].trim();
Matcher mixedMatcher = containsNumbers.matcher(s);
if (mixedMatcher.matches()){
List<String> tokens = s.split(' ');
for (string token: tokens){
Matcher zipMatcher = containsNumbers.matcher(token);
if (zipmatcher.matches())
zip += token;
}
break;
}
}
addressMap.put('Postal Code', zip);
return addressMap;
}
public static string checkForCountryString(String address){ //We're going to assume that it's a country string if the last line is all characters and there are at least 2 preceding lines
String country = '';
Pattern onlyLetters = Pattern.compile('^[a-zA-Z\\s]*$');
List<String> lines = address.split('\r\n');
if (address.contains('<br/>'))
lines = address.split('<br/>');
if (lines.size() > 2){
String lastLine = lines[(lines.size()-1)];
Matcher lettersMatcher = onlyLetters.matcher(lastLine.trim());
if (lettersMatcher.matches())
country = lastLine;
}
return country;
}
}