Commit b1d6cbf2 authored by rouslan@chromium.org's avatar rouslan@chromium.org

[rac] Validate an address.

BUG=327046

Review URL: https://codereview.chromium.org/116363003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@244046 0039d316-1c4b-4281-b951-d872f2087c98
parent e9149ab5
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
#ifndef I18N_ADDRESSINPUT_ADDRESS_DATA_H_ #ifndef I18N_ADDRESSINPUT_ADDRESS_DATA_H_
#define I18N_ADDRESSINPUT_ADDRESS_DATA_H_ #define I18N_ADDRESSINPUT_ADDRESS_DATA_H_
#include <libaddressinput/address_field.h>
#include <string> #include <string>
#include <vector> #include <vector>
...@@ -31,14 +33,16 @@ namespace addressinput { ...@@ -31,14 +33,16 @@ namespace addressinput {
// address.address_lines.push_back("1098 Alta Ave"); // address.address_lines.push_back("1098 Alta Ave");
// address.administrative_area = "CA"; // address.administrative_area = "CA";
// address.locality = "Mountain View"; // address.locality = "Mountain View";
// address.dependent_locality = "";
// address.postal_code = "94043"; // address.postal_code = "94043";
// address.sorting_code = "";
// address.organization = "Google"; // address.organization = "Google";
// address.recipient = "Chen-Kang Yang"; // address.recipient = "Chen-Kang Yang";
// address.language_code = "en"; // address.language_code = "en";
// Process(address); // Process(address);
struct AddressData { struct AddressData {
// Returns the value of the |field|. The parameter should not be
// STREET_ADDRESS, which comprises multiple fields.
const std::string& GetField(AddressField field) const;
// The BCP 47 language code used to guide how the address is formatted for // The BCP 47 language code used to guide how the address is formatted for
// display. The same address may have different representations in different // display. The same address may have different representations in different
// languages. // languages.
......
...@@ -59,6 +59,9 @@ struct AddressProblem { ...@@ -59,6 +59,9 @@ struct AddressProblem {
MISMATCHING_VALUE MISMATCHING_VALUE
}; };
AddressProblem(AddressField field, Type type, const std::string& description);
~AddressProblem();
// The address field that has the problem. // The address field that has the problem.
AddressField field; AddressField field;
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
'target_name': 'libaddressinput', 'target_name': 'libaddressinput',
'type': '<(component)', 'type': '<(component)',
'sources': [ 'sources': [
'src/address_data.cc',
'src/address_field.cc', 'src/address_field.cc',
'src/address_problem.cc', 'src/address_problem.cc',
'src/address_ui.cc', 'src/address_ui.cc',
......
// Copyright (C) 2013 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/address_data.h>
#include <libaddressinput/address_field.h>
#include <cassert>
#include <string>
namespace i18n {
namespace addressinput {
const std::string& AddressData::GetField(AddressField field) const {
switch (field) {
case COUNTRY:
return country_code;
case ADMIN_AREA:
return administrative_area;
case LOCALITY:
return locality;
case DEPENDENT_LOCALITY:
return dependent_locality;
case SORTING_CODE:
return sorting_code;
case POSTAL_CODE:
return postal_code;
case ORGANIZATION:
return organization;
case RECIPIENT:
return recipient;
default:
assert(false);
return recipient;
}
}
} // namespace addressinput
} // namespace i18n
...@@ -15,10 +15,18 @@ ...@@ -15,10 +15,18 @@
#include <libaddressinput/address_problem.h> #include <libaddressinput/address_problem.h>
#include <ostream> #include <ostream>
#include <string>
namespace i18n { namespace i18n {
namespace addressinput { namespace addressinput {
AddressProblem::AddressProblem(AddressField field,
Type type,
const std::string& description)
: field(field), type(type), description(description) {}
AddressProblem::~AddressProblem() {}
std::ostream& operator<<(std::ostream& o, AddressProblem::Type problem_type) { std::ostream& operator<<(std::ostream& o, AddressProblem::Type problem_type) {
switch (problem_type) { switch (problem_type) {
case AddressProblem::MISSING_REQUIRED_FIELD: case AddressProblem::MISSING_REQUIRED_FIELD:
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include <libaddressinput/address_validator.h> #include <libaddressinput/address_validator.h>
#include <libaddressinput/address_data.h>
#include <libaddressinput/downloader.h> #include <libaddressinput/downloader.h>
#include <libaddressinput/load_rules_delegate.h> #include <libaddressinput/load_rules_delegate.h>
#include <libaddressinput/localization.h> #include <libaddressinput/localization.h>
...@@ -21,14 +22,19 @@ ...@@ -21,14 +22,19 @@
#include <libaddressinput/util/basictypes.h> #include <libaddressinput/util/basictypes.h>
#include <libaddressinput/util/scoped_ptr.h> #include <libaddressinput/util/scoped_ptr.h>
#include <algorithm>
#include <cassert> #include <cassert>
#include <cstddef> #include <cstddef>
#include <map> #include <map>
#include <set> #include <set>
#include <string> #include <string>
#include <re2/re2.h>
#include "country_rules_aggregator.h" #include "country_rules_aggregator.h"
#include "messages.h"
#include "retriever.h" #include "retriever.h"
#include "rule.h"
#include "ruleset.h" #include "ruleset.h"
#include "util/stl_util.h" #include "util/stl_util.h"
...@@ -37,6 +43,25 @@ namespace addressinput { ...@@ -37,6 +43,25 @@ namespace addressinput {
namespace { namespace {
// Returns true if the filter is empty (all problems allowed) or contains the
// |field|->|problem| mapping (explicitly allowed).
bool FilterAllows(const AddressProblemFilter& filter,
AddressField field,
AddressProblem::Type problem) {
if (filter.empty()) {
return true;
}
for (AddressProblemFilter::const_iterator it = filter.begin();
it != filter.end(); ++it) {
if (it->first == field && it->second == problem) {
return true;
}
}
return false;
}
// Validates AddressData structure. // Validates AddressData structure.
class AddressValidatorImpl : public AddressValidator { class AddressValidatorImpl : public AddressValidator {
public: public:
...@@ -74,8 +99,95 @@ class AddressValidatorImpl : public AddressValidator { ...@@ -74,8 +99,95 @@ class AddressValidatorImpl : public AddressValidator {
const AddressProblemFilter& filter, const AddressProblemFilter& filter,
const Localization& localization, const Localization& localization,
AddressProblems* problems) const { AddressProblems* problems) const {
// TODO(rouslan): Validate the address. std::map<std::string, const Ruleset*>::const_iterator ruleset_it =
return RULES_UNAVAILABLE; rules_.find(address.country_code);
if (ruleset_it == rules_.end()) {
return loading_rules_.find(address.country_code) != loading_rules_.end()
? RULES_NOT_READY
: RULES_UNAVAILABLE;
}
const Ruleset* ruleset = ruleset_it->second;
assert(ruleset != NULL);
const Rule& country_rule =
ruleset->GetLanguageCodeRule(address.language_code);
// Validate required fields.
for (std::vector<AddressField>::const_iterator
field_it = country_rule.GetRequired().begin();
field_it != country_rule.GetRequired().end();
++field_it) {
if (address.GetField(*field_it).empty() &&
FilterAllows(
filter, *field_it, AddressProblem::MISSING_REQUIRED_FIELD)) {
problems->push_back(AddressProblem(
*field_it,
AddressProblem::MISSING_REQUIRED_FIELD,
localization.GetString(
IDS_LIBADDRESSINPUT_I18N_MISSING_REQUIRED_FIELD)));
}
}
// Validate general postal code format. A country-level rule specifies the
// regular expression for the whole postal code.
if (!address.postal_code.empty() &&
!country_rule.GetPostalCodeFormat().empty() &&
FilterAllows(filter,
POSTAL_CODE,
AddressProblem::UNRECOGNIZED_FORMAT) &&
!RE2::FullMatch(
address.postal_code, country_rule.GetPostalCodeFormat())) {
problems->push_back(AddressProblem(
POSTAL_CODE,
AddressProblem::UNRECOGNIZED_FORMAT,
localization.GetString(
country_rule.GetInvalidPostalCodeMessageId())));
}
while (ruleset != NULL) {
const Rule& rule = ruleset->GetLanguageCodeRule(address.language_code);
// Validate the field values, e.g. state names in US.
AddressField sub_field_type =
static_cast<AddressField>(ruleset->field() + 1);
const std::string& sub_field = address.GetField(sub_field_type);
const std::vector<std::string>& sub_keys = rule.GetSubKeys();
if (!sub_field.empty() &&
!sub_keys.empty() &&
FilterAllows(filter, sub_field_type, AddressProblem::UNKNOWN_VALUE) &&
std::find(sub_keys.begin(), sub_keys.end(), sub_field) ==
sub_keys.end()) {
problems->push_back(AddressProblem(
sub_field_type,
AddressProblem::UNKNOWN_VALUE,
localization.GetString(
country_rule.GetInvalidFieldMessageId(sub_field_type))));
}
// Validate sub-region specific postal code format. A sub-region specifies
// the regular expression for a prefix of the postal code.
int match_position = -1;
if (ruleset->field() > COUNTRY &&
!address.postal_code.empty() &&
!rule.GetPostalCodeFormat().empty() &&
FilterAllows(filter,
POSTAL_CODE,
AddressProblem::MISMATCHING_VALUE) &&
(!RE2::PartialMatch(address.postal_code,
rule.GetPostalCodeFormat(),
&match_position) ||
match_position != 0)) {
problems->push_back(AddressProblem(
POSTAL_CODE,
AddressProblem::MISMATCHING_VALUE,
localization.GetString(
country_rule.GetInvalidPostalCodeMessageId())));
}
ruleset = ruleset->GetSubRegionRuleset(sub_field);
}
return SUCCESS;
} }
private: private:
...@@ -87,6 +199,8 @@ class AddressValidatorImpl : public AddressValidator { ...@@ -87,6 +199,8 @@ class AddressValidatorImpl : public AddressValidator {
assert(rules_.find(country_code) == rules_.end()); assert(rules_.find(country_code) == rules_.end());
loading_rules_.erase(country_code); loading_rules_.erase(country_code);
if (success) { if (success) {
assert(ruleset != NULL);
assert(ruleset->field() == COUNTRY);
rules_[country_code] = ruleset.release(); rules_[country_code] = ruleset.release();
} }
if (load_rules_delegate_ != NULL) { if (load_rules_delegate_ != NULL) {
...@@ -105,7 +219,7 @@ class AddressValidatorImpl : public AddressValidator { ...@@ -105,7 +219,7 @@ class AddressValidatorImpl : public AddressValidator {
std::set<std::string> loading_rules_; std::set<std::string> loading_rules_;
// A mapping of a country code to the owned ruleset for that country code. // A mapping of a country code to the owned ruleset for that country code.
std::map<std::string, Ruleset*> rules_; std::map<std::string, const Ruleset*> rules_;
DISALLOW_COPY_AND_ASSIGN(AddressValidatorImpl); DISALLOW_COPY_AND_ASSIGN(AddressValidatorImpl);
}; };
......
...@@ -143,11 +143,11 @@ void CountryRulesAggregator::OnDataReady(bool success, ...@@ -143,11 +143,11 @@ void CountryRulesAggregator::OnDataReady(bool success,
default_language_ = rule->GetLanguage(); default_language_ = rule->GetLanguage();
languages_ = rule->GetLanguages(); languages_ = rule->GetLanguages();
root_.reset(new Ruleset(rule.Pass())); root_.reset(new Ruleset(request.level, rule.Pass()));
ruleset = root_.get(); ruleset = root_.get();
} else { } else {
assert(request.parent != NULL); assert(request.parent != NULL);
ruleset = new Ruleset(rule.Pass()); ruleset = new Ruleset(request.level, rule.Pass());
request.parent->AddSubRegionRuleset( request.parent->AddSubRegionRuleset(
request.id, scoped_ptr<Ruleset>(ruleset)); request.id, scoped_ptr<Ruleset>(ruleset));
} }
......
...@@ -111,49 +111,62 @@ void ParseAddressFieldsRequired(const std::string& required, ...@@ -111,49 +111,62 @@ void ParseAddressFieldsRequired(const std::string& required,
} }
} }
int GetAdminAreaMessageId(const std::string& admin_area_type) { int GetAdminAreaMessageId(const std::string& admin_area_type, bool error) {
if (admin_area_type == "area") { if (admin_area_type == "area") {
return IDS_LIBADDRESSINPUT_I18N_AREA; return error ? IDS_LIBADDRESSINPUT_I18N_INVALID_AREA
: IDS_LIBADDRESSINPUT_I18N_AREA;
} }
if (admin_area_type == "county") { if (admin_area_type == "county") {
return IDS_LIBADDRESSINPUT_I18N_COUNTY_LABEL; return error ? IDS_LIBADDRESSINPUT_I18N_INVALID_COUNTY_LABEL
: IDS_LIBADDRESSINPUT_I18N_COUNTY_LABEL;
} }
if (admin_area_type == "department") { if (admin_area_type == "department") {
return IDS_LIBADDRESSINPUT_I18N_DEPARTMENT; return error ? IDS_LIBADDRESSINPUT_I18N_INVALID_DEPARTMENT
: IDS_LIBADDRESSINPUT_I18N_DEPARTMENT;
} }
if (admin_area_type == "district") { if (admin_area_type == "district") {
return IDS_LIBADDRESSINPUT_I18N_DEPENDENT_LOCALITY_LABEL; return error ? IDS_LIBADDRESSINPUT_I18N_INVALID_DEPENDENT_LOCALITY_LABEL
: IDS_LIBADDRESSINPUT_I18N_DEPENDENT_LOCALITY_LABEL;
} }
if (admin_area_type == "do_si") { if (admin_area_type == "do_si") {
return IDS_LIBADDRESSINPUT_I18N_DO_SI; return error ? IDS_LIBADDRESSINPUT_I18N_INVALID_DO_SI
: IDS_LIBADDRESSINPUT_I18N_DO_SI;
} }
if (admin_area_type == "emirate") { if (admin_area_type == "emirate") {
return IDS_LIBADDRESSINPUT_I18N_EMIRATE; return error ? IDS_LIBADDRESSINPUT_I18N_INVALID_EMIRATE
: IDS_LIBADDRESSINPUT_I18N_EMIRATE;
} }
if (admin_area_type == "island") { if (admin_area_type == "island") {
return IDS_LIBADDRESSINPUT_I18N_ISLAND; return error ? IDS_LIBADDRESSINPUT_I18N_INVALID_ISLAND
: IDS_LIBADDRESSINPUT_I18N_ISLAND;
} }
if (admin_area_type == "parish") { if (admin_area_type == "parish") {
return IDS_LIBADDRESSINPUT_I18N_PARISH; return error ? IDS_LIBADDRESSINPUT_I18N_INVALID_PARISH
: IDS_LIBADDRESSINPUT_I18N_PARISH;
} }
if (admin_area_type == "prefecture") { if (admin_area_type == "prefecture") {
return IDS_LIBADDRESSINPUT_I18N_PREFECTURE; return error ? IDS_LIBADDRESSINPUT_I18N_INVALID_PREFECTURE
: IDS_LIBADDRESSINPUT_I18N_PREFECTURE;
} }
if (admin_area_type == "province") { if (admin_area_type == "province") {
return IDS_LIBADDRESSINPUT_I18N_PROVINCE; return error ? IDS_LIBADDRESSINPUT_I18N_INVALID_PROVINCE
: IDS_LIBADDRESSINPUT_I18N_PROVINCE;
} }
if (admin_area_type == "state") { if (admin_area_type == "state") {
return IDS_LIBADDRESSINPUT_I18N_STATE_LABEL; return error ? IDS_LIBADDRESSINPUT_I18N_INVALID_STATE_LABEL
: IDS_LIBADDRESSINPUT_I18N_STATE_LABEL;
} }
return INVALID_MESSAGE_ID; return INVALID_MESSAGE_ID;
} }
int GetPostalCodeMessageId(const std::string& postal_code_type) { int GetPostalCodeMessageId(const std::string& postal_code_type, bool error) {
if (postal_code_type == "postal") { if (postal_code_type == "postal") {
return IDS_LIBADDRESSINPUT_I18N_POSTAL_CODE_LABEL; return error ? IDS_LIBADDRESSINPUT_I18N_INVALID_POSTAL_CODE_LABEL
: IDS_LIBADDRESSINPUT_I18N_POSTAL_CODE_LABEL;
} }
if (postal_code_type == "zip") { if (postal_code_type == "zip") {
return IDS_LIBADDRESSINPUT_I18N_ZIP_CODE_LABEL; return error ? IDS_LIBADDRESSINPUT_I18N_INVALID_ZIP_CODE_LABEL
: IDS_LIBADDRESSINPUT_I18N_ZIP_CODE_LABEL;
} }
return INVALID_MESSAGE_ID; return INVALID_MESSAGE_ID;
} }
...@@ -192,7 +205,9 @@ void Rule::CopyFrom(const Rule& rule) { ...@@ -192,7 +205,9 @@ void Rule::CopyFrom(const Rule& rule) {
language_ = rule.language_; language_ = rule.language_;
postal_code_format_ = rule.postal_code_format_; postal_code_format_ = rule.postal_code_format_;
admin_area_name_message_id_ = rule.admin_area_name_message_id_; admin_area_name_message_id_ = rule.admin_area_name_message_id_;
invalid_admin_area_message_id_ = rule.invalid_admin_area_message_id_;
postal_code_name_message_id_ = rule.postal_code_name_message_id_; postal_code_name_message_id_ = rule.postal_code_name_message_id_;
invalid_postal_code_message_id_ = rule.invalid_postal_code_message_id_;
} }
bool Rule::ParseSerializedRule(const std::string& serialized_rule) { bool Rule::ParseSerializedRule(const std::string& serialized_rule) {
...@@ -230,15 +245,32 @@ bool Rule::ParseSerializedRule(const std::string& serialized_rule) { ...@@ -230,15 +245,32 @@ bool Rule::ParseSerializedRule(const std::string& serialized_rule) {
} }
if (json->GetStringValueForKey("state_name_type", &value)) { if (json->GetStringValueForKey("state_name_type", &value)) {
admin_area_name_message_id_ = GetAdminAreaMessageId(value); admin_area_name_message_id_ = GetAdminAreaMessageId(value, false);
invalid_admin_area_message_id_ = GetAdminAreaMessageId(value, true);
} }
if (json->GetStringValueForKey("zip_name_type", &value)) { if (json->GetStringValueForKey("zip_name_type", &value)) {
postal_code_name_message_id_ = GetPostalCodeMessageId(value); postal_code_name_message_id_ = GetPostalCodeMessageId(value, false);
invalid_postal_code_message_id_ = GetPostalCodeMessageId(value, true);
} }
return true; return true;
} }
int Rule::GetInvalidFieldMessageId(AddressField field) const {
switch (field) {
case ADMIN_AREA:
return invalid_admin_area_message_id_;
case LOCALITY:
return IDS_LIBADDRESSINPUT_I18N_INVALID_LOCALITY_LABEL;
case DEPENDENT_LOCALITY:
return IDS_LIBADDRESSINPUT_I18N_INVALID_DEPENDENT_LOCALITY_LABEL;
case POSTAL_CODE:
return invalid_postal_code_message_id_;
default:
return IDS_LIBADDRESSINPUT_I18N_INVALID_ENTRY;
}
}
} // namespace addressinput } // namespace addressinput
} // namespace i18n } // namespace i18n
...@@ -76,12 +76,27 @@ class Rule { ...@@ -76,12 +76,27 @@ class Rule {
// INVALID_MESSAGE_ID. // INVALID_MESSAGE_ID.
int GetAdminAreaNameMessageId() const { return admin_area_name_message_id_; } int GetAdminAreaNameMessageId() const { return admin_area_name_message_id_; }
// The error message string identifier for an invalid admin area. If not set,
// then INVALID_MESSAGE_ID.
int GetInvalidAdminAreaMessageId() const {
return invalid_admin_area_message_id_;
}
// The message string identifier for postal code name. If not set, then // The message string identifier for postal code name. If not set, then
// INVALID_MESSAGE_ID. // INVALID_MESSAGE_ID.
int GetPostalCodeNameMessageId() const { int GetPostalCodeNameMessageId() const {
return postal_code_name_message_id_; return postal_code_name_message_id_;
} }
// The error message string identifier for an invalid postal code. If not set,
// then INVALID_MESSAGE_ID.
int GetInvalidPostalCodeMessageId() const {
return invalid_postal_code_message_id_;
}
// Returns the error message string identifier for an invalid |field|.
int GetInvalidFieldMessageId(AddressField field) const;
private: private:
std::vector<std::vector<AddressField> > format_; std::vector<std::vector<AddressField> > format_;
std::vector<AddressField> required_; std::vector<AddressField> required_;
...@@ -90,7 +105,9 @@ class Rule { ...@@ -90,7 +105,9 @@ class Rule {
std::string language_; std::string language_;
std::string postal_code_format_; std::string postal_code_format_;
int admin_area_name_message_id_; int admin_area_name_message_id_;
int invalid_admin_area_message_id_;
int postal_code_name_message_id_; int postal_code_name_message_id_;
int invalid_postal_code_message_id_;
DISALLOW_COPY_AND_ASSIGN(Rule); DISALLOW_COPY_AND_ASSIGN(Rule);
}; };
......
...@@ -14,13 +14,13 @@ ...@@ -14,13 +14,13 @@
#include "ruleset.h" #include "ruleset.h"
#include <libaddressinput/address_field.h>
#include <libaddressinput/util/scoped_ptr.h> #include <libaddressinput/util/scoped_ptr.h>
#include <cassert> #include <cassert>
#include <cstddef> #include <cstddef>
#include <map> #include <map>
#include <string> #include <string>
#include <utility>
#include "rule.h" #include "rule.h"
#include "util/stl_util.h" #include "util/stl_util.h"
...@@ -28,10 +28,13 @@ ...@@ -28,10 +28,13 @@
namespace i18n { namespace i18n {
namespace addressinput { namespace addressinput {
Ruleset::Ruleset(scoped_ptr<Rule> rule) Ruleset::Ruleset(AddressField field, scoped_ptr<Rule> rule)
: rule_(rule.Pass()), : field_(field),
rule_(rule.Pass()),
sub_regions_(), sub_regions_(),
language_codes_() { language_codes_() {
assert(field_ >= COUNTRY);
assert(field_ <= DEPENDENT_LOCALITY);
assert(rule_ != NULL); assert(rule_ != NULL);
} }
...@@ -43,14 +46,30 @@ Ruleset::~Ruleset() { ...@@ -43,14 +46,30 @@ Ruleset::~Ruleset() {
void Ruleset::AddSubRegionRuleset(const std::string& sub_region, void Ruleset::AddSubRegionRuleset(const std::string& sub_region,
scoped_ptr<Ruleset> ruleset) { scoped_ptr<Ruleset> ruleset) {
assert(sub_regions_.find(sub_region) == sub_regions_.end()); assert(sub_regions_.find(sub_region) == sub_regions_.end());
assert(ruleset != NULL);
assert(ruleset->field() == static_cast<AddressField>(field() + 1));
sub_regions_[sub_region] = ruleset.release(); sub_regions_[sub_region] = ruleset.release();
} }
void Ruleset::AddLanguageCodeRule(const std::string& language_code, void Ruleset::AddLanguageCodeRule(const std::string& language_code,
scoped_ptr<Rule> rule) { scoped_ptr<Rule> rule) {
assert(language_codes_.find(language_code) == language_codes_.end()); assert(language_codes_.find(language_code) == language_codes_.end());
assert(rule != NULL);
language_codes_[language_code] = rule.release(); language_codes_[language_code] = rule.release();
} }
Ruleset* Ruleset::GetSubRegionRuleset(const std::string& sub_region) const {
std::map<std::string, Ruleset*>::const_iterator it =
sub_regions_.find(sub_region);
return it != sub_regions_.end() ? it->second : NULL;
}
const Rule& Ruleset::GetLanguageCodeRule(
const std::string& language_code) const {
std::map<std::string, const Rule*>::const_iterator it =
language_codes_.find(language_code);
return it != language_codes_.end() ? *it->second : *rule_;
}
} // namespace addressinput } // namespace addressinput
} // namespace i18n } // namespace i18n
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#ifndef I18N_ADDRESSINPUT_RULESET_H_ #ifndef I18N_ADDRESSINPUT_RULESET_H_
#define I18N_ADDRESSINPUT_RULESET_H_ #define I18N_ADDRESSINPUT_RULESET_H_
#include <libaddressinput/address_field.h>
#include <libaddressinput/util/basictypes.h> #include <libaddressinput/util/basictypes.h>
#include <libaddressinput/util/scoped_ptr.h> #include <libaddressinput/util/scoped_ptr.h>
...@@ -42,26 +43,49 @@ class Rule; ...@@ -42,26 +43,49 @@ class Rule;
// language version. // language version.
class Ruleset { class Ruleset {
public: public:
// Builds a ruleset with a region-wide |rule| in the default language of the // Builds a ruleset for |field| with a region-wide |rule| in the default
// country. The |rule| should not be NULL. // language of the country. The |field| should be between COUNTRY and
explicit Ruleset(scoped_ptr<Rule> rule); // DEPENDENT_LOCALITY (inclusively). The |rule| should not be NULL.
Ruleset(AddressField field, scoped_ptr<Rule> rule);
~Ruleset(); ~Ruleset();
// Returns the region-wide rule in the default language of the country. // Returns the field type for this ruleset.
const Rule& rule() const { return *rule_.get(); } AddressField field() const { return field_; }
// Adds and the |ruleset| for |sub_region|. // Returns the region-wide rule for this ruleset in the default language of
// the country.
const Rule& rule() const { return *rule_; }
// Adds the |ruleset| for |sub_region|. A |sub_region| should be added at most
// once. The |ruleset| should not be NULL.
//
// The field of the |ruleset| parameter must be exactly one smaller than the
// field of this ruleset. For example, a COUNTRY ruleset can contain
// ADMIN_AREA rulesets, but should not contain COUNTRY or LOCALITY rulesets.
void AddSubRegionRuleset(const std::string& sub_region, void AddSubRegionRuleset(const std::string& sub_region,
scoped_ptr<Ruleset> ruleset); scoped_ptr<Ruleset> ruleset);
// Adds a language-specific |rule| for |language_code| for this region. // Adds a language-specific |rule| for |language_code| for this region. A
// |language_code| should be added at most once. The |rule| should not be
// NULL.
void AddLanguageCodeRule(const std::string& language_code, void AddLanguageCodeRule(const std::string& language_code,
scoped_ptr<Rule> rule); scoped_ptr<Rule> rule);
// Returns the set of rules for |sub_region|. The result is NULL if there's no
// such |sub_region|. The caller does not own the result.
Ruleset* GetSubRegionRuleset(const std::string& sub_region) const;
// If there's a language-specific rule for |language_code|, then returns this
// rule. Otherwise returns the rule in the default language of the country.
const Rule& GetLanguageCodeRule(const std::string& language_code) const;
private: private:
// The field of this ruleset.
const AddressField field_;
// The region-wide rule in the default language of the country. // The region-wide rule in the default language of the country.
scoped_ptr<const Rule> rule_; const scoped_ptr<const Rule> rule_;
// Owned rulesets for sub-regions. // Owned rulesets for sub-regions.
std::map<std::string, Ruleset*> sub_regions_; std::map<std::string, Ruleset*> sub_regions_;
......
...@@ -71,8 +71,12 @@ TEST(RuleTest, CopyOverwritesRule) { ...@@ -71,8 +71,12 @@ TEST(RuleTest, CopyOverwritesRule) {
EXPECT_NE(rule.GetPostalCodeFormat(), copy.GetPostalCodeFormat()); EXPECT_NE(rule.GetPostalCodeFormat(), copy.GetPostalCodeFormat());
EXPECT_NE(rule.GetAdminAreaNameMessageId(), EXPECT_NE(rule.GetAdminAreaNameMessageId(),
copy.GetAdminAreaNameMessageId()); copy.GetAdminAreaNameMessageId());
EXPECT_NE(rule.GetInvalidAdminAreaMessageId(),
copy.GetInvalidAdminAreaMessageId());
EXPECT_NE(rule.GetPostalCodeNameMessageId(), EXPECT_NE(rule.GetPostalCodeNameMessageId(),
copy.GetPostalCodeNameMessageId()); copy.GetPostalCodeNameMessageId());
EXPECT_NE(rule.GetInvalidPostalCodeMessageId(),
copy.GetInvalidPostalCodeMessageId());
copy.CopyFrom(rule); copy.CopyFrom(rule);
EXPECT_EQ(rule.GetFormat(), copy.GetFormat()); EXPECT_EQ(rule.GetFormat(), copy.GetFormat());
...@@ -83,8 +87,12 @@ TEST(RuleTest, CopyOverwritesRule) { ...@@ -83,8 +87,12 @@ TEST(RuleTest, CopyOverwritesRule) {
EXPECT_EQ(rule.GetPostalCodeFormat(), copy.GetPostalCodeFormat()); EXPECT_EQ(rule.GetPostalCodeFormat(), copy.GetPostalCodeFormat());
EXPECT_EQ(rule.GetAdminAreaNameMessageId(), EXPECT_EQ(rule.GetAdminAreaNameMessageId(),
copy.GetAdminAreaNameMessageId()); copy.GetAdminAreaNameMessageId());
EXPECT_EQ(rule.GetInvalidAdminAreaMessageId(),
copy.GetInvalidAdminAreaMessageId());
EXPECT_EQ(rule.GetPostalCodeNameMessageId(), EXPECT_EQ(rule.GetPostalCodeNameMessageId(),
copy.GetPostalCodeNameMessageId()); copy.GetPostalCodeNameMessageId());
EXPECT_EQ(rule.GetInvalidPostalCodeMessageId(),
copy.GetInvalidPostalCodeMessageId());
} }
TEST(RuleTest, ParseOverwritesRule) { TEST(RuleTest, ParseOverwritesRule) {
...@@ -108,8 +116,12 @@ TEST(RuleTest, ParseOverwritesRule) { ...@@ -108,8 +116,12 @@ TEST(RuleTest, ParseOverwritesRule) {
EXPECT_FALSE(rule.GetPostalCodeFormat().empty()); EXPECT_FALSE(rule.GetPostalCodeFormat().empty());
EXPECT_EQ(IDS_LIBADDRESSINPUT_I18N_AREA, EXPECT_EQ(IDS_LIBADDRESSINPUT_I18N_AREA,
rule.GetAdminAreaNameMessageId()); rule.GetAdminAreaNameMessageId());
EXPECT_EQ(IDS_LIBADDRESSINPUT_I18N_INVALID_AREA,
rule.GetInvalidAdminAreaMessageId());
EXPECT_EQ(IDS_LIBADDRESSINPUT_I18N_POSTAL_CODE_LABEL, EXPECT_EQ(IDS_LIBADDRESSINPUT_I18N_POSTAL_CODE_LABEL,
rule.GetPostalCodeNameMessageId()); rule.GetPostalCodeNameMessageId());
EXPECT_EQ(IDS_LIBADDRESSINPUT_I18N_INVALID_POSTAL_CODE_LABEL,
rule.GetInvalidPostalCodeMessageId());
ASSERT_TRUE(rule.ParseSerializedRule( ASSERT_TRUE(rule.ParseSerializedRule(
"{" "{"
...@@ -130,8 +142,12 @@ TEST(RuleTest, ParseOverwritesRule) { ...@@ -130,8 +142,12 @@ TEST(RuleTest, ParseOverwritesRule) {
EXPECT_TRUE(rule.GetPostalCodeFormat().empty()); EXPECT_TRUE(rule.GetPostalCodeFormat().empty());
EXPECT_EQ(IDS_LIBADDRESSINPUT_I18N_DO_SI, EXPECT_EQ(IDS_LIBADDRESSINPUT_I18N_DO_SI,
rule.GetAdminAreaNameMessageId()); rule.GetAdminAreaNameMessageId());
EXPECT_EQ(IDS_LIBADDRESSINPUT_I18N_INVALID_DO_SI,
rule.GetInvalidAdminAreaMessageId());
EXPECT_EQ(IDS_LIBADDRESSINPUT_I18N_ZIP_CODE_LABEL, EXPECT_EQ(IDS_LIBADDRESSINPUT_I18N_ZIP_CODE_LABEL,
rule.GetPostalCodeNameMessageId()); rule.GetPostalCodeNameMessageId());
EXPECT_EQ(IDS_LIBADDRESSINPUT_I18N_INVALID_ZIP_CODE_LABEL,
rule.GetInvalidPostalCodeMessageId());
} }
TEST(RuleTest, ParseEmptyDataDoesNotOverwriteRule) { TEST(RuleTest, ParseEmptyDataDoesNotOverwriteRule) {
...@@ -160,8 +176,12 @@ TEST(RuleTest, ParseEmptyDataDoesNotOverwriteRule) { ...@@ -160,8 +176,12 @@ TEST(RuleTest, ParseEmptyDataDoesNotOverwriteRule) {
EXPECT_EQ(rule.GetPostalCodeFormat(), copy.GetPostalCodeFormat()); EXPECT_EQ(rule.GetPostalCodeFormat(), copy.GetPostalCodeFormat());
EXPECT_EQ(rule.GetAdminAreaNameMessageId(), EXPECT_EQ(rule.GetAdminAreaNameMessageId(),
copy.GetAdminAreaNameMessageId()); copy.GetAdminAreaNameMessageId());
EXPECT_EQ(rule.GetInvalidAdminAreaMessageId(),
copy.GetInvalidAdminAreaMessageId());
EXPECT_EQ(rule.GetPostalCodeNameMessageId(), EXPECT_EQ(rule.GetPostalCodeNameMessageId(),
copy.GetPostalCodeNameMessageId()); copy.GetPostalCodeNameMessageId());
EXPECT_EQ(rule.GetInvalidPostalCodeMessageId(),
copy.GetInvalidPostalCodeMessageId());
} }
TEST(RuleTest, ParseFormatWithNewLines) { TEST(RuleTest, ParseFormatWithNewLines) {
...@@ -288,67 +308,93 @@ TEST(RuleTest, EmptyDictionaryIsValid) { ...@@ -288,67 +308,93 @@ TEST(RuleTest, EmptyDictionaryIsValid) {
EXPECT_TRUE(rule.ParseSerializedRule("{}")); EXPECT_TRUE(rule.ParseSerializedRule("{}"));
} }
struct LabelData {
LabelData(const std::string& data, int name_id, int error_id)
: data(data), name_id(name_id), error_id(error_id) {}
~LabelData() {}
std::string data;
int name_id;
int error_id;
};
// Tests for parsing the postal code name. // Tests for parsing the postal code name.
class PostalCodeNameParseTest class PostalCodeNameParseTest : public testing::TestWithParam<LabelData> {
: public testing::TestWithParam<std::pair<std::string, int> > {
protected: protected:
Rule rule_; Rule rule_;
}; };
// Verifies that a postal code name is parsed correctly. // Verifies that a postal code name is parsed correctly.
TEST_P(PostalCodeNameParseTest, ParsedCorrectly) { TEST_P(PostalCodeNameParseTest, ParsedCorrectly) {
ASSERT_TRUE(rule_.ParseSerializedRule(GetParam().first)); ASSERT_TRUE(rule_.ParseSerializedRule(GetParam().data));
EXPECT_EQ(GetParam().second, rule_.GetPostalCodeNameMessageId()); EXPECT_EQ(GetParam().name_id, rule_.GetPostalCodeNameMessageId());
EXPECT_EQ(GetParam().error_id, rule_.GetInvalidPostalCodeMessageId());
EXPECT_EQ(GetParam().error_id, rule_.GetInvalidFieldMessageId(POSTAL_CODE));
} }
// Test parsing all postal code names. // Test parsing all postal code names.
INSTANTIATE_TEST_CASE_P( INSTANTIATE_TEST_CASE_P(
AllPostalCodeNames, PostalCodeNameParseTest, AllPostalCodeNames, PostalCodeNameParseTest,
testing::Values( testing::Values(
std::make_pair("{\"zip_name_type\":\"postal\"}", LabelData("{\"zip_name_type\":\"postal\"}",
IDS_LIBADDRESSINPUT_I18N_POSTAL_CODE_LABEL), IDS_LIBADDRESSINPUT_I18N_POSTAL_CODE_LABEL,
std::make_pair("{\"zip_name_type\":\"zip\"}", IDS_LIBADDRESSINPUT_I18N_INVALID_POSTAL_CODE_LABEL),
IDS_LIBADDRESSINPUT_I18N_ZIP_CODE_LABEL))); LabelData("{\"zip_name_type\":\"zip\"}",
IDS_LIBADDRESSINPUT_I18N_ZIP_CODE_LABEL,
IDS_LIBADDRESSINPUT_I18N_INVALID_ZIP_CODE_LABEL)));
// Tests for parsing the administrative area name. // Tests for parsing the administrative area name.
class AdminAreaNameParseTest class AdminAreaNameParseTest : public testing::TestWithParam<LabelData> {
: public testing::TestWithParam<std::pair<std::string, int> > {
protected: protected:
Rule rule_; Rule rule_;
}; };
// Verifies that an administrative area name is parsed correctly. // Verifies that an administrative area name is parsed correctly.
TEST_P(AdminAreaNameParseTest, ParsedCorrectly) { TEST_P(AdminAreaNameParseTest, ParsedCorrectly) {
ASSERT_TRUE(rule_.ParseSerializedRule(GetParam().first)); ASSERT_TRUE(rule_.ParseSerializedRule(GetParam().data));
EXPECT_EQ(GetParam().second, rule_.GetAdminAreaNameMessageId()); EXPECT_EQ(GetParam().name_id, rule_.GetAdminAreaNameMessageId());
EXPECT_EQ(GetParam().error_id, rule_.GetInvalidAdminAreaMessageId());
EXPECT_EQ(GetParam().error_id, rule_.GetInvalidFieldMessageId(ADMIN_AREA));
} }
// Test parsing all administrative area names. // Test parsing all administrative area names.
INSTANTIATE_TEST_CASE_P( INSTANTIATE_TEST_CASE_P(
AllAdminAreaNames, AdminAreaNameParseTest, AllAdminAreaNames, AdminAreaNameParseTest,
testing::Values( testing::Values(
std::make_pair("{\"state_name_type\":\"area\"}", LabelData("{\"state_name_type\":\"area\"}",
IDS_LIBADDRESSINPUT_I18N_AREA), IDS_LIBADDRESSINPUT_I18N_AREA,
std::make_pair("{\"state_name_type\":\"county\"}", IDS_LIBADDRESSINPUT_I18N_INVALID_AREA),
IDS_LIBADDRESSINPUT_I18N_COUNTY_LABEL), LabelData("{\"state_name_type\":\"county\"}",
std::make_pair("{\"state_name_type\":\"department\"}", IDS_LIBADDRESSINPUT_I18N_COUNTY_LABEL,
IDS_LIBADDRESSINPUT_I18N_DEPARTMENT), IDS_LIBADDRESSINPUT_I18N_INVALID_COUNTY_LABEL),
std::make_pair("{\"state_name_type\":\"district\"}", LabelData("{\"state_name_type\":\"department\"}",
IDS_LIBADDRESSINPUT_I18N_DEPENDENT_LOCALITY_LABEL), IDS_LIBADDRESSINPUT_I18N_DEPARTMENT,
std::make_pair("{\"state_name_type\":\"do_si\"}", IDS_LIBADDRESSINPUT_I18N_INVALID_DEPARTMENT),
IDS_LIBADDRESSINPUT_I18N_DO_SI), LabelData("{\"state_name_type\":\"district\"}",
std::make_pair("{\"state_name_type\":\"emirate\"}", IDS_LIBADDRESSINPUT_I18N_DEPENDENT_LOCALITY_LABEL,
IDS_LIBADDRESSINPUT_I18N_EMIRATE), IDS_LIBADDRESSINPUT_I18N_INVALID_DEPENDENT_LOCALITY_LABEL),
std::make_pair("{\"state_name_type\":\"island\"}", LabelData("{\"state_name_type\":\"do_si\"}",
IDS_LIBADDRESSINPUT_I18N_ISLAND), IDS_LIBADDRESSINPUT_I18N_DO_SI,
std::make_pair("{\"state_name_type\":\"parish\"}", IDS_LIBADDRESSINPUT_I18N_INVALID_DO_SI),
IDS_LIBADDRESSINPUT_I18N_PARISH), LabelData("{\"state_name_type\":\"emirate\"}",
std::make_pair("{\"state_name_type\":\"prefecture\"}", IDS_LIBADDRESSINPUT_I18N_EMIRATE,
IDS_LIBADDRESSINPUT_I18N_PREFECTURE), IDS_LIBADDRESSINPUT_I18N_INVALID_EMIRATE),
std::make_pair("{\"state_name_type\":\"province\"}", LabelData("{\"state_name_type\":\"island\"}",
IDS_LIBADDRESSINPUT_I18N_PROVINCE), IDS_LIBADDRESSINPUT_I18N_ISLAND,
std::make_pair("{\"state_name_type\":\"state\"}", IDS_LIBADDRESSINPUT_I18N_INVALID_ISLAND),
IDS_LIBADDRESSINPUT_I18N_STATE_LABEL))); LabelData("{\"state_name_type\":\"parish\"}",
IDS_LIBADDRESSINPUT_I18N_PARISH,
IDS_LIBADDRESSINPUT_I18N_INVALID_PARISH),
LabelData("{\"state_name_type\":\"prefecture\"}",
IDS_LIBADDRESSINPUT_I18N_PREFECTURE,
IDS_LIBADDRESSINPUT_I18N_INVALID_PREFECTURE),
LabelData("{\"state_name_type\":\"province\"}",
IDS_LIBADDRESSINPUT_I18N_PROVINCE,
IDS_LIBADDRESSINPUT_I18N_INVALID_PROVINCE),
LabelData("{\"state_name_type\":\"state\"}",
IDS_LIBADDRESSINPUT_I18N_STATE_LABEL,
IDS_LIBADDRESSINPUT_I18N_INVALID_STATE_LABEL)));
// Tests for rule parsing. // Tests for rule parsing.
class RuleParseTest : public testing::TestWithParam<std::string> { class RuleParseTest : public testing::TestWithParam<std::string> {
......
...@@ -68,6 +68,7 @@ ...@@ -68,6 +68,7 @@
'<(libaddressinput_dir)/cpp/include/libaddressinput/util/internal/scoped_ptr.h', '<(libaddressinput_dir)/cpp/include/libaddressinput/util/internal/scoped_ptr.h',
'<(libaddressinput_dir)/cpp/include/libaddressinput/util/internal/template_util.h', '<(libaddressinput_dir)/cpp/include/libaddressinput/util/internal/template_util.h',
'<(libaddressinput_dir)/cpp/include/libaddressinput/util/scoped_ptr.h', '<(libaddressinput_dir)/cpp/include/libaddressinput/util/scoped_ptr.h',
'<(libaddressinput_dir)/cpp/src/address_data.cc',
'<(libaddressinput_dir)/cpp/src/address_field.cc', '<(libaddressinput_dir)/cpp/src/address_field.cc',
'<(libaddressinput_dir)/cpp/src/address_problem.cc', '<(libaddressinput_dir)/cpp/src/address_problem.cc',
'<(libaddressinput_dir)/cpp/src/address_ui.cc', '<(libaddressinput_dir)/cpp/src/address_ui.cc',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment