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

Side by Side Diff: content/renderer/android/address_detector.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/renderer/android/address_detector.h"
6 6
7 #include <bitset> 7 #include <bitset>
8 8
9 #include "base/logging.h"
10 #include "base/string_util.h" 9 #include "base/string_util.h"
11 #include "base/utf_string_conversions.h" 10 #include "base/utf_string_conversions.h"
11 #include "content/common/android/address_parser.h"
12 #include "net/base/escape.h" 12 #include "net/base/escape.h"
13 #include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h"
14 13
15 namespace { 14 namespace {
16 15
17 // Prefix used for geographical address intent URIs. 16 // Prefix used for geographical address intent URIs.
18 static const char kAddressSchemaPrefix[] = "geo:0,0?q="; 17 static const char kAddressSchemaPrefix[] = "geo:0,0?q=";
19 18
20 // Maximum text length to be searched for address detection. 19 // Maximum text length to be searched for address detection.
21 static const size_t kMaxAddressLength = 250; 20 static const size_t kMaxAddressLength = 250;
22 21
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.
45 const size_t kZipDigits = 5;
46
47 // Number of digits for a valid zip code in the Zip Plus 4 format.
48 const size_t kZipPlus4Digits = 9;
49
50 // Maximum number of digits of a house number, including possible hyphens.
51 const size_t kMaxHouseDigits = 5;
52
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,
63 const string16::const_iterator& begin) {
64 if (it == begin)
65 return ' ';
66 return *(it - 1);
67 }
68
69 char16 SafeNextChar(const string16::const_iterator& it,
70 const string16::const_iterator& end) {
71 if (it == end)
72 return ' ';
73 return *(it + 1);
74 }
75
76 bool WordLowerCaseEqualsASCII(string16::const_iterator word_begin,
77 string16::const_iterator word_end, const char* ascii_to_match) {
78 for (string16::const_iterator it = word_begin; it != word_end;
79 ++it, ++ascii_to_match) {
80 if (!*ascii_to_match || base::ToLowerASCII(*it) != *ascii_to_match)
81 return false;
82 }
83 return *ascii_to_match == 0 || *ascii_to_match == ' ';
84 }
85
86 bool LowerCaseEqualsASCIIWithPlural(string16::const_iterator word_begin,
87 string16::const_iterator word_end, const char* ascii_to_match,
88 bool allow_plural) {
89 for (string16::const_iterator it = word_begin; it != word_end;
90 ++it, ++ascii_to_match) {
91 if (!*ascii_to_match && allow_plural && *it == 's' && it + 1 == word_end)
92 return true;
93
94 if (!*ascii_to_match || base::ToLowerASCII(*it) != *ascii_to_match)
95 return false;
96 }
97 return *ascii_to_match == 0;
98 }
99
100 } // anonymous namespace 22 } // anonymous namespace
101 23
102 namespace content { 24 namespace content {
103 25
104 AddressDetector::AddressDetector() { 26 AddressDetector::AddressDetector() {
105 } 27 }
106 28
107 AddressDetector::~AddressDetector() { 29 AddressDetector::~AddressDetector() {
108 } 30 }
109 31
110 std::string AddressDetector::GetContentText(const string16& text) {
111 // Get the address and replace unicode bullets with commas.
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) { 32 GURL AddressDetector::GetIntentURL(const std::string& content_text) {
119 return GURL(kAddressSchemaPrefix + 33 return GURL(kAddressSchemaPrefix +
120 net::EscapeQueryParamValue(content_text, true)); 34 net::EscapeQueryParamValue(content_text, true));
121 } 35 }
122 36
123 size_t AddressDetector::GetMaximumContentLength() { 37 size_t AddressDetector::GetMaximumContentLength() {
124 return kMaxAddressLength; 38 return kMaxAddressLength;
125 } 39 }
126 40
127 bool AddressDetector::FindContent(const string16::const_iterator& begin, 41 std::string AddressDetector::GetContentText(const string16& text) {
128 const string16::const_iterator& end, 42 // Get the address and replace unicode bullets with commas.
129 size_t* start_pos, 43 string16 address_16 = CollapseWhitespace(text, false);
130 size_t* end_pos, 44 std::replace(address_16.begin(), address_16.end(),
131 std::string* content_text) { 45 static_cast<char16>(0x2022), static_cast<char16>(','));
132 HouseNumberParser house_number_parser; 46 return UTF16ToUTF8(address_16);
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 } 47 }
286 48
287 AddressDetector::Word::Word(const string16::const_iterator& begin, 49 bool AddressDetector::FindContent(const string16::const_iterator& begin,
288 const string16::const_iterator& end) 50 const string16::const_iterator& end, size_t* start_pos, size_t* end_pos,
289 : begin(begin), 51 std::string* content_text) {
290 end(end) { 52 if (address_parser::FindAddress(begin, end, start_pos, end_pos)) {
291 DCHECK(begin <= end); 53 content_text->assign(
292 } 54 GetContentText(string16(begin + *start_pos, begin + *end_pos)));
293
294 bool AddressDetector::HouseNumberParser::IsPreDelimiter(
295 char16 character) {
296 return character == ':' || IsPostDelimiter(character);
297 }
298
299 bool AddressDetector::HouseNumberParser::IsPostDelimiter(
300 char16 character) {
301 return IsWhitespace(character) || strchr(",\"'", character);
302 }
303
304 void AddressDetector::HouseNumberParser::RestartOnNextDelimiter() {
305 ResetState();
306 for (; it_ != end_ && !IsPreDelimiter(*it_); ++it_) {}
307 }
308
309 void AddressDetector::HouseNumberParser::AcceptChars(size_t num_chars) {
310 size_t offset = std::min(static_cast<size_t>(std::distance(it_, end_)),
311 num_chars);
312 it_ += offset;
313 result_chars_ += offset;
314 }
315
316 void AddressDetector::HouseNumberParser::SkipChars(size_t num_chars) {
317 it_ += std::min(static_cast<size_t>(std::distance(it_, end_)), num_chars);
318 }
319
320 void AddressDetector::HouseNumberParser::ResetState() {
321 num_digits_ = 0;
322 result_chars_ = 0;
323 }
324
325 bool AddressDetector::HouseNumberParser::CheckFinished(Word* word) const {
326 // There should always be a number after a hyphen.
327 if (result_chars_ == 0 || SafePreviousChar(it_, begin_) == '-')
328 return false;
329
330 if (word) {
331 word->begin = it_ - result_chars_;
332 word->end = it_;
333 }
334 return true;
335 }
336
337 bool AddressDetector::HouseNumberParser::Parse(
338 const string16::const_iterator& begin,
339 const string16::const_iterator& end, Word* word) {
340 it_ = begin_ = begin;
341 end_ = end;
342 ResetState();
343
344 // Iterations only used as a fail-safe against any buggy infinite loops.
345 size_t iterations = 0;
346 size_t max_iterations = end - begin + 1;
347 for (; it_ != end_ && iterations < max_iterations; ++iterations) {
348
349 // Word finished case.
350 if (IsPostDelimiter(*it_)) {
351 if (CheckFinished(word))
352 return true;
353 else if (result_chars_)
354 ResetState();
355
356 SkipChars(1);
357 continue;
358 }
359
360 // More digits. There should be no more after a letter was found.
361 if (IsAsciiDigit(*it_)) {
362 if (num_digits_ >= kMaxHouseDigits) {
363 RestartOnNextDelimiter();
364 } else {
365 AcceptChars(1);
366 ++num_digits_;
367 }
368 continue;
369 }
370
371 if (IsAsciiAlpha(*it_)) {
372 // Handle special case 'one'.
373 if (result_chars_ == 0) {
374 if (it_ + 3 <= end_ && LowerCaseEqualsASCII(it_, it_ + 3, "one"))
375 AcceptChars(3);
376 else
377 RestartOnNextDelimiter();
378 continue;
379 }
380
381 // There should be more than 1 character because of result_chars.
382 DCHECK_GT(result_chars_, 0U);
383 DCHECK(it_ != begin_);
384 char16 previous = SafePreviousChar(it_, begin_);
385 if (IsAsciiDigit(previous)) {
386 // Check cases like '12A'.
387 char16 next = SafeNextChar(it_, end_);
388 if (IsPostDelimiter(next)) {
389 AcceptChars(1);
390 continue;
391 }
392
393 // Handle cases like 12a, 1st, 2nd, 3rd, 7th.
394 if (IsAsciiAlpha(next)) {
395 char16 last_digit = previous;
396 char16 first_letter = base::ToLowerASCII(*it_);
397 char16 second_letter = base::ToLowerASCII(next);
398 bool is_teen = SafePreviousChar(it_ - 1, begin_) == '1' &&
399 num_digits_ == 2;
400
401 switch (last_digit - '0') {
402 case 1:
403 if ((first_letter == 's' && second_letter == 't') ||
404 (first_letter == 't' && second_letter == 'h' && is_teen)) {
405 AcceptChars(2);
406 continue;
407 }
408 break;
409
410 case 2:
411 if ((first_letter == 'n' && second_letter == 'd') ||
412 (first_letter == 't' && second_letter == 'h' && is_teen)) {
413 AcceptChars(2);
414 continue;
415 }
416 break;
417
418 case 3:
419 if ((first_letter == 'r' && second_letter == 'd') ||
420 (first_letter == 't' && second_letter == 'h' && is_teen)) {
421 AcceptChars(2);
422 continue;
423 }
424 break;
425
426 case 0:
427 // Explicitly exclude '0th'.
428 if (num_digits_ == 1)
429 break;
430
431 case 4:
432 case 5:
433 case 6:
434 case 7:
435 case 8:
436 case 9:
437 if (first_letter == 't' && second_letter == 'h') {
438 AcceptChars(2);
439 continue;
440 }
441 break;
442
443 default:
444 NOTREACHED();
445 }
446 }
447 }
448
449 RestartOnNextDelimiter();
450 continue;
451 }
452
453 if (*it_ == '-' && num_digits_ > 0) {
454 AcceptChars(1);
455 ++num_digits_;
456 continue;
457 }
458
459 RestartOnNextDelimiter();
460 SkipChars(1);
461 }
462
463 if (iterations >= max_iterations)
464 return false;
465
466 return CheckFinished(word);
467 }
468
469 bool AddressDetector::FindStateStartingInWord(
470 WordList* words,
471 size_t state_first_word,
472 size_t* state_last_word,
473 String16Tokenizer* tokenizer,
474 size_t* state_index) {
475
476 // Bitmasks containing the allowed suffixes for 2-letter state codes.
477 static const int state_two_letter_suffix[23] = {
478 0x02060c00, // A followed by: [KLRSZ].
479 0x00000000, // B.
480 0x00084001, // C followed by: [AOT].
481 0x00000014, // D followed by: [CE].
482 0x00000000, // E.
483 0x00001800, // F followed by: [LM].
484 0x00100001, // G followed by: [AU].
485 0x00000100, // H followed by: [I].
486 0x00002809, // I followed by: [ADLN].
487 0x00000000, // J.
488 0x01040000, // K followed by: [SY].
489 0x00000001, // L followed by: [A].
490 0x000ce199, // M followed by: [ADEHINOPST].
491 0x0120129c, // N followed by: [CDEHJMVY].
492 0x00020480, // O followed by: [HKR].
493 0x00420001, // P followed by: [ARW].
494 0x00000000, // Q.
495 0x00000100, // R followed by: [I].
496 0x0000000c, // S followed by: [CD].
497 0x00802000, // T followed by: [NX].
498 0x00080000, // U followed by: [T].
499 0x00080101, // V followed by: [AIT].
500 0x01200101 // W followed by: [AIVY].
501 };
502
503 // Accumulative number of states for the 2-letter code indexed by the first.
504 static const int state_two_letter_accumulative[24] = {
505 0, 5, 5, 8, 10, 10, 12, 14,
506 15, 19, 19, 21, 22, 32, 40, 43,
507 46, 46, 47, 49, 51, 52, 55, 59
508 };
509
510 // State names sorted alphabetically with their lengths.
511 // There can be more than one possible name for a same state if desired.
512 static const struct StateNameInfo {
513 const char* string;
514 char first_word_length;
515 char length;
516 char state_index; // Relative to two-character code alphabetical order.
517 } state_names[59] = {
518 { "alabama", 7, 7, 1 }, { "alaska", 6, 6, 0 },
519 { "american samoa", 8, 14, 3 }, { "arizona", 7, 7, 4 },
520 { "arkansas", 8, 8, 2 },
521 { "california", 10, 10, 5 }, { "colorado", 8, 8, 6 },
522 { "connecticut", 11, 11, 7 }, { "delaware", 8, 8, 9 },
523 { "district of columbia", 8, 20, 8 },
524 { "federated states of micronesia", 9, 30, 11 }, { "florida", 7, 7, 10 },
525 { "guam", 4, 4, 13 }, { "georgia", 7, 7, 12 },
526 { "hawaii", 6, 6, 14 },
527 { "idaho", 5, 5, 16 }, { "illinois", 8, 8, 17 }, { "indiana", 7, 7, 18 },
528 { "iowa", 4, 4, 15 },
529 { "kansas", 6, 6, 19 }, { "kentucky", 8, 8, 20 },
530 { "louisiana", 9, 9, 21 },
531 { "maine", 5, 5, 24 }, { "marshall islands", 8, 16, 25 },
532 { "maryland", 8, 8, 23 }, { "massachusetts", 13, 13, 22 },
533 { "michigan", 8, 8, 26 }, { "minnesota", 9, 9, 27 },
534 { "mississippi", 11, 11, 30 }, { "missouri", 8, 8, 28 },
535 { "montana", 7, 7, 31 },
536 { "nebraska", 8, 8, 34 }, { "nevada", 6, 6, 38 },
537 { "new hampshire", 3, 13, 35 }, { "new jersey", 3, 10, 36 },
538 { "new mexico", 3, 10, 37 }, { "new york", 3, 8, 39 },
539 { "north carolina", 5, 14, 32 }, { "north dakota", 5, 12, 33 },
540 { "northern mariana islands", 8, 24, 29 },
541 { "ohio", 4, 4, 40 }, { "oklahoma", 8, 8, 41 }, { "oregon", 6, 6, 42 },
542 { "palau", 5, 5, 45 }, { "pennsylvania", 12, 12, 43 },
543 { "puerto rico", 6, 11, 44 },
544 { "rhode island", 5, 5, 46 },
545 { "south carolina", 5, 14, 47 }, { "south dakota", 5, 12, 48 },
546 { "tennessee", 9, 9, 49 }, { "texas", 5, 5, 50 },
547 { "utah", 4, 4, 51 },
548 { "vermont", 7, 7, 54 }, { "virgin islands", 6, 14, 53 },
549 { "virginia", 8, 8, 52 },
550 { "washington", 10, 10, 55 }, { "west virginia", 4, 13, 57 },
551 { "wisconsin", 9, 9, 56 }, { "wyoming", 7, 7, 58 }
552 };
553
554 // Accumulative number of states for sorted names indexed by the first letter.
555 // Required a different one since there are codes that don't share their
556 // first letter with the name of their state (MP = Northern Mariana Islands).
557 static const int state_names_accumulative[24] = {
558 0, 5, 5, 8, 10, 10, 12, 14,
559 15, 19, 19, 21, 22, 31, 40, 43,
560 46, 46, 47, 49, 51, 52, 55, 59
561 };
562
563 DCHECK_EQ(state_names_accumulative[arraysize(state_names_accumulative) - 1],
564 static_cast<int>(ARRAYSIZE_UNSAFE(state_names)));
565
566 const Word& first_word = words->at(state_first_word);
567 int length = first_word.end - first_word.begin;
568 if (length < 2 || !IsAsciiAlpha(*first_word.begin))
569 return false;
570
571 // No state names start with x, y, z.
572 char16 first_letter = base::ToLowerASCII(*first_word.begin);
573 if (first_letter > 'w')
574 return false;
575
576 DCHECK(first_letter >= 'a');
577 int first_index = first_letter - 'a';
578
579 // Look for two-letter state names.
580 if (length == 2 && IsAsciiAlpha(*(first_word.begin + 1))) {
581 char16 second_letter = base::ToLowerASCII(*(first_word.begin + 1));
582 DCHECK(second_letter >= 'a');
583
584 int second_index = second_letter - 'a';
585 if (!(state_two_letter_suffix[first_index] & (1 << second_index)))
586 return false;
587
588 std::bitset<32> previous_suffixes = state_two_letter_suffix[first_index] &
589 ((1 << second_index) - 1);
590 *state_last_word = state_first_word;
591 *state_index = state_two_letter_accumulative[first_index] +
592 previous_suffixes.count();
593 return true;
594 }
595
596 // Look for full state names by their first letter. Discard by length.
597 for (int state = state_names_accumulative[first_index];
598 state < state_names_accumulative[first_index + 1]; ++state) {
599 if (state_names[state].first_word_length != length)
600 continue;
601
602 bool state_match = false;
603 size_t state_word = state_first_word;
604 for (int pos = 0; true; ) {
605 if (!WordLowerCaseEqualsASCII(words->at(state_word).begin,
606 words->at(state_word).end, &state_names[state].string[pos]))
607 break;
608
609 pos += words->at(state_word).end - words->at(state_word).begin + 1;
610 if (pos >= state_names[state].length) {
611 state_match = true;
612 break;
613 }
614
615 // Ran out of words, extract more from the tokenizer.
616 if (++state_word == words->size()) {
617 do {
618 if (!tokenizer->GetNext())
619 break;
620 } while (tokenizer->token_is_delim());
621 words->push_back(
622 Word(tokenizer->token_begin(), tokenizer->token_end()));
623 }
624 }
625
626 if (state_match) {
627 *state_last_word = state_word;
628 *state_index = state_names[state].state_index;
629 return true;
630 }
631 }
632
633 return false;
634 }
635
636 bool AddressDetector::IsZipValid(const Word& word, size_t state_index) {
637 size_t length = word.end - word.begin;
638 if (length != kZipDigits && length != kZipPlus4Digits + 1)
639 return false;
640
641 for (string16::const_iterator it = word.begin; it != word.end; ++it) {
642 size_t pos = it - word.begin;
643 if (IsAsciiDigit(*it) || (*it == '-' && pos == kZipDigits))
644 continue;
645 return false;
646 }
647 return IsZipValidForState(word, state_index);
648 }
649
650 bool AddressDetector::IsZipValidForState(const Word& word, size_t state_index) {
651 // List of valid zip code ranges.
652 static const struct {
653 char low;
654 char high;
655 char exception1;
656 char exception2;
657 } zip_range[] = {
658 { 99, 99, -1, -1 }, // AK Alaska.
659 { 35, 36, -1, -1 }, // AL Alabama.
660 { 71, 72, -1, -1 }, // AR Arkansas.
661 { 96, 96, -1, -1 }, // AS American Samoa.
662 { 85, 86, -1, -1 }, // AZ Arizona.
663 { 90, 96, -1, -1 }, // CA California.
664 { 80, 81, -1, -1 }, // CO Colorado.
665 { 6, 6, -1, -1 }, // CT Connecticut.
666 { 20, 20, -1, -1 }, // DC District of Columbia.
667 { 19, 19, -1, -1 }, // DE Delaware.
668 { 32, 34, -1, -1 }, // FL Florida.
669 { 96, 96, -1, -1 }, // FM Federated States of Micronesia.
670 { 30, 31, -1, -1 }, // GA Georgia.
671 { 96, 96, -1, -1 }, // GU Guam.
672 { 96, 96, -1, -1 }, // HI Hawaii.
673 { 50, 52, -1, -1 }, // IA Iowa.
674 { 83, 83, -1, -1 }, // ID Idaho.
675 { 60, 62, -1, -1 }, // IL Illinois.
676 { 46, 47, -1, -1 }, // IN Indiana.
677 { 66, 67, 73, -1 }, // KS Kansas.
678 { 40, 42, -1, -1 }, // KY Kentucky.
679 { 70, 71, -1, -1 }, // LA Louisiana.
680 { 1, 2, -1, -1 }, // MA Massachusetts.
681 { 20, 21, -1, -1 }, // MD Maryland.
682 { 3, 4, -1, -1 }, // ME Maine.
683 { 96, 96, -1, -1 }, // MH Marshall Islands.
684 { 48, 49, -1, -1 }, // MI Michigan.
685 { 55, 56, -1, -1 }, // MN Minnesota.
686 { 63, 65, -1, -1 }, // MO Missouri.
687 { 96, 96, -1, -1 }, // MP Northern Mariana Islands.
688 { 38, 39, -1, -1 }, // MS Mississippi.
689 { 55, 56, -1, -1 }, // MT Montana.
690 { 27, 28, -1, -1 }, // NC North Carolina.
691 { 58, 58, -1, -1 }, // ND North Dakota.
692 { 68, 69, -1, -1 }, // NE Nebraska.
693 { 3, 4, -1, -1 }, // NH New Hampshire.
694 { 7, 8, -1, -1 }, // NJ New Jersey.
695 { 87, 88, 86, -1 }, // NM New Mexico.
696 { 88, 89, 96, -1 }, // NV Nevada.
697 { 10, 14, 0, 6 }, // NY New York.
698 { 43, 45, -1, -1 }, // OH Ohio.
699 { 73, 74, -1, -1 }, // OK Oklahoma.
700 { 97, 97, -1, -1 }, // OR Oregon.
701 { 15, 19, -1, -1 }, // PA Pennsylvania.
702 { 6, 6, 0, 9 }, // PR Puerto Rico.
703 { 96, 96, -1, -1 }, // PW Palau.
704 { 2, 2, -1, -1 }, // RI Rhode Island.
705 { 29, 29, -1, -1 }, // SC South Carolina.
706 { 57, 57, -1, -1 }, // SD South Dakota.
707 { 37, 38, -1, -1 }, // TN Tennessee.
708 { 75, 79, 87, 88 }, // TX Texas.
709 { 84, 84, -1, -1 }, // UT Utah.
710 { 22, 24, 20, -1 }, // VA Virginia.
711 { 6, 9, -1, -1 }, // VI Virgin Islands.
712 { 5, 5, -1, -1 }, // VT Vermont.
713 { 98, 99, -1, -1 }, // WA Washington.
714 { 53, 54, -1, -1 }, // WI Wisconsin.
715 { 24, 26, -1, -1 }, // WV West Virginia.
716 { 82, 83, -1, -1 } // WY Wyoming.
717 };
718
719 // Zip numeric value for the first two characters.
720 DCHECK(word.begin != word.end);
721 DCHECK(IsAsciiDigit(*word.begin));
722 DCHECK(IsAsciiDigit(*(word.begin + 1)));
723 int zip_prefix = (*word.begin - '0') * 10 + (*(word.begin + 1) - '0');
724
725 if ((zip_prefix >= zip_range[state_index].low &&
726 zip_prefix <= zip_range[state_index].high) ||
727 zip_prefix == zip_range[state_index].exception1 ||
728 zip_prefix == zip_range[state_index].exception2) {
729 return true; 55 return true;
730 } 56 }
731 return false; 57 return false;
732 } 58 }
733 59
734 bool AddressDetector::IsValidLocationName(const Word& word) {
735 // Supported location names sorted alphabetically and grouped by first letter.
736 static const struct LocationNameInfo {
737 const char* string;
738 char length;
739 bool allow_plural;
740 } location_names[157] = {
741 { "alley", 5, false }, { "annex", 5, false }, { "arcade", 6, false },
742 { "ave", 3, false }, { "ave.", 4, false }, { "avenue", 6, false },
743 { "alameda", 7, false },
744 { "bayou", 5, false }, { "beach", 5, false }, { "bend", 4, false },
745 { "bluff", 5, true }, { "bottom", 6, false }, { "boulevard", 9, false },
746 { "branch", 6, false }, { "bridge", 6, false }, { "brook", 5, true },
747 { "burg", 4, true }, { "bypass", 6, false }, { "broadway", 8, false },
748 { "camino", 6, false }, { "camp", 4, false }, { "canyon", 6, false },
749 { "cape", 4, false }, { "causeway", 8, false }, { "center", 6, true },
750 { "circle", 6, true }, { "cliff", 5, true }, { "club", 4, false },
751 { "common", 6, false }, { "corner", 6, true }, { "course", 6, false },
752 { "court", 5, true }, { "cove", 4, true }, { "creek", 5, false },
753 { "crescent", 8, false }, { "crest", 5, false }, { "crossing", 8, false },
754 { "crossroad", 9, false }, { "curve", 5, false }, { "circulo", 7, false },
755 { "dale", 4, false }, { "dam", 3, false }, { "divide", 6, false },
756 { "drive", 5, true },
757 { "estate", 6, true }, { "expressway", 10, false },
758 { "extension", 9, true },
759 { "fall", 4, true }, { "ferry", 5, false }, { "field", 5, true },
760 { "flat", 4, true }, { "ford", 4, true }, { "forest", 6, false },
761 { "forge", 5, true }, { "fork", 4, true }, { "fort", 4, false },
762 { "freeway", 7, false },
763 { "garden", 6, true }, { "gateway", 7, false }, { "glen", 4, true },
764 { "green", 5, true }, { "grove", 5, true },
765 { "harbor", 6, true }, { "haven", 5, false }, { "heights", 7, false },
766 { "highway", 7, false }, { "hill", 4, true }, { "hollow", 6, false },
767 { "inlet", 5, false }, { "island", 6, true }, { "isle", 4, false },
768 { "junction", 8, true },
769 { "key", 3, true }, { "knoll", 5, true },
770 { "lake", 4, true }, { "land", 4, false }, { "landing", 7, false },
771 { "lane", 4, false }, { "light", 5, true }, { "loaf", 4, false },
772 { "lock", 4, true }, { "lodge", 5, false }, { "loop", 4, false },
773 { "mall", 4, false }, { "manor", 5, true }, { "meadow", 6, true },
774 { "mews", 4, false }, { "mill", 4, true }, { "mission", 7, false },
775 { "motorway", 8, false }, { "mount", 5, false }, { "mountain", 8, true },
776 { "neck", 4, false },
777 { "orchard", 7, false }, { "oval", 4, false }, { "overpass", 8, false },
778 { "park", 4, true }, { "parkway", 7, true }, { "pass", 4, false },
779 { "passage", 7, false }, { "path", 4, false }, { "pike", 4, false },
780 { "pine", 4, true }, { "plain", 5, true }, { "plaza", 5, false },
781 { "point", 5, true }, { "port", 4, true }, { "prairie", 7, false },
782 { "privada", 7, false },
783 { "radial", 6, false }, { "ramp", 4, false }, { "ranch", 5, false },
784 { "rapid", 5, true }, { "rest", 4, false }, { "ridge", 5, true },
785 { "river", 5, false }, { "road", 4, true }, { "route", 5, false },
786 { "row", 3, false }, { "rue", 3, false }, { "run", 3, false },
787 { "shoal", 5, true }, { "shore", 5, true }, { "skyway", 6, false },
788 { "spring", 6, true }, { "spur", 4, true }, { "square", 6, true },
789 { "station", 7, false }, { "stravenue", 9, false }, { "stream", 6, false },
790 { "st", 2, false }, { "st.", 3, false }, { "street", 6, true },
791 { "summit", 6, false }, { "speedway", 8, false },
792 { "terrace", 7, false }, { "throughway", 10, false }, { "trace", 5, false },
793 { "track", 5, false }, { "trafficway", 10, false }, { "trail", 5, false },
794 { "tunnel", 6, false }, { "turnpike", 8, false },
795 { "underpass", 9, false }, { "union", 5, true },
796 { "valley", 6, true }, { "viaduct", 7, false }, { "view", 4, true },
797 { "village", 7, true }, { "ville", 5, false }, { "vista", 5, false },
798 { "walk", 4, true }, { "wall", 4, false }, { "way", 3, true },
799 { "well", 4, true },
800 { "xing", 4, false }, { "xrd", 3, false }
801 };
802
803 // Accumulative number of location names for each starting letter.
804 static const int location_names_accumulative[25] = {
805 0, 7, 19, 40, 44,
806 47, 57, 62, 68, 71,
807 72, 74, 83, 92, 93,
808 96, 109, 109, 121, 135,
809 143, 145, 151, 155, 157
810 };
811
812 DCHECK_EQ(
813 location_names_accumulative[arraysize(location_names_accumulative) - 1],
814 static_cast<int>(ARRAYSIZE_UNSAFE(location_names)));
815
816 if (!IsAsciiAlpha(*word.begin))
817 return false;
818
819 // No location names start with y, z.
820 char16 first_letter = base::ToLowerASCII(*word.begin);
821 if (first_letter > 'x')
822 return false;
823
824 DCHECK(first_letter >= 'a');
825 int index = first_letter - 'a';
826 int length = std::distance(word.begin, word.end);
827 for (int i = location_names_accumulative[index];
828 i < location_names_accumulative[index + 1]; ++i) {
829 if (location_names[i].length != length &&
830 (location_names[i].allow_plural &&
831 location_names[i].length + 1 != length)) {
832 continue;
833 }
834
835 if (LowerCaseEqualsASCIIWithPlural(word.begin, word.end,
836 location_names[i].string,
837 location_names[i].allow_plural)) {
838 return true;
839 }
840 }
841
842 return false;
843 }
844
845 } // namespace content 60 } // namespace content
OLDNEW
« no previous file with comments | « content/renderer/android/address_detector.h ('k') | content/renderer/android/address_detector_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698