Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(241)

Side by Side Diff: content/common/android/address_parser_internal.cc

Issue 10456007: [Android] Split the address parser from AddressDetector for WebView use. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: transforming AddressParser into a namespace. Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "content/renderer/android/address_detector.h" 5 #include "content/common/android/address_parser_internal.h"
6 6
7 #include <bitset> 7 #include <bitset>
8 8
9 #include "base/logging.h" 9 #include "base/logging.h"
10 #include "base/string_util.h" 10 #include "base/string_util.h"
11 #include "base/utf_string_conversions.h"
12 #include "net/base/escape.h"
13 #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h"
14 11
15 namespace { 12 namespace {
16 13
17 // Prefix used for geographical address intent URIs.
18 static const char kAddressSchemaPrefix[] = "geo:0,0?q=";
19
20 // Maximum text length to be searched for address detection.
21 static const size_t kMaxAddressLength = 250;
22
23 // Minimum number of words in an address after the house number
24 // before a state is expected to be found.
25 // A value too high can miss short addresses.
26 const size_t kMinAddressWords = 3;
27
28 // Maximum number of words allowed in an address between the house number
29 // and the state, both not included.
30 const size_t kMaxAddressWords = 12;
31
32 // Maximum number of lines allowed in an address between the house number
33 // and the state, both not included.
34 const size_t kMaxAddressLines = 5;
35
36 // Maximum length allowed for any address word between the house number
37 // and the state, both not included.
38 const size_t kMaxAddressNameWordLength = 25;
39
40 // Maximum number of words after the house number in which the location name
41 // should be found.
42 const size_t kMaxLocationNameDistance = 4;
43
44 // Number of digits for a valid zip code. 14 // Number of digits for a valid zip code.
45 const size_t kZipDigits = 5; 15 const size_t kZipDigits = 5;
46 16
47 // Number of digits for a valid zip code in the Zip Plus 4 format. 17 // Number of digits for a valid zip code in the Zip Plus 4 format.
48 const size_t kZipPlus4Digits = 9; 18 const size_t kZipPlus4Digits = 9;
49 19
50 // Maximum number of digits of a house number, including possible hyphens. 20 // Maximum number of digits of a house number, including possible hyphens.
51 const size_t kMaxHouseDigits = 5; 21 const size_t kMaxHouseDigits = 5;
52 22
53 // Additional characters used as new line delimiters.
54 const char16 kNewlineDelimiters[] = {
55 '\n',
56 ',',
57 '*',
58 0x2022, // Unicode bullet
59 0,
60 };
61
62 char16 SafePreviousChar(const string16::const_iterator& it, 23 char16 SafePreviousChar(const string16::const_iterator& it,
63 const string16::const_iterator& begin) { 24 const string16::const_iterator& begin) {
64 if (it == begin) 25 if (it == begin)
65 return ' '; 26 return ' ';
66 return *(it - 1); 27 return *(it - 1);
67 } 28 }
68 29
69 char16 SafeNextChar(const string16::const_iterator& it, 30 char16 SafeNextChar(const string16::const_iterator& it,
70 const string16::const_iterator& end) { 31 const string16::const_iterator& end) {
71 if (it == end) 32 if (it == end)
(...skipping 22 matching lines...) Expand all
94 if (!*ascii_to_match || base::ToLowerASCII(*it) != *ascii_to_match) 55 if (!*ascii_to_match || base::ToLowerASCII(*it) != *ascii_to_match)
95 return false; 56 return false;
96 } 57 }
97 return *ascii_to_match == 0; 58 return *ascii_to_match == 0;
98 } 59 }
99 60
100 } // anonymous namespace 61 } // anonymous namespace
101 62
102 namespace content { 63 namespace content {
103 64
104 AddressDetector::AddressDetector() { 65 namespace address_parser {
105 }
106 66
107 AddressDetector::~AddressDetector() { 67 namespace internal {
108 }
109 68
110 std::string AddressDetector::GetContentText(const string16& text) { 69 Word::Word(const string16::const_iterator& begin,
111 // Get the address and replace unicode bullets with commas. 70 const string16::const_iterator& end)
112 string16 address_16 = CollapseWhitespace(text, false);
113 std::replace(address_16.begin(), address_16.end(),
114 static_cast<char16>(0x2022), static_cast<char16>(','));
115 return UTF16ToUTF8(address_16);
116 }
117
118 GURL AddressDetector::GetIntentURL(const std::string& content_text) {
119 return GURL(kAddressSchemaPrefix +
120 net::EscapeQueryParamValue(content_text, true));
121 }
122
123 size_t AddressDetector::GetMaximumContentLength() {
124 return kMaxAddressLength;
125 }
126
127 bool AddressDetector::FindContent(const string16::const_iterator& begin,
128 const string16::const_iterator& end,
129 size_t* start_pos,
130 size_t* end_pos,
131 std::string* content_text) {
132 HouseNumberParser house_number_parser;
133
134 // Keep going through the input string until a potential house number is
135 // detected. Start tokenizing the following words to find a valid
136 // street name within a word range. Then, find a state name followed
137 // by a valid zip code for that state. Also keep a look for any other
138 // possible house numbers to continue from in case of no match and for
139 // state names not followed by a zip code (e.g. New York, NY 10000).
140 const string16 newline_delimiters = kNewlineDelimiters;
141 const string16 delimiters = kWhitespaceUTF16 + newline_delimiters;
142 for (string16::const_iterator it = begin; it != end; ) {
143 Word house_number;
144 if (!house_number_parser.Parse(it, end, &house_number))
145 return false;
146
147 String16Tokenizer tokenizer(house_number.end, end, delimiters);
148 tokenizer.set_options(String16Tokenizer::RETURN_DELIMS);
149
150 std::vector<Word> words;
151 words.push_back(house_number);
152
153 bool found_location_name = false;
154 bool continue_on_house_number = true;
155 bool consecutive_house_numbers = true;
156 size_t next_house_number_word = 0;
157 size_t num_lines = 1;
158
159 // Don't include the house number in the word count.
160 size_t next_word = 1;
161 for (; next_word <= kMaxAddressWords + 1; ++next_word) {
162
163 // Extract a new word from the tokenizer.
164 if (next_word == words.size()) {
165 do {
166 if (!tokenizer.GetNext())
167 return false;
168
169 // Check the number of address lines.
170 if (tokenizer.token_is_delim() && newline_delimiters.find(
171 *tokenizer.token_begin()) != string16::npos) {
172 ++num_lines;
173 }
174 } while (tokenizer.token_is_delim());
175
176 if (num_lines > kMaxAddressLines)
177 break;
178
179 words.push_back(Word(tokenizer.token_begin(), tokenizer.token_end()));
180 }
181
182 // Check the word length. If too long, don't try to continue from
183 // the next house number as no address can hold this word.
184 const Word& current_word = words[next_word];
185 DCHECK_GT(std::distance(current_word.begin, current_word.end), 0);
186 size_t current_word_length = std::distance(
187 current_word.begin, current_word.end);
188 if (current_word_length > kMaxAddressNameWordLength) {
189 continue_on_house_number = false;
190 break;
191 }
192
193 // Check if the new word is a valid house number.
194 if (house_number_parser.Parse(current_word.begin, current_word.end,
195 NULL)) {
196 // Increase the number of consecutive house numbers since the beginning.
197 if (consecutive_house_numbers) {
198 // Check if there is a new line between consecutive house numbers.
199 // This avoids false positives of the form "Cafe 21\n 750 Fifth Ave.."
200 if (num_lines > 1) {
201 next_house_number_word = next_word;
202 break;
203 }
204 }
205
206 // Keep the next candidate to resume parsing from in case of failure.
207 if (next_house_number_word == 0) {
208 next_house_number_word = next_word;
209 continue;
210 }
211 } else {
212 consecutive_house_numbers = false;
213 }
214
215 // Look for location names in the words after the house number.
216 // A range limitation is introduced to avoid matching
217 // anything that starts with a number before a legitimate address.
218 if (next_word <= kMaxLocationNameDistance &&
219 IsValidLocationName(current_word)) {
220 found_location_name = true;
221 continue;
222 }
223
224 // Don't count the house number.
225 if (next_word > kMinAddressWords) {
226 // Looking for the state is likely to add new words to the list while
227 // checking for multi-word state names.
228 size_t state_first_word = next_word;
229 size_t state_last_word, state_index;
230 if (FindStateStartingInWord(&words, state_first_word, &state_last_word,
231 &tokenizer, &state_index)) {
232
233 // A location name should have been found at this point.
234 if (!found_location_name)
235 break;
236
237 // Explicitly exclude "et al", as "al" is a valid state code.
238 if (current_word_length == 2 && words.size() > 2) {
239 const Word& previous_word = words[state_first_word - 1];
240 if (previous_word.end - previous_word.begin == 2 &&
241 LowerCaseEqualsASCII(previous_word.begin, previous_word.end,
242 "et") &&
243 LowerCaseEqualsASCII(current_word.begin, current_word.end,
244 "al"))
245 break;
246 }
247
248 // Extract one more word from the tokenizer if not already available.
249 size_t zip_word = state_last_word + 1;
250 if (zip_word == words.size()) {
251 do {
252 if (!tokenizer.GetNext())
253 return false;
254 } while (tokenizer.token_is_delim());
255 words.push_back(Word(tokenizer.token_begin(),
256 tokenizer.token_end()));
257 }
258
259 // Check the parsing validity and state range of the zip code.
260 next_word = state_last_word;
261 if (!IsZipValid(words[zip_word], state_index))
262 continue;
263
264 *start_pos = words[0].begin - begin;
265 *end_pos = words[zip_word].end - begin;
266 content_text->assign(GetContentText(string16(words[0].begin,
267 words[zip_word].end)));
268 return true;
269 }
270 }
271 }
272
273 // Avoid skipping too many words because of a non-address number
274 // at the beginning of the contents to parse.
275 if (continue_on_house_number && next_house_number_word > 0) {
276 it = words[next_house_number_word].begin;
277 } else {
278 DCHECK(!words.empty());
279 next_word = std::min(next_word, words.size() - 1);
280 it = words[next_word].end;
281 }
282 }
283
284 return false;
285 }
286
287 AddressDetector::Word::Word(const string16::const_iterator& begin,
288 const string16::const_iterator& end)
289 : begin(begin), 71 : begin(begin),
290 end(end) { 72 end(end) {
291 DCHECK(begin <= end); 73 DCHECK(begin <= end);
292 } 74 }
293 75
294 bool AddressDetector::HouseNumberParser::IsPreDelimiter( 76 bool HouseNumberParser::IsPreDelimiter(char16 character) {
295 char16 character) {
296 return character == ':' || IsPostDelimiter(character); 77 return character == ':' || IsPostDelimiter(character);
297 } 78 }
298 79
299 bool AddressDetector::HouseNumberParser::IsPostDelimiter( 80 bool HouseNumberParser::IsPostDelimiter(char16 character) {
300 char16 character) {
301 return IsWhitespace(character) || strchr(",\"'", character); 81 return IsWhitespace(character) || strchr(",\"'", character);
302 } 82 }
303 83
304 void AddressDetector::HouseNumberParser::RestartOnNextDelimiter() { 84 void HouseNumberParser::RestartOnNextDelimiter() {
305 ResetState(); 85 ResetState();
306 for (; it_ != end_ && !IsPreDelimiter(*it_); ++it_) {} 86 for (; it_ != end_ && !IsPreDelimiter(*it_); ++it_) {}
307 } 87 }
308 88
309 void AddressDetector::HouseNumberParser::AcceptChars(size_t num_chars) { 89 void HouseNumberParser::AcceptChars(size_t num_chars) {
310 size_t offset = std::min(static_cast<size_t>(std::distance(it_, end_)), 90 size_t offset = std::min(static_cast<size_t>(std::distance(it_, end_)),
311 num_chars); 91 num_chars);
312 it_ += offset; 92 it_ += offset;
313 result_chars_ += offset; 93 result_chars_ += offset;
314 } 94 }
315 95
316 void AddressDetector::HouseNumberParser::SkipChars(size_t num_chars) { 96 void HouseNumberParser::SkipChars(size_t num_chars) {
317 it_ += std::min(static_cast<size_t>(std::distance(it_, end_)), num_chars); 97 it_ += std::min(static_cast<size_t>(std::distance(it_, end_)), num_chars);
318 } 98 }
319 99
320 void AddressDetector::HouseNumberParser::ResetState() { 100 void HouseNumberParser::ResetState() {
321 num_digits_ = 0; 101 num_digits_ = 0;
322 result_chars_ = 0; 102 result_chars_ = 0;
323 } 103 }
324 104
325 bool AddressDetector::HouseNumberParser::CheckFinished(Word* word) const { 105 bool HouseNumberParser::CheckFinished(Word* word) const {
326 // There should always be a number after a hyphen. 106 // There should always be a number after a hyphen.
327 if (result_chars_ == 0 || SafePreviousChar(it_, begin_) == '-') 107 if (result_chars_ == 0 || SafePreviousChar(it_, begin_) == '-')
328 return false; 108 return false;
329 109
330 if (word) { 110 if (word) {
331 word->begin = it_ - result_chars_; 111 word->begin = it_ - result_chars_;
332 word->end = it_; 112 word->end = it_;
333 } 113 }
334 return true; 114 return true;
335 } 115 }
336 116
337 bool AddressDetector::HouseNumberParser::Parse( 117 bool HouseNumberParser::Parse(
338 const string16::const_iterator& begin, 118 const string16::const_iterator& begin,
339 const string16::const_iterator& end, Word* word) { 119 const string16::const_iterator& end, Word* word) {
340 it_ = begin_ = begin; 120 it_ = begin_ = begin;
341 end_ = end; 121 end_ = end;
342 ResetState(); 122 ResetState();
343 123
344 // Iterations only used as a fail-safe against any buggy infinite loops. 124 // Iterations only used as a fail-safe against any buggy infinite loops.
345 size_t iterations = 0; 125 size_t iterations = 0;
346 size_t max_iterations = end - begin + 1; 126 size_t max_iterations = end - begin + 1;
347 for (; it_ != end_ && iterations < max_iterations; ++iterations) { 127 for (; it_ != end_ && iterations < max_iterations; ++iterations) {
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after
459 RestartOnNextDelimiter(); 239 RestartOnNextDelimiter();
460 SkipChars(1); 240 SkipChars(1);
461 } 241 }
462 242
463 if (iterations >= max_iterations) 243 if (iterations >= max_iterations)
464 return false; 244 return false;
465 245
466 return CheckFinished(word); 246 return CheckFinished(word);
467 } 247 }
468 248
469 bool AddressDetector::FindStateStartingInWord( 249 bool FindStateStartingInWord(WordList* words,
470 WordList* words, 250 size_t state_first_word,
471 size_t state_first_word, 251 size_t* state_last_word,
472 size_t* state_last_word, 252 String16Tokenizer* tokenizer,
473 String16Tokenizer* tokenizer, 253 size_t* state_index) {
474 size_t* state_index) {
475 254
476 // Bitmasks containing the allowed suffixes for 2-letter state codes. 255 // Bitmasks containing the allowed suffixes for 2-letter state codes.
477 static const int state_two_letter_suffix[23] = { 256 static const int state_two_letter_suffix[23] = {
478 0x02060c00, // A followed by: [KLRSZ]. 257 0x02060c00, // A followed by: [KLRSZ].
479 0x00000000, // B. 258 0x00000000, // B.
480 0x00084001, // C followed by: [AOT]. 259 0x00084001, // C followed by: [AOT].
481 0x00000014, // D followed by: [CE]. 260 0x00000014, // D followed by: [CE].
482 0x00000000, // E. 261 0x00000000, // E.
483 0x00001800, // F followed by: [LM]. 262 0x00001800, // F followed by: [LM].
484 0x00100001, // G followed by: [AU]. 263 0x00100001, // G followed by: [AU].
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after
626 if (state_match) { 405 if (state_match) {
627 *state_last_word = state_word; 406 *state_last_word = state_word;
628 *state_index = state_names[state].state_index; 407 *state_index = state_names[state].state_index;
629 return true; 408 return true;
630 } 409 }
631 } 410 }
632 411
633 return false; 412 return false;
634 } 413 }
635 414
636 bool AddressDetector::IsZipValid(const Word& word, size_t state_index) { 415 bool IsZipValid(const Word& word, size_t state_index) {
637 size_t length = word.end - word.begin; 416 size_t length = word.end - word.begin;
638 if (length != kZipDigits && length != kZipPlus4Digits + 1) 417 if (length != kZipDigits && length != kZipPlus4Digits + 1)
639 return false; 418 return false;
640 419
641 for (string16::const_iterator it = word.begin; it != word.end; ++it) { 420 for (string16::const_iterator it = word.begin; it != word.end; ++it) {
642 size_t pos = it - word.begin; 421 size_t pos = it - word.begin;
643 if (IsAsciiDigit(*it) || (*it == '-' && pos == kZipDigits)) 422 if (IsAsciiDigit(*it) || (*it == '-' && pos == kZipDigits))
644 continue; 423 continue;
645 return false; 424 return false;
646 } 425 }
647 return IsZipValidForState(word, state_index); 426 return IsZipValidForState(word, state_index);
648 } 427 }
649 428
650 bool AddressDetector::IsZipValidForState(const Word& word, size_t state_index) { 429 bool IsZipValidForState(const Word& word, size_t state_index) {
651 // List of valid zip code ranges. 430 // List of valid zip code ranges.
652 static const struct { 431 static const struct {
653 char low; 432 char low;
654 char high; 433 char high;
655 char exception1; 434 char exception1;
656 char exception2; 435 char exception2;
657 } zip_range[] = { 436 } zip_range[] = {
658 { 99, 99, -1, -1 }, // AK Alaska. 437 { 99, 99, -1, -1 }, // AK Alaska.
659 { 35, 36, -1, -1 }, // AL Alabama. 438 { 35, 36, -1, -1 }, // AL Alabama.
660 { 71, 72, -1, -1 }, // AR Arkansas. 439 { 71, 72, -1, -1 }, // AR Arkansas.
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
724 503
725 if ((zip_prefix >= zip_range[state_index].low && 504 if ((zip_prefix >= zip_range[state_index].low &&
726 zip_prefix <= zip_range[state_index].high) || 505 zip_prefix <= zip_range[state_index].high) ||
727 zip_prefix == zip_range[state_index].exception1 || 506 zip_prefix == zip_range[state_index].exception1 ||
728 zip_prefix == zip_range[state_index].exception2) { 507 zip_prefix == zip_range[state_index].exception2) {
729 return true; 508 return true;
730 } 509 }
731 return false; 510 return false;
732 } 511 }
733 512
734 bool AddressDetector::IsValidLocationName(const Word& word) { 513 bool IsValidLocationName(const Word& word) {
735 // Supported location names sorted alphabetically and grouped by first letter. 514 // Supported location names sorted alphabetically and grouped by first letter.
736 static const struct LocationNameInfo { 515 static const struct LocationNameInfo {
737 const char* string; 516 const char* string;
738 char length; 517 char length;
739 bool allow_plural; 518 bool allow_plural;
740 } location_names[157] = { 519 } location_names[157] = {
741 { "alley", 5, false }, { "annex", 5, false }, { "arcade", 6, false }, 520 { "alley", 5, false }, { "annex", 5, false }, { "arcade", 6, false },
742 { "ave", 3, false }, { "ave.", 4, false }, { "avenue", 6, false }, 521 { "ave", 3, false }, { "ave.", 4, false }, { "avenue", 6, false },
743 { "alameda", 7, false }, 522 { "alameda", 7, false },
744 { "bayou", 5, false }, { "beach", 5, false }, { "bend", 4, false }, 523 { "bayou", 5, false }, { "beach", 5, false }, { "bend", 4, false },
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after
835 if (LowerCaseEqualsASCIIWithPlural(word.begin, word.end, 614 if (LowerCaseEqualsASCIIWithPlural(word.begin, word.end,
836 location_names[i].string, 615 location_names[i].string,
837 location_names[i].allow_plural)) { 616 location_names[i].allow_plural)) {
838 return true; 617 return true;
839 } 618 }
840 } 619 }
841 620
842 return false; 621 return false;
843 } 622 }
844 623
624 } // namespace internal
625
626 } // namespace address_parser
627
845 } // namespace content 628 } // namespace content
OLDNEW
« no previous file with comments | « content/common/android/address_parser_internal.h ('k') | content/common/android/address_parser_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698