lookupZIP <- function(city = "Iowa City", state = "IA") { require(XML) if (length(city) != 1 || length(state) != 1) stop("can only handle one query at a time") city <- gsub(" ", "+", city) queryURL <- paste("http://www.zipinfo.com/cgi-local/zipsrch.exe?ll=ll&zip=", city, "+", state, "&Go=Go", sep = "") ## Parse the query using capture.output to suppress output. capture.output(z <- htmlParse(queryURL)) ## When a single matching city is found the response contains a ## table of data with a header row (numCol th elements within a tr). ## This is the table we want. If there is no such table then the ## Find call returns NULL. numCol <- 5 tableHasHeaderRow <- function(x) length(getNodeSet(x, "tr/th")) == numCol dataTable <- Find(tableHasHeaderRow, getNodeSet(z,"//table")) if (! is.null(dataTable)) { ## Drop off the header row. dataRows <- getNodeSet(dataTable, "tr")[-1] ## Extract data values, using as.character to remove names. extract <- function(r) as.character(sapply(xmlChildren(r),xmlValue)) rows <- lapply(dataRows, extract) ## Form result into a data frame with appropriate variable types. val <- do.call(rbind, rows) rownames(val) <- NULL data.frame(ZIP = as.integer(val[,1]), city = val[,2], state = val[,3], lat = as.double(val[,4]), long = as.double(val[,5])) } else NULL }