1// Copyright (C) 2013 Google Inc.
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#include "rule.h"
16
17#include <cassert>
18#include <cstddef>
19#include <map>
20#include <string>
21#include <utility>
22
23#include <re2/re2.h>
24
25#include "address_field_util.h"
26#include "format_element.h"
27#include "grit.h"
28#include "messages.h"
29#include "region_data_constants.h"
30#include "util/json.h"
31#include "util/re2ptr.h"
32#include "util/string_split.h"
33
34namespace i18n {
35namespace addressinput {
36
37namespace {
38
39typedef std::map<std::string, int> NameMessageIdMap;
40
41// Used as a separator in a list of items. For example, the list of supported
42// languages can be "de~fr~it".
43const char kSeparator = '~';
44
45NameMessageIdMap InitAdminAreaMessageIds() {
46  NameMessageIdMap message_ids;
47  message_ids.insert(std::make_pair(
48      "area", IDS_LIBADDRESSINPUT_AREA));
49  message_ids.insert(std::make_pair(
50      "county", IDS_LIBADDRESSINPUT_COUNTY));
51  message_ids.insert(std::make_pair(
52      "department", IDS_LIBADDRESSINPUT_DEPARTMENT));
53  message_ids.insert(std::make_pair(
54      "district", IDS_LIBADDRESSINPUT_DISTRICT));
55  message_ids.insert(std::make_pair(
56      "do_si", IDS_LIBADDRESSINPUT_DO_SI));
57  message_ids.insert(std::make_pair(
58      "emirate", IDS_LIBADDRESSINPUT_EMIRATE));
59  message_ids.insert(std::make_pair(
60      "island", IDS_LIBADDRESSINPUT_ISLAND));
61  message_ids.insert(std::make_pair(
62      "oblast", IDS_LIBADDRESSINPUT_OBLAST));
63  message_ids.insert(std::make_pair(
64      "parish", IDS_LIBADDRESSINPUT_PARISH));
65  message_ids.insert(std::make_pair(
66      "prefecture", IDS_LIBADDRESSINPUT_PREFECTURE));
67  message_ids.insert(std::make_pair(
68      "province", IDS_LIBADDRESSINPUT_PROVINCE));
69  message_ids.insert(std::make_pair(
70      "state", IDS_LIBADDRESSINPUT_STATE));
71  return message_ids;
72}
73
74const NameMessageIdMap& GetAdminAreaMessageIds() {
75  static const NameMessageIdMap kAdminAreaMessageIds(InitAdminAreaMessageIds());
76  return kAdminAreaMessageIds;
77}
78
79NameMessageIdMap InitPostalCodeMessageIds() {
80  NameMessageIdMap message_ids;
81  message_ids.insert(std::make_pair(
82      "postal", IDS_LIBADDRESSINPUT_POSTAL_CODE_LABEL));
83  message_ids.insert(std::make_pair(
84      "zip", IDS_LIBADDRESSINPUT_ZIP_CODE_LABEL));
85  return message_ids;
86}
87
88const NameMessageIdMap& GetPostalCodeMessageIds() {
89  static const NameMessageIdMap kPostalCodeMessageIds(
90      InitPostalCodeMessageIds());
91  return kPostalCodeMessageIds;
92}
93
94int GetMessageIdFromName(const std::string& name,
95                         const NameMessageIdMap& message_ids) {
96  NameMessageIdMap::const_iterator it = message_ids.find(name);
97  return it != message_ids.end() ? it->second : INVALID_MESSAGE_ID;
98}
99
100// Determines whether a given string is a reg-exp or a string. We consider a
101// string to be anything that doesn't contain characters with special meanings
102// in regular expressions - (, [, \, {, ?. These special characters are all the
103// ones that appear in the postal code regular expressions.
104bool ContainsRegExSpecialCharacters(const std::string& input) {
105  return input.find_first_of("([\\{?") != std::string::npos;
106}
107
108}  // namespace
109
110Rule::Rule()
111    : id_(),
112      format_(),
113      latin_format_(),
114      required_(),
115      sub_keys_(),
116      languages_(),
117      postal_code_matcher_(NULL),
118      sole_postal_code_(),
119      admin_area_name_message_id_(INVALID_MESSAGE_ID),
120      postal_code_name_message_id_(INVALID_MESSAGE_ID),
121      name_(),
122      latin_name_(),
123      postal_code_example_(),
124      post_service_url_() {}
125
126Rule::~Rule() {}
127
128// static
129const Rule& Rule::GetDefault() {
130  // Allocated once and leaked on shutdown.
131  static Rule* default_rule = NULL;
132  if (default_rule == NULL) {
133    default_rule = new Rule;
134    default_rule->ParseSerializedRule(
135        RegionDataConstants::GetDefaultRegionData());
136  }
137  return *default_rule;
138}
139
140void Rule::CopyFrom(const Rule& rule) {
141  assert(this != &rule);
142  id_ = rule.id_;
143  format_ = rule.format_;
144  latin_format_ = rule.latin_format_;
145  required_ = rule.required_;
146  sub_keys_ = rule.sub_keys_;
147  languages_ = rule.languages_;
148  postal_code_matcher_.reset(
149      rule.postal_code_matcher_ == NULL
150          ? NULL
151          : new RE2ptr(new RE2(rule.postal_code_matcher_->ptr->pattern(),
152                               rule.postal_code_matcher_->ptr->options())));
153  sole_postal_code_ = rule.sole_postal_code_;
154  admin_area_name_message_id_ = rule.admin_area_name_message_id_;
155  postal_code_name_message_id_ = rule.postal_code_name_message_id_;
156  name_ = rule.name_;
157  latin_name_ = rule.latin_name_;
158  postal_code_example_ = rule.postal_code_example_;
159  post_service_url_ = rule.post_service_url_;
160}
161
162bool Rule::ParseSerializedRule(const std::string& serialized_rule) {
163  Json json;
164  if (!json.ParseObject(serialized_rule)) {
165    return false;
166  }
167  ParseJsonRule(json);
168  return true;
169}
170
171void Rule::ParseJsonRule(const Json& json) {
172  std::string value;
173  if (json.GetStringValueForKey("id", &value)) {
174    id_.swap(value);
175  }
176
177  if (json.GetStringValueForKey("fmt", &value)) {
178    ParseFormatRule(value, &format_);
179  }
180
181  if (json.GetStringValueForKey("lfmt", &value)) {
182    ParseFormatRule(value, &latin_format_);
183  }
184
185  if (json.GetStringValueForKey("require", &value)) {
186    ParseAddressFieldsRequired(value, &required_);
187  }
188
189  if (json.GetStringValueForKey("sub_keys", &value)) {
190    SplitString(value, kSeparator, &sub_keys_);
191  }
192
193  if (json.GetStringValueForKey("languages", &value)) {
194    SplitString(value, kSeparator, &languages_);
195  }
196
197  sole_postal_code_.clear();
198  if (json.GetStringValueForKey("zip", &value)) {
199    // The "zip" field in the JSON data is used in two different ways to
200    // validate the postal code. At the country level, the "zip" field indicates
201    // a Java compatible regular expression corresponding to all postal codes in
202    // the country. At other levels, the regular expression indicates the postal
203    // code prefix expected for addresses in that region.
204    //
205    // In order to make the RE2 object created from the "zip" field useable for
206    // both these purposes, the pattern string is here prefixed with "^" to
207    // anchor it at the beginning of the string so that it can be used with
208    // RE2::PartialMatch() to perform prefix matching or else with
209    // RE2::FullMatch() to perform matching against the entire string.
210    RE2::Options options;
211    options.set_never_capture(true);
212    RE2* matcher = new RE2("^(" + value + ")", options);
213    if (matcher->ok()) {
214      postal_code_matcher_.reset(new RE2ptr(matcher));
215    } else {
216      postal_code_matcher_.reset(NULL);
217      delete matcher;
218    }
219    // If the "zip" field is not a regular expression, then it is the sole
220    // postal code for this rule.
221    if (!ContainsRegExSpecialCharacters(value)) {
222      sole_postal_code_.swap(value);
223    }
224  }
225
226  if (json.GetStringValueForKey("state_name_type", &value)) {
227    admin_area_name_message_id_ =
228        GetMessageIdFromName(value, GetAdminAreaMessageIds());
229  }
230
231  if (json.GetStringValueForKey("zip_name_type", &value)) {
232    postal_code_name_message_id_ =
233        GetMessageIdFromName(value, GetPostalCodeMessageIds());
234  }
235
236  if (json.GetStringValueForKey("name", &value)) {
237    name_.swap(value);
238  }
239
240  if (json.GetStringValueForKey("lname", &value)) {
241    latin_name_.swap(value);
242  }
243
244  if (json.GetStringValueForKey("zipex", &value)) {
245    postal_code_example_.swap(value);
246  }
247
248  if (json.GetStringValueForKey("posturl", &value)) {
249    post_service_url_.swap(value);
250  }
251}
252
253}  // namespace addressinput
254}  // namespace i18n
255