1// Copyright (C) 2012 The Libphonenumber Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Author: Patrick Mezard 16 17#include "phonenumbers/geocoding/phonenumber_offline_geocoder.h" 18 19#include <algorithm> 20#include <map> 21#include <string> 22 23#include <unicode/unistr.h> // NOLINT(build/include_order) 24 25#include "phonenumbers/geocoding/area_code_map.h" 26#include "phonenumbers/geocoding/geocoding_data.h" 27#include "phonenumbers/geocoding/mapping_file_provider.h" 28#include "phonenumbers/phonenumberutil.h" 29#include "phonenumbers/stl_util.h" 30 31namespace i18n { 32namespace phonenumbers { 33 34using icu::UnicodeString; 35using std::map; 36using std::string; 37 38namespace { 39 40// Returns true if s1 comes strictly before s2 in lexicographic order. 41bool IsLowerThan(const char* s1, const char* s2) { 42 return strcmp(s1, s2) < 0; 43} 44 45} // namespace 46 47PhoneNumberOfflineGeocoder::PhoneNumberOfflineGeocoder() { 48 Init(get_country_calling_codes(), get_country_calling_codes_size(), 49 get_country_languages, get_prefix_language_code_pairs(), 50 get_prefix_language_code_pairs_size(), get_prefix_descriptions); 51} 52 53PhoneNumberOfflineGeocoder::PhoneNumberOfflineGeocoder( 54 const int* country_calling_codes, int country_calling_codes_size, 55 country_languages_getter get_country_languages, 56 const char** prefix_language_code_pairs, 57 int prefix_language_code_pairs_size, 58 prefix_descriptions_getter get_prefix_descriptions) { 59 Init(country_calling_codes, country_calling_codes_size, 60 get_country_languages, prefix_language_code_pairs, 61 prefix_language_code_pairs_size, get_prefix_descriptions); 62} 63 64void PhoneNumberOfflineGeocoder::Init( 65 const int* country_calling_codes, int country_calling_codes_size, 66 country_languages_getter get_country_languages, 67 const char** prefix_language_code_pairs, 68 int prefix_language_code_pairs_size, 69 prefix_descriptions_getter get_prefix_descriptions) { 70 phone_util_ = PhoneNumberUtil::GetInstance(); 71 provider_.reset(new MappingFileProvider(country_calling_codes, 72 country_calling_codes_size, 73 get_country_languages)); 74 prefix_language_code_pairs_ = prefix_language_code_pairs; 75 prefix_language_code_pairs_size_ = prefix_language_code_pairs_size; 76 get_prefix_descriptions_ = get_prefix_descriptions; 77} 78 79PhoneNumberOfflineGeocoder::~PhoneNumberOfflineGeocoder() { 80 STLDeleteContainerPairSecondPointers( 81 available_maps_.begin(), available_maps_.end()); 82} 83 84const AreaCodeMap* PhoneNumberOfflineGeocoder::GetPhonePrefixDescriptions( 85 int prefix, const string& language, const string& script, 86 const string& region) const { 87 string filename; 88 provider_->GetFileName(prefix, language, script, region, &filename); 89 if (filename.empty()) { 90 return NULL; 91 } 92 AreaCodeMaps::const_iterator it = available_maps_.find(filename); 93 if (it == available_maps_.end()) { 94 it = LoadAreaCodeMapFromFile(filename); 95 if (it == available_maps_.end()) { 96 return NULL; 97 } 98 } 99 return it->second; 100} 101 102PhoneNumberOfflineGeocoder::AreaCodeMaps::const_iterator 103PhoneNumberOfflineGeocoder::LoadAreaCodeMapFromFile( 104 const string& filename) const { 105 const char** const prefix_language_code_pairs_end = 106 prefix_language_code_pairs_ + prefix_language_code_pairs_size_; 107 const char** const prefix_language_code_pair = 108 std::lower_bound(prefix_language_code_pairs_, 109 prefix_language_code_pairs_end, 110 filename.c_str(), IsLowerThan); 111 if (prefix_language_code_pair != prefix_language_code_pairs_end && 112 filename.compare(*prefix_language_code_pair) == 0) { 113 AreaCodeMap* const m = new AreaCodeMap(); 114 m->ReadAreaCodeMap(get_prefix_descriptions_( 115 prefix_language_code_pair - prefix_language_code_pairs_)); 116 return available_maps_.insert(AreaCodeMaps::value_type(filename, m)).first; 117 } 118 return available_maps_.end(); 119} 120 121string PhoneNumberOfflineGeocoder::GetCountryNameForNumber( 122 const PhoneNumber& number, const Locale& language) const { 123 string region_code; 124 phone_util_->GetRegionCodeForNumber(number, ®ion_code); 125 return GetRegionDisplayName(®ion_code, language); 126} 127 128string PhoneNumberOfflineGeocoder::GetRegionDisplayName( 129 const string* region_code, const Locale& language) const { 130 if (region_code == NULL || region_code->compare("ZZ") == 0 || 131 region_code->compare( 132 PhoneNumberUtil::kRegionCodeForNonGeoEntity) == 0) { 133 return ""; 134 } 135 UnicodeString udisplay_country; 136 icu::Locale("", region_code->c_str()).getDisplayCountry( 137 language, udisplay_country); 138 string display_country; 139 udisplay_country.toUTF8String(display_country); 140 return display_country; 141} 142 143string PhoneNumberOfflineGeocoder::GetDescriptionForValidNumber( 144 const PhoneNumber& number, const Locale& language) const { 145 const char* const description = GetAreaDescription( 146 number, language.getLanguage(), "", language.getCountry()); 147 return *description != '\0' 148 ? description 149 : GetCountryNameForNumber(number, language); 150} 151 152string PhoneNumberOfflineGeocoder::GetDescriptionForValidNumber( 153 const PhoneNumber& number, const Locale& language, 154 const string& user_region) const { 155 // If the user region matches the number's region, then we just show the 156 // lower-level description, if one exists - if no description exists, we will 157 // show the region(country) name for the number. 158 string region_code; 159 phone_util_->GetRegionCodeForNumber(number, ®ion_code); 160 if (user_region.compare(region_code) == 0) { 161 return GetDescriptionForValidNumber(number, language); 162 } 163 // Otherwise, we just show the region(country) name for now. 164 return GetRegionDisplayName(®ion_code, language); 165} 166 167string PhoneNumberOfflineGeocoder::GetDescriptionForNumber( 168 const PhoneNumber& number, const Locale& locale) const { 169 if (!phone_util_->IsValidNumber(number)) { 170 return ""; 171 } 172 return GetDescriptionForValidNumber(number, locale); 173} 174 175string PhoneNumberOfflineGeocoder::GetDescriptionForNumber( 176 const PhoneNumber& number, const Locale& language, 177 const string& user_region) const { 178 if (!phone_util_->IsValidNumber(number)) { 179 return ""; 180 } 181 return GetDescriptionForValidNumber(number, language, user_region); 182} 183 184const char* PhoneNumberOfflineGeocoder::GetAreaDescription( 185 const PhoneNumber& number, const string& lang, const string& script, 186 const string& region) const { 187 const int country_calling_code = number.country_code(); 188 // NANPA area is not split in C++ code. 189 const int phone_prefix = country_calling_code; 190 const AreaCodeMap* const descriptions = GetPhonePrefixDescriptions( 191 phone_prefix, lang, script, region); 192 const char* description = descriptions ? descriptions->Lookup(number) : NULL; 193 // When a location is not available in the requested language, fall back to 194 // English. 195 if ((!description || *description == '\0') && MayFallBackToEnglish(lang)) { 196 const AreaCodeMap* default_descriptions = GetPhonePrefixDescriptions( 197 phone_prefix, "en", "", ""); 198 if (!default_descriptions) { 199 return ""; 200 } 201 description = default_descriptions->Lookup(number); 202 } 203 return description ? description : ""; 204} 205 206// Don't fall back to English if the requested language is among the following: 207// - Chinese 208// - Japanese 209// - Korean 210bool PhoneNumberOfflineGeocoder::MayFallBackToEnglish( 211 const string& lang) const { 212 return lang.compare("zh") && lang.compare("ja") && lang.compare("ko"); 213} 214 215} // namespace phonenumbers 216} // namespace i18n 217