blob: a42d4a1693511bedd90d9f05458b95c114315609 [file] [log] [blame]
// Copyright (C) 2014 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <libaddressinput/region_data_builder.h>
#include <libaddressinput/address_data.h>
#include <libaddressinput/preload_supplier.h>
#include <libaddressinput/region_data.h>
#include <cassert>
#include <cstddef>
#include <string>
#include <utility>
#include <vector>
#include "language.h"
#include "lookup_key.h"
#include "region_data_constants.h"
#include "rule.h"
#include "util/size.h"
namespace i18n {
namespace addressinput {
namespace {
// The maximum depth of lookup keys.
static const size_t kLookupKeysMaxDepth = size(LookupKey::kHierarchy) - 1;
// Does not take ownership of |parent_region|, which is not allowed to be
// nullptr.
void BuildRegionTreeRecursively(
const std::map<std::string, const Rule*>& rules,
std::map<std::string, const Rule*>::const_iterator hint,
const LookupKey& parent_key,
RegionData* parent_region,
const std::vector<std::string>& keys,
bool prefer_latin_name,
size_t region_max_depth) {
assert(parent_region != nullptr);
LookupKey lookup_key;
for (std::vector<std::string>::const_iterator key_it = keys.begin();
key_it != keys.end(); ++key_it) {
lookup_key.FromLookupKey(parent_key, *key_it);
const std::string& lookup_key_string =
lookup_key.ToKeyString(kLookupKeysMaxDepth);
++hint;
if (hint == rules.end() || hint->first != lookup_key_string) {
hint = rules.find(lookup_key_string);
if (hint == rules.end()) {
return;
}
}
const Rule* rule = hint->second;
assert(rule != nullptr);
const std::string& local_name = rule->GetName().empty()
? *key_it : rule->GetName();
const std::string& name =
prefer_latin_name && !rule->GetLatinName().empty()
? rule->GetLatinName() : local_name;
RegionData* region = parent_region->AddSubRegion(*key_it, name);
if (!rule->GetSubKeys().empty() &&
region_max_depth > parent_key.GetDepth()) {
BuildRegionTreeRecursively(rules,
hint,
lookup_key,
region,
rule->GetSubKeys(),
prefer_latin_name,
region_max_depth);
}
}
}
// The caller owns the result.
RegionData* BuildRegion(const std::map<std::string, const Rule*>& rules,
const std::string& region_code,
const Language& language) {
AddressData address;
address.region_code = region_code;
LookupKey lookup_key;
lookup_key.FromAddress(address);
std::map<std::string, const Rule*>::const_iterator hint =
rules.find(lookup_key.ToKeyString(kLookupKeysMaxDepth));
assert(hint != rules.end());
const Rule* rule = hint->second;
assert(rule != nullptr);
RegionData* region = new RegionData(region_code);
// If there're sub-keys for field X, but field X is not used in this region
// code, then these sub-keys are skipped over. For example, CH has sub-keys
// for field ADMIN_AREA, but CH does not use ADMIN_AREA field.
size_t region_max_depth =
RegionDataConstants::GetMaxLookupKeyDepth(region_code);
if (region_max_depth > 0) {
BuildRegionTreeRecursively(rules,
hint,
lookup_key,
region,
rule->GetSubKeys(),
language.has_latin_script,
region_max_depth);
}
return region;
}
} // namespace
RegionDataBuilder::RegionDataBuilder(PreloadSupplier* supplier)
: supplier_(supplier),
cache_() {
assert(supplier_ != nullptr);
}
RegionDataBuilder::~RegionDataBuilder() {
for (RegionCodeDataMap::const_iterator region_it = cache_.begin();
region_it != cache_.end(); ++region_it) {
for (LanguageRegionMap::const_iterator
language_it = region_it->second->begin();
language_it != region_it->second->end(); ++language_it) {
delete language_it->second;
}
delete region_it->second;
}
}
const RegionData& RegionDataBuilder::Build(
const std::string& region_code,
const std::string& ui_language_tag,
std::string* best_region_tree_language_tag) {
assert(supplier_->IsLoaded(region_code));
assert(best_region_tree_language_tag != nullptr);
// Look up the region tree in cache first before building it.
RegionCodeDataMap::const_iterator region_it = cache_.find(region_code);
if (region_it == cache_.end()) {
region_it =
cache_.insert(std::make_pair(region_code, new LanguageRegionMap)).first;
}
// No need to copy from default rule first, because only languages and Latin
// format are going to be used, which do not exist in the default rule.
Rule rule;
rule.ParseSerializedRule(RegionDataConstants::GetRegionData(region_code));
static const Language kUndefinedLanguage("und");
const Language& best_language =
rule.GetLanguages().empty()
? kUndefinedLanguage
: ChooseBestAddressLanguage(rule, Language(ui_language_tag));
*best_region_tree_language_tag = best_language.tag;
LanguageRegionMap::const_iterator language_it =
region_it->second->find(best_language.tag);
if (language_it == region_it->second->end()) {
const std::map<std::string, const Rule*>& rules =
supplier_->GetRulesForRegion(region_code);
language_it =
region_it->second->insert(std::make_pair(best_language.tag,
BuildRegion(rules,
region_code,
best_language)))
.first;
}
return *language_it->second;
}
} // namespace addressinput
} // namespace i18n