| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/autofill/contact_info.h" |
| |
| #include "base/basictypes.h" |
| #include "base/string_util.h" |
| #include "chrome/browser/autofill/autofill_type.h" |
| #include "chrome/browser/autofill/field_types.h" |
| |
| static const string16 kNameSplitChars = ASCIIToUTF16("-'. "); |
| |
| static const AutoFillFieldType kAutoFillContactInfoTypes[] = { |
| NAME_FIRST, |
| NAME_MIDDLE, |
| NAME_LAST, |
| EMAIL_ADDRESS, |
| COMPANY_NAME, |
| }; |
| |
| static const size_t kAutoFillContactInfoLength = |
| arraysize(kAutoFillContactInfoTypes); |
| |
| FormGroup* ContactInfo::Clone() const { |
| return new ContactInfo(*this); |
| } |
| |
| void ContactInfo::GetPossibleFieldTypes(const string16& text, |
| FieldTypeSet* possible_types) const { |
| if (IsFirstName(text)) |
| possible_types->insert(NAME_FIRST); |
| |
| if (IsMiddleName(text)) |
| possible_types->insert(NAME_MIDDLE); |
| |
| if (IsLastName(text)) |
| possible_types->insert(NAME_LAST); |
| |
| if (IsMiddleInitial(text)) |
| possible_types->insert(NAME_MIDDLE_INITIAL); |
| |
| if (IsSuffix(text)) |
| possible_types->insert(NAME_SUFFIX); |
| |
| if (IsFullName(text)) |
| possible_types->insert(NAME_FULL); |
| |
| if (email_ == text) |
| possible_types->insert(EMAIL_ADDRESS); |
| |
| if (company_name_ == text) |
| possible_types->insert(COMPANY_NAME); |
| } |
| |
| void ContactInfo::FindInfoMatches(const AutoFillType& type, |
| const string16& info, |
| std::vector<string16>* matched_text) const { |
| if (matched_text == NULL) { |
| DLOG(ERROR) << "NULL matched vector passed in"; |
| return; |
| } |
| |
| string16 match; |
| if (type.field_type() == UNKNOWN_TYPE) { |
| for (size_t i = 0; i < kAutoFillContactInfoLength; i++) { |
| if (FindInfoMatchesHelper(kAutoFillContactInfoTypes[i], info, &match)) |
| matched_text->push_back(match); |
| } |
| } else if (FindInfoMatchesHelper(type.field_type(), info, &match)) { |
| matched_text->push_back(match); |
| } |
| } |
| |
| string16 ContactInfo::GetFieldText(const AutoFillType& type) const { |
| AutoFillFieldType field_type = type.field_type(); |
| if (field_type == NAME_FIRST) |
| return first(); |
| |
| if (field_type == NAME_MIDDLE) |
| return middle(); |
| |
| if (field_type == NAME_LAST) |
| return last(); |
| |
| if (field_type == NAME_MIDDLE_INITIAL) |
| return MiddleInitial(); |
| |
| if (field_type == NAME_FULL) |
| return FullName(); |
| |
| if (field_type == NAME_SUFFIX) |
| return suffix(); |
| |
| if (field_type == EMAIL_ADDRESS) |
| return email(); |
| |
| if (field_type == COMPANY_NAME) |
| return company_name(); |
| |
| return string16(); |
| } |
| |
| void ContactInfo::SetInfo(const AutoFillType& type, const string16& value) { |
| AutoFillFieldType field_type = type.field_type(); |
| DCHECK(type.group() == AutoFillType::CONTACT_INFO); |
| if (field_type == NAME_FIRST) |
| SetFirst(value); |
| else if (field_type == NAME_MIDDLE || field_type == NAME_MIDDLE_INITIAL) |
| SetMiddle(value); |
| else if (field_type == NAME_LAST) |
| SetLast(value); |
| else if (field_type == NAME_SUFFIX) |
| set_suffix(value); |
| else if (field_type == NAME_FULL) { |
| // TODO(dhollowa): This needs formal spec on how names are split from |
| // unstructured string to structured fields. |
| std::vector<string16> values; |
| SplitStringAlongWhitespace(value, &values); |
| if (values.size() == 1) { |
| SetInfo(AutoFillType(NAME_FIRST), values[0]); |
| } else if (values.size() == 2) { |
| SetInfo(AutoFillType(NAME_FIRST), values[0]); |
| SetInfo(AutoFillType(NAME_LAST), values[1]); |
| } else if (values.size() == 3) { |
| SetInfo(AutoFillType(NAME_FIRST), values[0]); |
| SetInfo(AutoFillType(NAME_MIDDLE), values[1]); |
| SetInfo(AutoFillType(NAME_LAST), values[2]); |
| } else if (values.size() >= 4) { |
| SetInfo(AutoFillType(NAME_FIRST), values[0]); |
| SetInfo(AutoFillType(NAME_MIDDLE), values[1]); |
| SetInfo(AutoFillType(NAME_LAST), values[2]); |
| SetInfo(AutoFillType(NAME_SUFFIX), values[3]); |
| } |
| } else if (field_type == EMAIL_ADDRESS) |
| email_ = value; |
| else if (field_type == COMPANY_NAME) |
| company_name_ = value; |
| else if (field_type == NAME_FULL) |
| SetFullName(value); |
| else |
| NOTREACHED(); |
| } |
| |
| ContactInfo::ContactInfo(const ContactInfo& contact_info) |
| : FormGroup(), |
| first_tokens_(contact_info.first_tokens_), |
| middle_tokens_(contact_info.middle_tokens_), |
| last_tokens_(contact_info.last_tokens_), |
| first_(contact_info.first_), |
| middle_(contact_info.middle_), |
| last_(contact_info.last_), |
| suffix_(contact_info.suffix_), |
| email_(contact_info.email_), |
| company_name_(contact_info.company_name_) { |
| } |
| |
| string16 ContactInfo::FullName() const { |
| if (first_.empty()) |
| return string16(); |
| |
| std::vector<string16> full_name; |
| full_name.push_back(first_); |
| |
| if (!middle_.empty()) |
| full_name.push_back(middle_); |
| |
| if (!last_.empty()) |
| full_name.push_back(last_); |
| |
| if (!suffix_.empty()) |
| full_name.push_back(suffix_); |
| |
| return JoinString(full_name, ' '); |
| } |
| |
| string16 ContactInfo::MiddleInitial() const { |
| if (middle_.empty()) |
| return string16(); |
| |
| string16 middle_name(middle()); |
| string16 initial; |
| initial.push_back(middle_name[0]); |
| return initial; |
| } |
| |
| bool ContactInfo::FindInfoMatchesHelper(const AutoFillFieldType& field_type, |
| const string16& info, |
| string16* match) const { |
| if (match == NULL) { |
| DLOG(ERROR) << "NULL match string passed in"; |
| return false; |
| } |
| |
| match->clear(); |
| if (field_type == NAME_FIRST && |
| StartsWith(first(), info, false)) { |
| *match = first(); |
| } else if (field_type == NAME_MIDDLE && |
| StartsWith(middle(), info, false)) { |
| *match = middle(); |
| } else if (field_type == NAME_LAST && |
| StartsWith(last(), info, false)) { |
| *match = last(); |
| } else if (field_type == NAME_SUFFIX && |
| StartsWith(suffix(), info, false)) { |
| *match = suffix(); |
| } else if (field_type == NAME_MIDDLE_INITIAL && IsMiddleInitial(info)) { |
| *match = MiddleInitial(); |
| } else if (field_type == NAME_FULL && |
| StartsWith(FullName(), info, false)) { |
| *match = FullName(); |
| } else if (field_type == EMAIL_ADDRESS && |
| StartsWith(email(), info, false)) { |
| *match = email(); |
| } else if (field_type == COMPANY_NAME && |
| StartsWith(company_name(), info, false)) { |
| *match = company_name(); |
| } |
| |
| return !match->empty(); |
| } |
| |
| // If each of the 'words' contained in the text are also present in the first |
| // name then we will consider the text to be of type kFirstName. This means |
| // that people with multiple first names will be able to enter any one of |
| // their first names and have it correctly recognized. |
| bool ContactInfo::IsFirstName(const string16& text) const { |
| return IsNameMatch(text, first_tokens_); |
| } |
| |
| // If each of the 'words' contained in the text are also present in the middle |
| // name then we will consider the text to be of type kMiddleName. |
| bool ContactInfo::IsMiddleName(const string16& text) const { |
| return IsNameMatch(text, middle_tokens_); |
| } |
| |
| // If each of the 'words' contained in the text are also present in the last |
| // name then we will consider the text to be of type kLastName. |
| bool ContactInfo::IsLastName(const string16& text) const { |
| return IsNameMatch(text, last_tokens_); |
| } |
| |
| bool ContactInfo::IsSuffix(const string16& text) const { |
| string16 lower_suffix = StringToLowerASCII(suffix_); |
| return (lower_suffix == text); |
| } |
| |
| bool ContactInfo::IsMiddleInitial(const string16& text) const { |
| if (text.length() != 1) |
| return false; |
| |
| string16 lower_case = StringToLowerASCII(text); |
| // If the text entered was a single character and it matches the first letter |
| // of any of the given middle names then we consider it to be a middle |
| // initial field. |
| size_t middle_tokens_size = middle_tokens_.size(); |
| for (size_t i = 0; i < middle_tokens_size; ++i) { |
| if (middle_tokens_[i][0] == lower_case[0]) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // A field will be considered to be of type NAME_FULL if: |
| // 1) it contains at least one word from the first name. |
| // 2) it contains at least one word from the last name. |
| // 3) all of the words in the field match a word in either the first, |
| // middle, or last name. |
| bool ContactInfo::IsFullName(const string16& text) const { |
| size_t first_tokens_size = first_tokens_.size(); |
| if (first_tokens_size == 0) |
| return false; |
| |
| size_t middle_tokens_size = middle_tokens_.size(); |
| |
| size_t last_tokens_size = last_tokens_.size(); |
| if (last_tokens_size == 0) |
| return false; |
| |
| NameTokens text_tokens; |
| Tokenize(text, kNameSplitChars, &text_tokens); |
| size_t text_tokens_size = text_tokens.size(); |
| if (text_tokens_size == 0 || text_tokens_size < 2) |
| return false; |
| |
| size_t name_tokens_size = |
| first_tokens_size + middle_tokens_size + last_tokens_size; |
| if (text_tokens_size > name_tokens_size) |
| return false; |
| |
| bool first_name_match = false; |
| bool last_name_match = false; |
| NameTokens::iterator iter; |
| for (iter = text_tokens.begin(); iter != text_tokens.end(); ++iter) { |
| bool match = false; |
| if (IsWordInName(*iter, first_tokens_)) { |
| match = true; |
| first_name_match = true; |
| } |
| |
| if (IsWordInName(*iter, last_tokens_)) { |
| match = true; |
| last_name_match = true; |
| } |
| |
| if (IsWordInName(*iter, middle_tokens_)) |
| match = true; |
| |
| if (!match) |
| return false; |
| } |
| |
| return (first_name_match && last_name_match); |
| } |
| |
| bool ContactInfo::IsNameMatch(const string16& text, |
| const NameTokens& name_tokens) const { |
| size_t name_tokens_size = name_tokens.size(); |
| if (name_tokens_size == 0) |
| return false; |
| |
| NameTokens text_tokens; |
| Tokenize(text, kNameSplitChars, &text_tokens); |
| size_t text_tokens_size = text_tokens.size(); |
| if (text_tokens_size == 0) |
| return false; |
| |
| if (text_tokens_size > name_tokens_size) |
| return false; |
| |
| // If each of the 'words' contained in the text are also present in the name, |
| // then we will consider the text to match the name. |
| NameTokens::iterator iter; |
| for (iter = text_tokens.begin(); iter != text_tokens.end(); ++iter) { |
| if (!IsWordInName(*iter, name_tokens)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ContactInfo::IsWordInName(const string16& word, |
| const NameTokens& name_tokens) const { |
| NameTokens::const_iterator iter; |
| for (iter = name_tokens.begin(); iter != name_tokens.end(); ++iter) { |
| // |*iter| is already lower-cased. |
| if (StringToLowerASCII(word) == *iter) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void ContactInfo::SetFirst(const string16& first) { |
| first_ = first; |
| first_tokens_.clear(); |
| Tokenize(first, kNameSplitChars, &first_tokens_); |
| NameTokens::iterator iter; |
| for (iter = first_tokens_.begin(); iter != first_tokens_.end(); ++iter) |
| *iter = StringToLowerASCII(*iter); |
| } |
| |
| void ContactInfo::SetMiddle(const string16& middle) { |
| middle_ = middle; |
| middle_tokens_.clear(); |
| Tokenize(middle, kNameSplitChars, &middle_tokens_); |
| NameTokens::iterator iter; |
| for (iter = middle_tokens_.begin(); iter != middle_tokens_.end(); ++iter) |
| *iter = StringToLowerASCII(*iter); |
| } |
| |
| void ContactInfo::SetLast(const string16& last) { |
| last_ = last; |
| last_tokens_.clear(); |
| Tokenize(last, kNameSplitChars, &last_tokens_); |
| NameTokens::iterator iter; |
| for (iter = last_tokens_.begin(); iter != last_tokens_.end(); ++iter) |
| *iter = StringToLowerASCII(*iter); |
| } |
| |
| void ContactInfo::SetFullName(const string16& full) { |
| NameTokens full_name_tokens; |
| Tokenize(full, ASCIIToUTF16(" "), &full_name_tokens); |
| // Clear the names. |
| SetFirst(string16()); |
| SetMiddle(string16()); |
| SetLast(string16()); |
| |
| // There are four possibilities: empty; first name; first and last names; |
| // first, middle (possibly multiple strings) and then the last name. |
| if (full_name_tokens.size() > 0) { |
| SetFirst(full_name_tokens[0]); |
| if (full_name_tokens.size() > 1) { |
| SetLast(full_name_tokens.back()); |
| if (full_name_tokens.size() > 2) { |
| full_name_tokens.erase(full_name_tokens.begin()); |
| full_name_tokens.pop_back(); |
| SetMiddle(JoinString(full_name_tokens, ' ')); |
| } |
| } |
| } |
| } |
| |