// Copyright (c) 2011 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 <stddef.h> #include <ostream> #include <string> #include "base/basictypes.h" #include "base/logging.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "chrome/browser/autofill/autofill_type.h" #include "chrome/browser/autofill/field_types.h" static const string16 kNameSplitChars = ASCIIToUTF16("-'. "); static const AutofillFieldType kAutofillNameInfoTypes[] = { NAME_FIRST, NAME_MIDDLE, NAME_LAST }; static const size_t kAutofillNameInfoLength = arraysize(kAutofillNameInfoTypes); NameInfo::NameInfo() {} NameInfo::NameInfo(const NameInfo& info) : FormGroup() { *this = info; } NameInfo::~NameInfo() {} NameInfo& NameInfo::operator=(const NameInfo& info) { if (this == &info) return *this; first_tokens_ = info.first_tokens_; middle_tokens_ = info.middle_tokens_; last_tokens_ = info.last_tokens_; first_ = info.first_; middle_ = info.middle_; last_ = info.last_; return *this; } void NameInfo::GetPossibleFieldTypes(const string16& text, FieldTypeSet* possible_types) const { DCHECK(possible_types); 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 (IsFullName(text)) possible_types->insert(NAME_FULL); } void NameInfo::GetAvailableFieldTypes(FieldTypeSet* available_types) const { DCHECK(available_types); if (!first().empty()) available_types->insert(NAME_FIRST); if (!middle().empty()) available_types->insert(NAME_MIDDLE); if (!last().empty()) available_types->insert(NAME_LAST); if (!MiddleInitial().empty()) available_types->insert(NAME_MIDDLE_INITIAL); if (!FullName().empty()) available_types->insert(NAME_FULL); } string16 NameInfo::GetInfo(AutofillFieldType type) const { if (type == NAME_FIRST) return first(); if (type == NAME_MIDDLE) return middle(); if (type == NAME_LAST) return last(); if (type == NAME_MIDDLE_INITIAL) return MiddleInitial(); if (type == NAME_FULL) return FullName(); return string16(); } void NameInfo::SetInfo(AutofillFieldType type, const string16& value) { DCHECK_EQ(AutofillType::NAME, AutofillType(type).group()); if (type == NAME_FIRST) SetFirst(value); else if (type == NAME_MIDDLE || type == NAME_MIDDLE_INITIAL) SetMiddle(value); else if (type == NAME_LAST) SetLast(value); else if (type == NAME_FULL) SetFullName(value); else NOTREACHED(); } string16 NameInfo::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_); return JoinString(full_name, ' '); } string16 NameInfo::MiddleInitial() const { if (middle_.empty()) return string16(); string16 middle_name(middle()); string16 initial; initial.push_back(middle_name[0]); return initial; } // 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 NameInfo::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 NameInfo::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 NameInfo::IsLastName(const string16& text) const { return IsNameMatch(text, last_tokens_); } bool NameInfo::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 NameInfo::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; std::vector<string16> 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; for (std::vector<string16>::iterator 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 NameInfo::IsNameMatch(const string16& text, const std::vector<string16>& name_tokens) const { size_t name_tokens_size = name_tokens.size(); if (name_tokens_size == 0) return false; std::vector<string16> 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. for (std::vector<string16>::iterator iter = text_tokens.begin(); iter != text_tokens.end(); ++iter) { if (!IsWordInName(*iter, name_tokens)) return false; } return true; } bool NameInfo::IsWordInName(const string16& word, const std::vector<string16>& name_tokens) const { for (std::vector<string16>::const_iterator iter = name_tokens.begin(); iter != name_tokens.end(); ++iter) { // |*iter| is already lower-cased. if (StringToLowerASCII(word) == *iter) return true; } return false; } void NameInfo::SetFirst(const string16& first) { first_ = first; first_tokens_.clear(); Tokenize(first, kNameSplitChars, &first_tokens_); for (std::vector<string16>::iterator iter = first_tokens_.begin(); iter != first_tokens_.end(); ++iter) { *iter = StringToLowerASCII(*iter); } } void NameInfo::SetMiddle(const string16& middle) { middle_ = middle; middle_tokens_.clear(); Tokenize(middle, kNameSplitChars, &middle_tokens_); for (std::vector<string16>::iterator iter = middle_tokens_.begin(); iter != middle_tokens_.end(); ++iter) { *iter = StringToLowerASCII(*iter); } } void NameInfo::SetLast(const string16& last) { last_ = last; last_tokens_.clear(); Tokenize(last, kNameSplitChars, &last_tokens_); for (std::vector<string16>::iterator iter = last_tokens_.begin(); iter != last_tokens_.end(); ++iter) { *iter = StringToLowerASCII(*iter); } } void NameInfo::SetFullName(const string16& full) { std::vector<string16> 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, ' ')); } } } } EmailInfo::EmailInfo() {} EmailInfo::EmailInfo(const EmailInfo& info) : FormGroup() { *this = info; } EmailInfo::~EmailInfo() {} EmailInfo& EmailInfo::operator=(const EmailInfo& info) { if (this == &info) return *this; email_ = info.email_; return *this; } void EmailInfo::GetPossibleFieldTypes(const string16& text, FieldTypeSet* possible_types) const { DCHECK(possible_types); // TODO(isherman): Investigate case-insensitive comparison. if (email_ == text) possible_types->insert(EMAIL_ADDRESS); } void EmailInfo::GetAvailableFieldTypes(FieldTypeSet* available_types) const { DCHECK(available_types); if (!email_.empty()) available_types->insert(EMAIL_ADDRESS); } string16 EmailInfo::GetInfo(AutofillFieldType type) const { if (type == EMAIL_ADDRESS) return email_; return string16(); } void EmailInfo::SetInfo(AutofillFieldType type, const string16& value) { DCHECK_EQ(EMAIL_ADDRESS, type); email_ = value; } CompanyInfo::CompanyInfo() {} CompanyInfo::CompanyInfo(const CompanyInfo& info) : FormGroup() { *this = info; } CompanyInfo::~CompanyInfo() {} CompanyInfo& CompanyInfo::operator=(const CompanyInfo& info) { if (this == &info) return *this; company_name_ = info.company_name_; return *this; } void CompanyInfo::GetPossibleFieldTypes(const string16& text, FieldTypeSet* possible_types) const { DCHECK(possible_types); if (company_name_ == text) possible_types->insert(COMPANY_NAME); } void CompanyInfo::GetAvailableFieldTypes(FieldTypeSet* available_types) const { DCHECK(available_types); if (!company_name_.empty()) available_types->insert(COMPANY_NAME); } string16 CompanyInfo::GetInfo(AutofillFieldType type) const { if (type == COMPANY_NAME) return company_name_; return string16(); } void CompanyInfo::SetInfo(AutofillFieldType type, const string16& value) { DCHECK_EQ(COMPANY_NAME, type); company_name_ = value; }