| Index: third_party/libaddressinput/chromium/suggestions.cc
|
| diff --git a/third_party/libaddressinput/chromium/suggestions.cc b/third_party/libaddressinput/chromium/suggestions.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..7005e701d9bd06b590623c031f9d1fd77ff09db8
|
| --- /dev/null
|
| +++ b/third_party/libaddressinput/chromium/suggestions.cc
|
| @@ -0,0 +1,435 @@
|
| +// Copyright 2014 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 "third_party/libaddressinput/chromium/suggestions.h"
|
| +
|
| +// Use "base/memory/scoped_ptr.h" instead.
|
| +#define I18N_ADDRESSINPUT_UTIL_SCOPED_PTR_H_
|
| +
|
| +#include "base/basictypes.h"
|
| +#include "base/logging.h"
|
| +#include "base/memory/scoped_ptr.h"
|
| +#include "third_party/icu/source/common/unicode/errorcode.h"
|
| +#include "third_party/icu/source/common/unicode/locid.h"
|
| +#include "third_party/icu/source/common/unicode/unistr.h"
|
| +#include "third_party/icu/source/common/unicode/utypes.h"
|
| +#include "third_party/icu/source/i18n/unicode/coll.h"
|
| +#include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_data.h"
|
| +#include "third_party/libaddressinput/src/cpp/include/libaddressinput/preload_supplier.h"
|
| +#include "third_party/libaddressinput/src/cpp/include/libaddressinput/region_data.h"
|
| +
|
| +namespace autofill {
|
| +
|
| +using ::i18n::addressinput::ADMIN_AREA;
|
| +using ::i18n::addressinput::DEPENDENT_LOCALITY;
|
| +using ::i18n::addressinput::RegionData;
|
| +
|
| +typedef std::set<const RegionData*> RegionContainer;
|
| +
|
| +namespace {
|
| +
|
| +// Collects regions based on whether they have a parent in the given list.
|
| +class ParentedRegionCollector {
|
| + public:
|
| + // Retains a reference to both of the parameters. Does not make a copy of
|
| + // |parent_regions|. Does not take ownership of |regions_with_parents|. The
|
| + // |regions_with_parents| parameter should not be NULL.
|
| + ParentedRegionCollector(const RegionContainer& parent_regions,
|
| + RegionContainer* regions_with_parents)
|
| + : parent_regions_(parent_regions),
|
| + regions_with_parents_(regions_with_parents) {
|
| + DCHECK(regions_with_parents_);
|
| + }
|
| +
|
| + ~ParentedRegionCollector() {}
|
| +
|
| + // Adds |region_to_test| to the |regions_with_parents_| collection, if the
|
| + // given region has a parent in |parent_regions_|. The |region_to_test|
|
| + // parameter should not be NULL.
|
| + void operator()(const RegionData* region_to_test) {
|
| + DCHECK(region_to_test);
|
| + if (parent_regions_.find(®ion_to_test->parent()) !=
|
| + parent_regions_.end()) {
|
| + regions_with_parents_->insert(region_to_test);
|
| + }
|
| + }
|
| +
|
| + private:
|
| + const RegionContainer parent_regions_;
|
| + RegionContainer* regions_with_parents_;
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +class Suggestions::CanonicalizerImpl {
|
| + public:
|
| + CanonicalizerImpl() {
|
| + UErrorCode error_code = U_ZERO_ERROR;
|
| + collator_.reset(
|
| + icu::Collator::createInstance(icu::Locale::getRoot(), error_code));
|
| + DCHECK(U_SUCCESS(error_code));
|
| + collator_->setStrength(icu::Collator::PRIMARY);
|
| + }
|
| +
|
| + ~CanonicalizerImpl() {}
|
| +
|
| + // Returns a canonical version of the string that can be used for comparing
|
| + // strings regardless of diacritics and capitalization.
|
| + // CanonicalizeString("Texas") == CanonicalizeString("T\u00E9xas");
|
| + // CanonicalizeString("Texas") == CanonicalizeString("teXas");
|
| + // CanonicalizeString("Texas") != CanonicalizeString("California");
|
| + //
|
| + // The output is not human-readable.
|
| + // CanonicalizeString("Texas") != "Texas";
|
| + std::string CanonicalizeString(const std::string& original) {
|
| + icu::UnicodeString icu_str(
|
| + original.c_str(), static_cast<int32_t>(original.length()));
|
| + int32_t buffer_size = collator_->getSortKey(icu_str, NULL, 0);
|
| + scoped_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
|
| + DCHECK(buffer.get());
|
| + int32_t filled_size =
|
| + collator_->getSortKey(icu_str, buffer.get(), buffer_size);
|
| + DCHECK_EQ(buffer_size, filled_size);
|
| + return std::string(reinterpret_cast<const char*>(buffer.get()));
|
| + }
|
| +
|
| + private:
|
| + scoped_ptr<icu::Collator> collator_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(CanonicalizerImpl);
|
| +};
|
| +
|
| +Suggestions::Suggestions(const PreloadSupplier* supplier)
|
| + : supplier_(supplier),
|
| + canonicalizer_(new CanonicalizerImpl) {}
|
| +
|
| +Suggestions::~Suggestions() {
|
| + // Delete the maps and trie objects owned by |tries_| field.
|
| + for (RegionCodeMap::const_iterator region_it = tries_.begin();
|
| + region_it != tries_.end(); ++region_it) {
|
| + LanguageTagMap* lang_map = region_it->second;
|
| + DCHECK(lang_map);
|
| +
|
| + for (LanguageTagMap::const_iterator lang_it = lang_map->begin();
|
| + lang_it != lang_map->end(); ++lang_it) {
|
| + AddressFieldMap* field_map = lang_it->second;
|
| + DCHECK(field_map);
|
| +
|
| + for (AddressFieldMap::const_iterator field_it = field_map->begin();
|
| + field_it != field_map->end(); ++field_it) {
|
| + RegionIdMap* id_map = field_it->second;
|
| + DCHECK(id_map);
|
| +
|
| + for (RegionIdMap::const_iterator id_it = id_map->begin();
|
| + id_it != id_map->end(); ++id_it) {
|
| + // The tries do not own the region objects.
|
| + Trie<const RegionData*>* trie = id_it->second;
|
| + delete trie;
|
| + }
|
| + delete id_map;
|
| + }
|
| + delete field_map;
|
| + }
|
| + delete lang_map;
|
| + }
|
| +}
|
| +
|
| +void Suggestions::GetSuggestions(const AddressData& user_input,
|
| + AddressField focused_field,
|
| + size_t suggestions_limit,
|
| + std::vector<AddressData>* suggestions) {
|
| + /*
|
| + !!!TODO: IMPL!!!
|
| + DCHECK(suggestions);
|
| +
|
| + std::map<std::string, Ruleset*>::const_iterator ruleset_it =
|
| + rules_.find(user_input.region_code);
|
| +
|
| + if (ruleset_it == rules_.end()) {
|
| + return
|
| + loading_rules_.find(user_input.region_code) != loading_rules_.end()
|
| + ? RULES_NOT_READY
|
| + : RULES_UNAVAILABLE;
|
| + }
|
| +
|
| + if (suggestions == NULL) {
|
| + return SUCCESS;
|
| + }
|
| + suggestions->clear();
|
| +
|
| + assert(ruleset_it->second != NULL);
|
| +
|
| + // Do not suggest anything if the user is typing in the field for which
|
| + // there's no validation data.
|
| + if (focused_field != POSTAL_CODE &&
|
| + (focused_field < ADMIN_AREA || focused_field > DEPENDENT_LOCALITY)) {
|
| + return SUCCESS;
|
| + }
|
| +
|
| + // Do not suggest anything if the user input is empty.
|
| + if (user_input.GetFieldValue(focused_field).empty()) {
|
| + return SUCCESS;
|
| + }
|
| +
|
| + const Ruleset& country_ruleset = *ruleset_it->second;
|
| + const Rule& country_rule =
|
| + country_ruleset.GetLanguageCodeRule(user_input.language_code);
|
| +
|
| + // Do not suggest anything if the user is typing the postal code that is not
|
| + // valid for the country.
|
| + if (!user_input.postal_code.empty() &&
|
| + focused_field == POSTAL_CODE &&
|
| + !country_rule.GetPostalCodeFormat().empty() &&
|
| + !ValueMatchesPrefixRegex(
|
| + user_input.postal_code, country_rule.GetPostalCodeFormat())) {
|
| + return SUCCESS;
|
| + }
|
| +
|
| + // Initialize the prefix search index lazily.
|
| + if (!ruleset_it->second->prefix_search_index_ready()) {
|
| + ruleset_it->second->BuildPrefixSearchIndex();
|
| + }
|
| +
|
| + if (focused_field != POSTAL_CODE &&
|
| + focused_field > country_ruleset.deepest_ruleset_level()) {
|
| + return SUCCESS;
|
| + }
|
| +
|
| + // Determine the most specific address field that can be suggested.
|
| + AddressField suggestion_field = focused_field != POSTAL_CODE
|
| + ? focused_field : DEPENDENT_LOCALITY;
|
| + if (suggestion_field > country_ruleset.deepest_ruleset_level()) {
|
| + suggestion_field = country_ruleset.deepest_ruleset_level();
|
| + }
|
| + if (focused_field != POSTAL_CODE) {
|
| + while (user_input.GetFieldValue(suggestion_field).empty() &&
|
| + suggestion_field > ADMIN_AREA) {
|
| + suggestion_field = static_cast<AddressField>(suggestion_field - 1);
|
| + }
|
| + }
|
| +
|
| + // Find all rulesets that match user input.
|
| + AddressFieldRulesets rulesets;
|
| + for (int i = ADMIN_AREA; i <= suggestion_field; ++i) {
|
| + for (int j = Rule::KEY; j <= Rule::LATIN_NAME; ++j) {
|
| + AddressField address_field = static_cast<AddressField>(i);
|
| + Rule::IdentityField rule_field = static_cast<Rule::IdentityField>(j);
|
| +
|
| + // Find all rulesets at |address_field| level whose |rule_field| starts
|
| + // with user input value.
|
| + country_ruleset.FindRulesetsByPrefix(
|
| + user_input.language_code, address_field, rule_field,
|
| + user_input.GetFieldValue(address_field),
|
| + &rulesets[address_field][rule_field]);
|
| +
|
| + // Filter out the rulesets whose parents do not match the user input.
|
| + if (address_field > ADMIN_AREA) {
|
| + AddressField parent_field =
|
| + static_cast<AddressField>(address_field - 1);
|
| + Rulesets rulesets_with_parents;
|
| + std::for_each(
|
| + rulesets[address_field][rule_field].begin(),
|
| + rulesets[address_field][rule_field].end(),
|
| + ParentedRulesetCollector(rulesets[parent_field][rule_field],
|
| + &rulesets_with_parents));
|
| + rulesets[address_field][rule_field].swap(rulesets_with_parents);
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Determine the fields in the rules that match the user input. This
|
| + // operation converts a map of Rule::IdentityField value -> Ruleset into a
|
| + // map of Ruleset -> Rule::IdentityField bitset.
|
| + std::map<const Ruleset*, MatchingRuleFields> suggestion_rulesets;
|
| + for (IdentityFieldRulesets::const_iterator rule_field_it =
|
| + rulesets[suggestion_field].begin();
|
| + rule_field_it != rulesets[suggestion_field].end();
|
| + ++rule_field_it) {
|
| + const Rule::IdentityField rule_identity_field = rule_field_it->first;
|
| + for (Rulesets::const_iterator ruleset_it = rule_field_it->second.begin();
|
| + ruleset_it != rule_field_it->second.end();
|
| + ++ruleset_it) {
|
| + suggestion_rulesets[*ruleset_it].set(rule_identity_field);
|
| + }
|
| + }
|
| +
|
| + // Generate suggestions based on the rulesets. Use a Rule::IdentityField
|
| + // from the bitset to generate address field values.
|
| + for (std::map<const Ruleset*, MatchingRuleFields>::const_iterator
|
| + suggestion_it = suggestion_rulesets.begin();
|
| + suggestion_it != suggestion_rulesets.end();
|
| + ++suggestion_it) {
|
| + const Ruleset& ruleset = *suggestion_it->first;
|
| + const Rule& rule = ruleset.GetLanguageCodeRule(user_input.language_code);
|
| + const MatchingRuleFields& matching_rule_fields = suggestion_it->second;
|
| +
|
| + // Do not suggest this region if the postal code in user input does not
|
| + // match it.
|
| + if (!user_input.postal_code.empty() &&
|
| + !rule.GetPostalCodeFormat().empty() &&
|
| + !ValueMatchesPrefixRegex(
|
| + user_input.postal_code, rule.GetPostalCodeFormat())) {
|
| + continue;
|
| + }
|
| +
|
| + // Do not add more suggestions than |suggestions_limit|.
|
| + if (suggestions->size() >= suggestions_limit) {
|
| + suggestions->clear();
|
| + return SUCCESS;
|
| + }
|
| +
|
| + // If the user's language is not one of the supported languages of a
|
| + // country that has latinized names for its regions, then prefer to
|
| + // suggest the latinized region names. If the user types in local script
|
| + // instead, then the local script names will be suggested.
|
| + Rule::IdentityField rule_field = Rule::KEY;
|
| + if (!country_rule.GetLanguage().empty() &&
|
| + country_rule.GetLanguage() != user_input.language_code &&
|
| + !rule.GetLatinName().empty() &&
|
| + matching_rule_fields.test(Rule::LATIN_NAME)) {
|
| + rule_field = Rule::LATIN_NAME;
|
| + } else if (matching_rule_fields.test(Rule::KEY)) {
|
| + rule_field = Rule::KEY;
|
| + } else if (matching_rule_fields.test(Rule::NAME)) {
|
| + rule_field = Rule::NAME;
|
| + } else if (matching_rule_fields.test(Rule::LATIN_NAME)) {
|
| + rule_field = Rule::LATIN_NAME;
|
| + } else {
|
| + assert(false);
|
| + }
|
| +
|
| + AddressData suggestion;
|
| + suggestion.region_code = user_input.region_code;
|
| + suggestion.postal_code = user_input.postal_code;
|
| +
|
| + // Traverse the tree of rulesets from the most specific |ruleset| to the
|
| + // country-wide "root" of the tree. Use the region names found at each of
|
| + // the levels of the ruleset tree to build the |suggestion|.
|
| + for (const Ruleset* suggestion_ruleset = &ruleset;
|
| + suggestion_ruleset->parent() != NULL;
|
| + suggestion_ruleset = suggestion_ruleset->parent()) {
|
| + const Rule& suggestion_rule =
|
| + suggestion_ruleset->GetLanguageCodeRule(user_input.language_code);
|
| + suggestion.SetFieldValue(suggestion_ruleset->field(),
|
| + suggestion_rule.GetIdentityField(rule_field));
|
| + }
|
| +
|
| + suggestions->push_back(suggestion);
|
| + }
|
| +
|
| + return SUCCESS;
|
| +}
|
| +
|
| +void Ruleset::AddSubRegionRulesetsToTrie(const Ruleset& parent_ruleset) {
|
| + assert(field_ == COUNTRY);
|
| + assert(canonicalizer_ != NULL);
|
| +
|
| + for (std::map<std::string, Ruleset*>::const_iterator sub_region_it =
|
| + parent_ruleset.sub_regions_.begin();
|
| + sub_region_it != parent_ruleset.sub_regions_.end();
|
| + ++sub_region_it) {
|
| + const Ruleset* ruleset = sub_region_it->second;
|
| + assert(ruleset != NULL);
|
| +
|
| + if (deepest_ruleset_level_ < ruleset->field()) {
|
| + deepest_ruleset_level_ = ruleset->field();
|
| + }
|
| +
|
| + for (LanguageCodeTries::const_iterator lang_it = tries_.begin();
|
| + lang_it != tries_.end(); ++lang_it) {
|
| + const std::string& language_code = lang_it->first;
|
| + const Rule& rule = ruleset->GetLanguageCodeRule(language_code);
|
| +
|
| + AddressFieldTries* address_field_tries = lang_it->second;
|
| + assert(address_field_tries != NULL);
|
| +
|
| + AddressFieldTries::const_iterator address_field_it =
|
| + address_field_tries->find(ruleset->field());
|
| + assert(address_field_it != address_field_tries->end());
|
| +
|
| + IdentityFieldTries* identity_field_tries = address_field_it->second;
|
| + assert(identity_field_tries != NULL);
|
| +
|
| + IdentityFieldTries::const_iterator identity_field_it =
|
| + identity_field_tries->find(Rule::KEY);
|
| + assert(identity_field_it != identity_field_tries->end());
|
| +
|
| + Trie<const Ruleset*>* key_trie = identity_field_it->second;
|
| + assert(key_trie != NULL);
|
| +
|
| + identity_field_it = identity_field_tries->find(Rule::NAME);
|
| + assert(identity_field_it != identity_field_tries->end());
|
| +
|
| + Trie<const Ruleset*>* name_trie = identity_field_it->second;
|
| + assert(name_trie != NULL);
|
| +
|
| + identity_field_it = identity_field_tries->find(Rule::LATIN_NAME);
|
| + assert(identity_field_it != identity_field_tries->end());
|
| +
|
| + Trie<const Ruleset*>* latin_name_trie = identity_field_it->second;
|
| + assert(latin_name_trie != NULL);
|
| +
|
| + if (!rule.GetKey().empty()) {
|
| + key_trie->AddDataForKey(
|
| + canonicalizer_->CanonicalizeString(rule.GetKey()), ruleset);
|
| + }
|
| +
|
| + if (!rule.GetName().empty()) {
|
| + name_trie->AddDataForKey(
|
| + canonicalizer_->CanonicalizeString(rule.GetName()), ruleset);
|
| + }
|
| +
|
| + if (!rule.GetLatinName().empty()) {
|
| + latin_name_trie->AddDataForKey(
|
| + canonicalizer_->CanonicalizeString(rule.GetLatinName()), ruleset);
|
| + }
|
| + }
|
| +
|
| + AddSubRegionRulesetsToTrie(*ruleset);
|
| + }
|
| + */
|
| +}
|
| +
|
| +void Suggestions::FindRegionsByPrefix(const std::string& region_code,
|
| + const std::string& language_tag,
|
| + AddressField address_field,
|
| + RegionIdentityField region_identity_field,
|
| + const std::string& canonicalized_prefix,
|
| + RegionContainer* result) const {
|
| + DCHECK_GE(address_field, ADMIN_AREA);
|
| + DCHECK_LE(address_field, DEPENDENT_LOCALITY);
|
| + DCHECK(result);
|
| +
|
| + RegionCodeMap::const_iterator region_it = tries_.find(region_code);
|
| + if (region_it == tries_.end())
|
| + return;
|
| +
|
| + const LanguageTagMap* lang_map = region_it->second;
|
| + DCHECK(lang_map);
|
| + LanguageTagMap::const_iterator lang_it = lang_map->find(language_tag);
|
| + if (lang_it == lang_map->end()) {
|
| + if (!lang_map->empty())
|
| + lang_it = lang_map->begin();
|
| + else
|
| + return;
|
| + }
|
| +
|
| + const AddressFieldMap* field_map = lang_it->second;
|
| + DCHECK(field_map);
|
| + AddressFieldMap::const_iterator field_it = field_map->find(address_field);
|
| + if (field_it == field_map->end())
|
| + return;
|
| +
|
| + const RegionIdMap* id_map = field_it->second;
|
| + DCHECK(id_map);
|
| + RegionIdMap::const_iterator id_it = id_map->find(region_identity_field);
|
| + if (id_it == id_map->end())
|
| + return;
|
| +
|
| + const Trie<const RegionData*>* trie = id_it->second;
|
| + DCHECK(trie);
|
| + trie->FindDataForKeyPrefix(canonicalized_prefix, result);
|
| +}
|
| +
|
| +} // namespace autofill
|
|
|