Index: components/autofill/core/browser/contact_info.cc |
diff --git a/components/autofill/core/browser/contact_info.cc b/components/autofill/core/browser/contact_info.cc |
index c0028cf18ee680f587ad19ced31e4a908cd08e3b..7571e5f2e37d4ee04b517d029904e6f84bb5eed9 100644 |
--- a/components/autofill/core/browser/contact_info.cc |
+++ b/components/autofill/core/browser/contact_info.cc |
@@ -16,6 +16,130 @@ |
namespace autofill { |
+namespace { |
+ |
+const char* const name_prefixes[] = { |
+ "1lt", "1st", "2lt", "2nd", "3rd", "admiral", "capt", "captain", "col", |
+ "cpt", "dr", "gen", "general", "lcdr", "lt", "ltc", "ltg", "ltjg", "maj", |
+ "major", "mg", "mr", "mrs", "ms", "pastor", "prof", "rep", "reverend", |
+ "rev", "sen", "st" }; |
+ |
+const char* const name_suffixes[] = { |
+ "b.a", "ba", "d.d.s", "dds", "i", "ii", "iii", "iv", "ix", "jr", "m.a", |
+ "m.d", "ma", "md", "ms", "ph.d", "phd", "sr", "v", "vi", "vii", "viii", |
+ "x" }; |
+ |
+const char* const family_name_prefixes[] = { |
+ "d'", "de", "del", "der", "di", "la", "le", "mc", "san", "st", "ter", |
+ "van", "von" }; |
+ |
+// Returns true if |set| contains |element|, modulo a final period. |
+bool ContainsString(const char* const set[], |
+ size_t set_size, |
+ const base::string16& element) { |
+ if (!base::IsStringASCII(element)) |
+ return false; |
+ |
+ base::string16 trimmed_element; |
+ base::TrimString(element, base::ASCIIToUTF16("."), &trimmed_element); |
+ |
+ for (size_t i = 0; i < set_size; ++i) { |
+ if (LowerCaseEqualsASCII(trimmed_element, set[i])) |
+ return true; |
+ } |
+ |
+ return false; |
+} |
+ |
+// Removes common name prefixes from |name_tokens|. |
+void StripPrefixes(std::vector<base::string16>* name_tokens) { |
+ std::vector<base::string16>::iterator iter = name_tokens->begin(); |
+ while(iter != name_tokens->end()) { |
+ if (!ContainsString(name_prefixes, arraysize(name_prefixes), *iter)) |
+ break; |
+ ++iter; |
+ } |
+ |
+ std::vector<base::string16> copy_vector; |
+ copy_vector.assign(iter, name_tokens->end()); |
+ *name_tokens = copy_vector; |
+} |
+ |
+// Removes common name suffixes from |name_tokens|. |
+void StripSuffixes(std::vector<base::string16>* name_tokens) { |
+ while(!name_tokens->empty()) { |
+ if (!ContainsString(name_suffixes, arraysize(name_suffixes), |
+ name_tokens->back())) { |
+ break; |
+ } |
+ name_tokens->pop_back(); |
+ } |
+} |
+ |
+struct NameParts { |
+ base::string16 given; |
+ base::string16 middle; |
+ base::string16 family; |
+}; |
+ |
+// TODO(estade): This does Western name splitting. It should do different |
+// splitting based on the app locale. |
+NameParts SplitName(const base::string16& name) { |
+ std::vector<base::string16> name_tokens; |
+ Tokenize(name, base::ASCIIToUTF16(" ,"), &name_tokens); |
+ |
+ StripPrefixes(&name_tokens); |
+ |
+ // Don't assume "Ma" is a suffix in John Ma. |
+ if (name_tokens.size() > 2) |
+ StripSuffixes(&name_tokens); |
+ |
+ NameParts parts; |
+ |
+ if (name_tokens.empty()) { |
+ // Bad things have happened; just assume the whole thing is a given name. |
+ parts.given = name; |
+ return parts; |
+ } |
+ |
+ // Only one token, assume given name. |
+ if (name_tokens.size() == 1) { |
+ parts.given = name_tokens[0]; |
+ return parts; |
+ } |
+ |
+ // 2 or more tokens. Grab the family, which is the last word plus any |
+ // recognizable family prefixes. |
+ std::vector<base::string16> reverse_family_tokens; |
+ reverse_family_tokens.push_back(name_tokens.back()); |
+ name_tokens.pop_back(); |
+ while (name_tokens.size() >= 1 && |
+ ContainsString(family_name_prefixes, |
+ arraysize(family_name_prefixes), |
+ name_tokens.back())) { |
+ reverse_family_tokens.push_back(name_tokens.back()); |
+ name_tokens.pop_back(); |
+ } |
+ |
+ std::vector<base::string16> family_tokens(reverse_family_tokens.rbegin(), |
+ reverse_family_tokens.rend()); |
+ parts.family = JoinString(family_tokens, base::char16(' ')); |
+ |
+ // Take the last remaining token as the middle name (if there are at least 2 |
+ // tokens). |
+ if (name_tokens.size() >= 2) { |
+ parts.middle = name_tokens.back(); |
+ name_tokens.pop_back(); |
+ } |
+ |
+ // Remainder is given name. |
+ parts.given = JoinString(name_tokens, base::char16(' ')); |
+ |
+ return parts; |
+} |
+ |
+} // namespace |
+ |
NameInfo::NameInfo() {} |
NameInfo::NameInfo(const NameInfo& info) : FormGroup() { |
@@ -28,16 +152,17 @@ NameInfo& NameInfo::operator=(const NameInfo& info) { |
if (this == &info) |
return *this; |
- first_ = info.first_; |
+ given_ = info.given_; |
middle_ = info.middle_; |
- last_ = info.last_; |
+ family_ = info.family_; |
+ full_ = info.full_; |
return *this; |
} |
bool NameInfo::EqualsIgnoreCase(const NameInfo& info) { |
- return (StringToLowerASCII(first_) == StringToLowerASCII(info.first_) && |
+ return (StringToLowerASCII(given_) == StringToLowerASCII(info.given_) && |
StringToLowerASCII(middle_) == StringToLowerASCII(info.middle_) && |
- StringToLowerASCII(last_) == StringToLowerASCII(info.last_)); |
+ StringToLowerASCII(family_) == StringToLowerASCII(info.family_)); |
} |
void NameInfo::GetSupportedTypes(ServerFieldTypeSet* supported_types) const { |
@@ -52,13 +177,13 @@ base::string16 NameInfo::GetRawInfo(ServerFieldType type) const { |
DCHECK_EQ(NAME, AutofillType(type).group()); |
switch (type) { |
case NAME_FIRST: |
- return first(); |
+ return given_; |
case NAME_MIDDLE: |
- return middle(); |
+ return middle_; |
case NAME_LAST: |
- return last(); |
+ return family_; |
case NAME_MIDDLE_INITIAL: |
return MiddleInitial(); |
@@ -73,9 +198,14 @@ base::string16 NameInfo::GetRawInfo(ServerFieldType type) const { |
void NameInfo::SetRawInfo(ServerFieldType type, const base::string16& value) { |
DCHECK_EQ(NAME, AutofillType(type).group()); |
+ |
+ // Always clear out the full name if we're making a change. |
+ if (value != GetRawInfo(type)) |
+ full_.clear(); |
+ |
switch (type) { |
case NAME_FIRST: |
- first_ = value; |
+ given_ = value; |
break; |
case NAME_MIDDLE: |
@@ -84,10 +214,12 @@ void NameInfo::SetRawInfo(ServerFieldType type, const base::string16& value) { |
break; |
case NAME_LAST: |
- last_ = value; |
+ family_ = value; |
break; |
case NAME_FULL: |
+ // TODO(estade): this should just set |full_|; only SetInfo should attempt |
+ // to be smart. http://crbug.com/384640 |
SetFullName(value); |
break; |
@@ -97,15 +229,18 @@ void NameInfo::SetRawInfo(ServerFieldType type, const base::string16& value) { |
} |
base::string16 NameInfo::FullName() const { |
+ if (!full_.empty()) |
+ return full_; |
+ |
std::vector<base::string16> full_name; |
- if (!first_.empty()) |
- full_name.push_back(first_); |
+ if (!given_.empty()) |
+ full_name.push_back(given_); |
if (!middle_.empty()) |
full_name.push_back(middle_); |
- if (!last_.empty()) |
- full_name.push_back(last_); |
+ if (!family_.empty()) |
+ full_name.push_back(family_); |
return JoinString(full_name, ' '); |
} |
@@ -114,34 +249,32 @@ base::string16 NameInfo::MiddleInitial() const { |
if (middle_.empty()) |
return base::string16(); |
- base::string16 middle_name(middle()); |
+ base::string16 middle_name(middle_); |
base::string16 initial; |
initial.push_back(middle_name[0]); |
return initial; |
} |
void NameInfo::SetFullName(const base::string16& full) { |
- // Clear the names. |
- first_ = base::string16(); |
- middle_ = base::string16(); |
- last_ = base::string16(); |
- |
- std::vector<base::string16> full_name_tokens; |
- Tokenize(full, base::ASCIIToUTF16(" "), &full_name_tokens); |
- |
- // 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) { |
- first_ = full_name_tokens[0]; |
- if (full_name_tokens.size() > 1) { |
- last_ = full_name_tokens.back(); |
- if (full_name_tokens.size() > 2) { |
- full_name_tokens.erase(full_name_tokens.begin()); |
- full_name_tokens.pop_back(); |
- middle_ = JoinString(full_name_tokens, ' '); |
- } |
- } |
- } |
+ // Hack: don't do anything if this wouldn't change the full, concatenated |
+ // name. Otherwise when unpickling data from the database, "First|Middle|" |
+ // will get parsed as "First||Middle". |
+ // TODO(estade): we should be able to remove this when fixing the TODO in |
+ // SetRawInfo. http://crbug.com/384640 |
+ if (FullName() == full) |
+ return; |
+ |
+ full_ = full; |
+ |
+ // If |full| is empty, leave the other name parts alone. This might occur |
+ // due to a migrated database with an empty |full_name| value. |
+ if (full.empty()) |
+ return; |
+ |
+ NameParts parts = SplitName(full); |
+ given_ = parts.given; |
+ middle_ = parts.middle; |
+ family_ = parts.family; |
} |
EmailInfo::EmailInfo() {} |