// 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;
}