Commit 9983928d authored by rouslan@chromium.org's avatar rouslan@chromium.org

[rac] Format an address for display.

BUG=327046

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@245389 0039d316-1c4b-4281-b951-d872f2087c98
parent 99cc6b44
......@@ -29,19 +29,28 @@ namespace addressinput {
// Stores an address. Sample usage:
// AddressData address;
// address.country_code = "US";
// address.recipient = "Chen-Kang Yang";
// address.organization = "Google";
// address.address_lines.push_back("1098 Alta Ave");
// address.administrative_area = "CA";
// address.locality = "Mountain View";
// address.postal_code = "94043";
// address.organization = "Google";
// address.recipient = "Chen-Kang Yang";
// address.country_code = "US";
// address.language_code = "en";
// Process(address);
struct AddressData {
// Clears |lines| and populates it with the lines of the address as they
// should appear on an envelope for |country_code|. The |lines| parameter
// should not be NULL.
//
// If there're no address formatting rules for |country_code|, then the
// default rules are used:
// https://i18napis.appspot.com/ssl-address/data/ZZ
void FormatForDisplay(std::vector<std::string>* lines) const;
// Returns the value of the |field|. The parameter should not be
// STREET_ADDRESS, which comprises multiple fields.
const std::string& GetField(AddressField field) const;
const std::string& GetFieldValue(AddressField field) const;
// The BCP 47 language code used to guide how the address is formatted for
// display. The same address may have different representations in different
......
......@@ -30,6 +30,12 @@ const std::vector<std::string>& GetRegionCodes();
// on error.
std::vector<AddressUiComponent> BuildComponents(const std::string& region_code);
// Returns the string to use as a separator between lines when displaying the
// address in a compact form. For example, returns ", " for en-US.
const std::string& GetCompactAddressLinesSeparator(
const std::string& language_code,
const std::string& country_code);
} // namespace addressinput
} // namespace i18n
......
......@@ -59,6 +59,7 @@
'target_name': 'unit_tests',
'type': 'executable',
'sources': [
'test/address_data_test.cc',
'test/address_ui_test.cc',
'test/fake_downloader.cc',
'test/fake_downloader_test.cc',
......
......@@ -17,12 +17,54 @@
#include <libaddressinput/address_field.h>
#include <cassert>
#include <cstddef>
#include <string>
#include <vector>
#include "region_data_constants.h"
#include "rule.h"
namespace i18n {
namespace addressinput {
const std::string& AddressData::GetField(AddressField field) const {
void AddressData::FormatForDisplay(std::vector<std::string>* lines) const {
assert(lines != NULL);
lines->clear();
Rule rule;
rule.CopyFrom(Rule::GetDefault());
rule.ParseSerializedRule(RegionDataConstants::GetRegionData(country_code));
const std::vector<std::vector<FormatElement> >& format = rule.GetFormat();
for (size_t i = 0; i < format.size(); ++i) {
std::string line;
for (size_t j = 0; j < format[i].size(); ++j) {
const FormatElement& element = format[i][j];
if (element.IsField()) {
if (element.field == STREET_ADDRESS) {
// Street address field can contain multiple values.
for (size_t k = 0; k < address_lines.size(); ++k) {
line += address_lines[k];
if (k < address_lines.size() - 1) {
lines->push_back(line);
line.clear();
}
}
} else {
line += GetFieldValue(element.field);
}
} else {
line += element.literal;
}
}
if (!line.empty()) {
lines->push_back(line);
}
}
}
const std::string& AddressData::GetFieldValue(AddressField field) const {
switch (field) {
case COUNTRY:
return country_code;
......
......@@ -17,6 +17,8 @@
#include <libaddressinput/address_field.h>
#include <libaddressinput/address_ui_component.h>
#include <algorithm>
#include <set>
#include <string>
#include <vector>
......@@ -74,19 +76,41 @@ std::vector<AddressUiComponent> BuildComponents(
return result;
}
for (std::vector<std::vector<AddressField> >::const_iterator
line_it = rule.GetFormat().begin();
// For avoiding showing an input field twice, when the field is displayed
// twice on an envelope.
std::set<AddressField> fields;
for (std::vector<std::vector<FormatElement> >::const_iterator
line_it = rule.GetFormat().begin();
line_it != rule.GetFormat().end();
++line_it) {
for (std::vector<AddressField>::const_iterator field_it = line_it->begin();
field_it != line_it->end(); ++field_it) {
int num_fields_this_row = 0;
for (std::vector<FormatElement>::const_iterator element_it =
line_it->begin();
element_it != line_it->end();
++element_it) {
if (element_it->IsField()) {
++num_fields_this_row;
}
}
for (std::vector<FormatElement>::const_iterator element_it =
line_it->begin();
element_it != line_it->end();
++element_it) {
AddressField field = element_it->field;
if (!element_it->IsField() || fields.find(field) != fields.end()) {
continue;
}
fields.insert(field);
AddressUiComponent component;
component.length_hint =
line_it->size() == 1 ? AddressUiComponent::HINT_LONG
: AddressUiComponent::HINT_SHORT;
component.field = *field_it;
num_fields_this_row == 1 ? AddressUiComponent::HINT_LONG
: AddressUiComponent::HINT_SHORT;
component.field = field;
component.name_id =
GetMessageIdForField(*field_it,
GetMessageIdForField(field,
rule.GetAdminAreaNameMessageId(),
rule.GetPostalCodeNameMessageId());
result.push_back(component);
......@@ -96,5 +120,31 @@ std::vector<AddressUiComponent> BuildComponents(
return result;
}
const std::string& GetCompactAddressLinesSeparator(
const std::string& language_code,
const std::string& country_code) {
Rule rule;
rule.CopyFrom(Rule::GetDefault());
if (!rule.ParseSerializedRule(
RegionDataConstants::GetRegionData(country_code))) {
return RegionDataConstants::GetLanguageCompactLineSeparator(language_code);
}
std::vector<std::string>::const_iterator lang_it =
std::find(rule.GetLanguages().begin(),
rule.GetLanguages().end(),
language_code);
if (lang_it != rule.GetLanguages().end()) {
return RegionDataConstants::GetLanguageCompactLineSeparator(*lang_it);
}
if (!rule.GetLanguage().empty()) {
return RegionDataConstants::GetLanguageCompactLineSeparator(
rule.GetLanguage());
}
return RegionDataConstants::GetCountryCompactLineSeparator(country_code);
}
} // namespace addressinput
} // namespace i18n
......@@ -115,7 +115,7 @@ class AddressValidatorImpl : public AddressValidator {
field_it = country_rule.GetRequired().begin();
field_it != country_rule.GetRequired().end();
++field_it) {
if (address.GetField(*field_it).empty() &&
if (address.GetFieldValue(*field_it).empty() &&
FilterAllows(
filter, *field_it, AddressProblem::MISSING_REQUIRED_FIELD)) {
problems->push_back(AddressProblem(
......@@ -146,7 +146,7 @@ class AddressValidatorImpl : public AddressValidator {
// 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::string& sub_field = address.GetFieldValue(sub_field_type);
const std::vector<std::string>& sub_keys = rule.GetSubKeys();
if (!sub_field.empty() &&
!sub_keys.empty() &&
......
......@@ -26,6 +26,12 @@ class RegionDataConstants {
static const std::vector<std::string>& GetRegionCodes();
static const std::string& GetRegionData(const std::string& region_code);
static const std::string& GetDefaultRegionData();
static const std::string& GetLanguageCompactLineSeparator(
const std::string& language_code);
static const std::string& GetCountryCompactLineSeparator(
const std::string& country_code);
};
} // namespace addressinput
......
......@@ -68,30 +68,56 @@ bool ParseToken(char c, AddressField* field) {
}
}
// Clears |fields|, parses |format|, and adds the format address fields to
// |fields|.
// Clears |lines|, parses |format|, and adds the address fields and literals to
// |lines|.
//
// For example, the address format in Switzerland is "%O%n%N%n%A%nAX-%Z
// %C%nÅLAND". It includes the allowed fields prefixed with %, newlines denoted
// %n, and the extra text that should be included on an envelope. This function
// parses only the tokens denoted % to determine how an address input form
// should be laid out.
//
// The format string "%O%n%N%n%A%nAX-%Z%C%nÅLAND" is parsed into
// {{ORGANIZATION}, {RECIPIENT}, {STREET_ADDRESS}, {POSTAL_CODE, LOCALITY}, {}}.
// For example, the address format in Finland is "%O%n%N%n%A%nAX-%Z %C%nÅLAND".
// It includes the allowed fields prefixed with %, newlines denoted %n, and the
// extra text that should be included on an envelope. It is parsed into:
// {
// {ORGANIZATION},
// {RECIPIENT},
// {STREET_ADDRESS},
// {"AX-", POSTAL_CODE, " ", LOCALITY},
// {"ÅLAND"}
// }
void ParseAddressFieldsFormat(const std::string& format,
std::vector<std::vector<AddressField> >* fields) {
assert(fields != NULL);
fields->clear();
fields->resize(1);
std::vector<std::vector<FormatElement> >* lines) {
assert(lines != NULL);
lines->clear();
lines->resize(1);
std::vector<std::string> format_parts;
SplitString(format, '%', &format_parts);
// If the address format starts with a literal, then it will be in the first
// element of |format_parts|. This literal does not begin with % and should
// not be parsed as a token.
if (!format_parts.empty() && !format_parts[0].empty()) {
lines->back().push_back(FormatElement(format_parts[0]));
}
// The rest of the elements in |format_parts| begin with %.
for (size_t i = 1; i < format_parts.size(); ++i) {
if (format_parts[i].empty()) {
continue;
}
// The first character after % denotes a field or a newline token.
const char control_character = format_parts[i][0];
// The rest of the string after the token is a literal.
const std::string literal = format_parts[i].substr(1);
AddressField field = COUNTRY;
if (ParseToken(format_parts[i][0], &field)) {
fields->back().push_back(field);
} else if (format_parts[i][0] == 'n') {
fields->push_back(std::vector<AddressField>());
if (ParseToken(control_character, &field)) {
lines->back().push_back(FormatElement(field));
} else if (control_character == 'n') {
lines->push_back(std::vector<FormatElement>());
}
if (!literal.empty()) {
lines->back().push_back(FormatElement(literal));
}
}
}
......@@ -173,6 +199,20 @@ int GetPostalCodeMessageId(const std::string& postal_code_type, bool error) {
} // namespace
FormatElement::FormatElement(AddressField field)
: field(field), literal() {}
FormatElement::FormatElement(const std::string& literal)
: field(COUNTRY), literal(literal) {
assert(!literal.empty());
}
FormatElement::~FormatElement() {}
bool FormatElement::operator==(const FormatElement& other) const {
return field == other.field && literal == other.literal;
}
Rule::Rule()
: format_(),
required_(),
......
......@@ -26,7 +26,34 @@
namespace i18n {
namespace addressinput {
// Stores the validation rules. Sample usage:
// Stores an element in the format of an address as it should be displayed on an
// envelope. The element can be either a literal string, like " ", or a field,
// like ADMIN_AREA.
struct FormatElement {
// Builds an element of address format for |field|.
explicit FormatElement(AddressField field);
// Builds an element of address format for |literal|. The literal should not
// be empty.
explicit FormatElement(const std::string& literal);
~FormatElement();
// Returns true if this is a field element.
bool IsField() const { return literal.empty(); }
bool operator==(const FormatElement& other) const;
// The field for this element in address format. Should be used only if
// |literal| is an empty string.
AddressField field;
// The literal string for this element in address format. If empty, then this
// is a field element.
std::string literal;
};
// Stores the validation, input, and display rules for an address. Sample usage:
// Rule rule;
// if (rule.ParseSerializedRule("{\"fmt\": \"%A%n%C%S %Z\"}")) {
// Process(rule.GetFormat());
......@@ -48,8 +75,8 @@ class Rule {
// format (JSON dictionary).
bool ParseSerializedRule(const std::string& serialized_rule);
// Returns the address format for this rule.
const std::vector<std::vector<AddressField> >& GetFormat() const {
// Returns the format of the address as it should appear on an envelope.
const std::vector<std::vector<FormatElement> >& GetFormat() const {
return format_;
}
......@@ -98,7 +125,7 @@ class Rule {
int GetInvalidFieldMessageId(AddressField field) const;
private:
std::vector<std::vector<AddressField> > format_;
std::vector<std::vector<FormatElement> > format_;
std::vector<AddressField> required_;
std::vector<std::string> sub_keys_;
std::vector<std::string> languages_;
......
// 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/address_data.h>
#include <string>
#include <vector>
#include <gtest/gtest.h>
namespace {
using i18n::addressinput::AddressData;
TEST(AddressDataTest, FormatForDisplayEmpty) {
AddressData address;
std::vector<std::string> actual;
address.FormatForDisplay(&actual);
EXPECT_EQ(std::vector<std::string>(), actual);
}
TEST(AddressDataTest, FormatForDisplayAr) {
AddressData address;
address.country_code = "AR";
address.administrative_area = "Capital Federal";
address.locality = "Buenos Aires";
address.postal_code = "C1001AFB";
address.address_lines.push_back("Su Calle 123");
address.address_lines.push_back("5° Piso");
address.organization = "Empresa Ejemplo";
address.recipient = "Juan Perez";
std::vector<std::string> actual;
address.FormatForDisplay(&actual);
std::vector<std::string> expected;
expected.push_back(address.recipient);
expected.push_back(address.organization);
expected.insert(expected.end(),
address.address_lines.begin(),
address.address_lines.end());
expected.push_back(address.postal_code + " " + address.locality);
expected.push_back(address.administrative_area);
EXPECT_EQ(expected, actual);
}
TEST(AddressDataTest, FormatForDisplayJp) {
AddressData address;
address.country_code = "JP";
address.administrative_area = "東京都";
address.locality = "渋谷区";
address.postal_code = "150-8512";
address.address_lines.push_back("桜丘町26-1");
address.address_lines.push_back("セルリアンタワー6階");
address.organization = "グーグル株式会社";
address.recipient = "村上 美紀";
std::vector<std::string> actual;
address.FormatForDisplay(&actual);
std::vector<std::string> expected;
expected.push_back("〒" + address.postal_code);
expected.push_back(address.administrative_area + address.locality);
expected.insert(expected.end(),
address.address_lines.begin(),
address.address_lines.end());
expected.push_back(address.organization);
expected.push_back(address.recipient);
EXPECT_EQ(expected, actual);
}
TEST(AddressDataTest, FormatForDisplayWithStreetCi) {
AddressData address;
address.country_code = "CI";
address.address_lines.push_back("Street Line 1");
address.locality = "City Name";
address.sorting_code = "123CEDEX";
std::vector<std::string> actual;
address.FormatForDisplay(&actual);
std::vector<std::string> expected(
1,
address.sorting_code + " " +
address.address_lines[0] + " " +
address.locality + " " +
address.sorting_code);
EXPECT_EQ(expected, actual);
}
TEST(AddressDataTest, FormatForDisplayWithoutStreetCi) {
AddressData address;
address.country_code = "CI";
address.locality = "City Name";
address.sorting_code = "123CEDEX";
std::vector<std::string> actual;
address.FormatForDisplay(&actual);
std::vector<std::string> expected(
1,
address.sorting_code + " " +
address.locality + " " +
address.sorting_code);
EXPECT_EQ(expected, actual);
}
} // namespace
......@@ -17,6 +17,7 @@
#include <libaddressinput/address_field.h>
#include <libaddressinput/address_ui_component.h>
#include <set>
#include <string>
#include <vector>
......@@ -29,25 +30,32 @@ namespace addressinput {
namespace {
// Returns testing::AssertionSuccess if the |components| are valid. Uses
// |region_code| in test failure messages.
// Returns testing::AssertionSuccess if the |components| are valid.
testing::AssertionResult ComponentsAreValid(
const std::vector<AddressUiComponent>& components) {
if (components.empty()) {
return testing::AssertionFailure() << "no components";
}
std::set<AddressField> fields;
for (std::vector<AddressUiComponent>::const_iterator
component_it = components.begin();
component_it != components.end(); ++component_it) {
component_it = components.begin();
component_it != components.end();
++component_it) {
static const AddressField kMinAddressField = COUNTRY;
static const AddressField kMaxAddressField = RECIPIENT;
if (component_it->field < kMinAddressField ||
component_it->field > kMaxAddressField) {
return testing::AssertionFailure() << "unexpected field "
return testing::AssertionFailure() << "unexpected input field "
<< component_it->field;
}
if (fields.find(component_it->field) != fields.end()) {
return testing::AssertionFailure() << "duplicate input field "
<< component_it->field;
}
fields.insert(component_it->field);
if (component_it->name_id == INVALID_MESSAGE_ID) {
return testing::AssertionFailure() << "invalid field name_id for field "
<< component_it->field;
......@@ -82,6 +90,45 @@ TEST_F(AddressUiTest, InvalidRegionCodeReturnsEmptyVector) {
EXPECT_TRUE(BuildComponents("INVALID-REGION-CODE").empty());
}
struct SeparatorData {
SeparatorData(const std::string& language_code,
const std::string& country_code,
const std::string& compact_line_separator)
: language_code(language_code),
country_code(country_code),
compact_line_separator(compact_line_separator) {}
~SeparatorData() {}
std::string language_code;
std::string country_code;
std::string compact_line_separator;
};
// Tests for compact line separator.
class CompactLineSeparatorTest
: public testing::TestWithParam<SeparatorData> {};
TEST_P(CompactLineSeparatorTest, BasicTest) {
EXPECT_EQ(GetParam().compact_line_separator,
GetCompactAddressLinesSeparator(GetParam().language_code,
GetParam().country_code));
}
INSTANTIATE_TEST_CASE_P(
CompactLineSeparators, CompactLineSeparatorTest,
testing::Values(
SeparatorData("", "AD", ", "),
SeparatorData("", "XX", ", "),
SeparatorData("ja", "JP", ""),
SeparatorData("zh", "HK", ""),
SeparatorData("zh-hans", "CN", ""),
SeparatorData("ar", "YE", "، "),
SeparatorData("ko", "KR", " "),
SeparatorData("th", "TH", " "),
SeparatorData("en", "US", ", ")));
} // namespace
} // namespace addressinput
......
......@@ -30,6 +30,7 @@ namespace {
using i18n::addressinput::AddressField;
using i18n::addressinput::ADMIN_AREA;
using i18n::addressinput::COUNTRY;
using i18n::addressinput::FormatElement;
using i18n::addressinput::LOCALITY;
using i18n::addressinput::ORGANIZATION;
using i18n::addressinput::POSTAL_CODE;
......@@ -38,9 +39,11 @@ using i18n::addressinput::RegionDataConstants;
using i18n::addressinput::Rule;
using i18n::addressinput::STREET_ADDRESS;
bool IsFormatEmpty(const std::vector<std::vector<AddressField> >& format) {
for (std::vector<std::vector<AddressField> >::const_iterator
it = format.begin(); it != format.end(); ++it) {
bool IsFormatEmpty(const std::vector<std::vector<FormatElement> >& format) {
for (std::vector<std::vector<FormatElement> >::const_iterator
it = format.begin();
it != format.end();
++it) {
if (!it->empty()) {
return false;
}
......@@ -188,13 +191,22 @@ TEST(RuleTest, ParseFormatWithNewLines) {
Rule rule;
ASSERT_TRUE(
rule.ParseSerializedRule("{\"fmt\":\"%O%n%N%n%A%nAX-%Z %C%nÅLAND\"}"));
std::vector<std::vector<AddressField> > expected_format;
expected_format.push_back(std::vector<AddressField>(1, ORGANIZATION));
expected_format.push_back(std::vector<AddressField>(1, RECIPIENT));
expected_format.push_back(std::vector<AddressField>(1, STREET_ADDRESS));
expected_format.push_back(std::vector<AddressField>(1, POSTAL_CODE));
expected_format.back().push_back(LOCALITY);
expected_format.push_back(std::vector<AddressField>());
std::vector<std::vector<FormatElement> > expected_format;
expected_format.push_back(
std::vector<FormatElement>(1, FormatElement(ORGANIZATION)));
expected_format.push_back(
std::vector<FormatElement>(1, FormatElement(RECIPIENT)));
expected_format.push_back(
std::vector<FormatElement>(1, FormatElement(STREET_ADDRESS)));
expected_format.push_back(
std::vector<FormatElement>(1, FormatElement("AX-")));
expected_format.back().push_back(FormatElement(POSTAL_CODE));
expected_format.back().push_back(FormatElement(" "));
expected_format.back().push_back(FormatElement(LOCALITY));
expected_format.push_back(
std::vector<FormatElement>(1, FormatElement("ÅLAND")));
EXPECT_EQ(expected_format, rule.GetFormat());
}
......@@ -413,16 +425,52 @@ class RuleParseTest : public testing::TestWithParam<std::string> {
TEST_P(RuleParseTest, ConsecutiveLinesWithMultipleFields) {
ASSERT_TRUE(rule_.ParseSerializedRule(GetData()));
bool previous_line_has_single_field = true;
for (std::vector<std::vector<AddressField> >::const_iterator
line_it = rule_.GetFormat().begin();
for (std::vector<std::vector<FormatElement> >::const_iterator
line_it = rule_.GetFormat().begin();
line_it != rule_.GetFormat().end();
++line_it) {
if (line_it->empty()) {
int num_fields = 0;
for (std::vector<FormatElement>::const_iterator
element_it = line_it->begin();
element_it != line_it->end();
++element_it) {
if (element_it->IsField()) {
++num_fields;
}
}
if (num_fields == 0) {
continue;
}
ASSERT_TRUE(line_it->size() == 1 || previous_line_has_single_field)
ASSERT_TRUE(num_fields == 1 || previous_line_has_single_field)
<< GetParam() << ": " << GetData();
previous_line_has_single_field = line_it->size() == 1;
previous_line_has_single_field = num_fields == 1;
}
}
// Verifies that a street line is surrounded by either newlines or spaces. A
// different format will result in incorrect behavior in
// AddressData::BuildDisplayLines().
TEST_P(RuleParseTest, StreetAddressSurroundingElements) {
ASSERT_TRUE(rule_.ParseSerializedRule(GetData()));
for (std::vector<std::vector<FormatElement> >::const_iterator
line_it = rule_.GetFormat().begin();
line_it != rule_.GetFormat().end();
++line_it) {
for (size_t i = 0; i < line_it->size(); ++i) {
const FormatElement& element = line_it->at(i);
if (element.IsField() && element.field == STREET_ADDRESS) {
bool surrounded_by_newlines = line_it->size() == 1;
bool surrounded_by_spaces =
i > 0 &&
i < line_it->size() - 1 &&
!line_it->at(i - 1).IsField() &&
line_it->at(i - 1).literal == " " &&
!line_it->at(i + 1).IsField() &&
line_it->at(i + 1).literal == " ";
EXPECT_TRUE(surrounded_by_newlines || surrounded_by_spaces)
<< GetParam() << ": " << GetData();
}
}
}
}
......
......@@ -118,6 +118,7 @@
'sources': [
'chromium/chrome_downloader_impl_unittest.cc',
'chromium/chrome_storage_impl_unittest.cc',
'<(libaddressinput_dir)/cpp/test/address_data_test.cc',
'<(libaddressinput_dir)/cpp/test/address_ui_test.cc',
'<(libaddressinput_dir)/cpp/test/fake_downloader.cc',
'<(libaddressinput_dir)/cpp/test/fake_downloader.h',
......
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