OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chrome/common/extensions/message_bundle.h" | |
6 | |
7 #include <string> | |
8 #include <vector> | |
9 | |
10 #include "base/containers/hash_tables.h" | |
11 #include "base/i18n/rtl.h" | |
12 #include "base/lazy_instance.h" | |
13 #include "base/memory/linked_ptr.h" | |
14 #include "base/memory/scoped_ptr.h" | |
15 #include "base/stl_util.h" | |
16 #include "base/strings/stringprintf.h" | |
17 #include "base/strings/utf_string_conversions.h" | |
18 #include "base/values.h" | |
19 #include "chrome/common/extensions/extension_l10n_util.h" | |
20 #include "extensions/common/error_utils.h" | |
21 #include "extensions/common/manifest_constants.h" | |
22 #include "ui/base/l10n/l10n_util.h" | |
23 | |
24 namespace extensions { | |
25 | |
26 namespace errors = manifest_errors; | |
27 | |
28 const char* MessageBundle::kContentKey = "content"; | |
29 const char* MessageBundle::kMessageKey = "message"; | |
30 const char* MessageBundle::kPlaceholdersKey = "placeholders"; | |
31 | |
32 const char* MessageBundle::kPlaceholderBegin = "$"; | |
33 const char* MessageBundle::kPlaceholderEnd = "$"; | |
34 const char* MessageBundle::kMessageBegin = "__MSG_"; | |
35 const char* MessageBundle::kMessageEnd = "__"; | |
36 | |
37 // Reserved messages names. | |
38 const char* MessageBundle::kUILocaleKey = "@@ui_locale"; | |
39 const char* MessageBundle::kBidiDirectionKey = "@@bidi_dir"; | |
40 const char* MessageBundle::kBidiReversedDirectionKey = | |
41 "@@bidi_reversed_dir"; | |
42 const char* MessageBundle::kBidiStartEdgeKey = "@@bidi_start_edge"; | |
43 const char* MessageBundle::kBidiEndEdgeKey = "@@bidi_end_edge"; | |
44 const char* MessageBundle::kExtensionIdKey = "@@extension_id"; | |
45 | |
46 // Reserved messages values. | |
47 const char* MessageBundle::kBidiLeftEdgeValue = "left"; | |
48 const char* MessageBundle::kBidiRightEdgeValue = "right"; | |
49 | |
50 // Formats message in case we encounter a bad formed key in the JSON object. | |
51 // Returns false and sets |error| to actual error message. | |
52 static bool BadKeyMessage(const std::string& name, std::string* error) { | |
53 *error = base::StringPrintf( | |
54 "Name of a key \"%s\" is invalid. Only ASCII [a-z], " | |
55 "[A-Z], [0-9] and \"_\" are allowed.", | |
56 name.c_str()); | |
57 return false; | |
58 } | |
59 | |
60 // static | |
61 MessageBundle* MessageBundle::Create(const CatalogVector& locale_catalogs, | |
62 std::string* error) { | |
63 scoped_ptr<MessageBundle> message_bundle(new MessageBundle); | |
64 if (!message_bundle->Init(locale_catalogs, error)) | |
65 return NULL; | |
66 | |
67 return message_bundle.release(); | |
68 } | |
69 | |
70 bool MessageBundle::Init(const CatalogVector& locale_catalogs, | |
71 std::string* error) { | |
72 dictionary_.clear(); | |
73 | |
74 for (CatalogVector::const_reverse_iterator it = locale_catalogs.rbegin(); | |
75 it != locale_catalogs.rend(); ++it) { | |
76 base::DictionaryValue* catalog = (*it).get(); | |
77 for (base::DictionaryValue::Iterator message_it(*catalog); | |
78 !message_it.IsAtEnd(); message_it.Advance()) { | |
79 std::string key(StringToLowerASCII(message_it.key())); | |
80 if (!IsValidName(message_it.key())) | |
81 return BadKeyMessage(key, error); | |
82 std::string value; | |
83 if (!GetMessageValue(message_it.key(), message_it.value(), &value, error)) | |
84 return false; | |
85 // Keys are not case-sensitive. | |
86 dictionary_[key] = value; | |
87 } | |
88 } | |
89 | |
90 if (!AppendReservedMessagesForLocale( | |
91 extension_l10n_util::CurrentLocaleOrDefault(), error)) | |
92 return false; | |
93 | |
94 return true; | |
95 } | |
96 | |
97 bool MessageBundle::AppendReservedMessagesForLocale( | |
98 const std::string& app_locale, std::string* error) { | |
99 SubstitutionMap append_messages; | |
100 append_messages[kUILocaleKey] = app_locale; | |
101 | |
102 // Calling base::i18n::GetTextDirection on non-UI threads doesn't seems safe, | |
103 // so we use GetTextDirectionForLocale instead. | |
104 if (base::i18n::GetTextDirectionForLocale(app_locale.c_str()) == | |
105 base::i18n::RIGHT_TO_LEFT) { | |
106 append_messages[kBidiDirectionKey] = "rtl"; | |
107 append_messages[kBidiReversedDirectionKey] = "ltr"; | |
108 append_messages[kBidiStartEdgeKey] = kBidiRightEdgeValue; | |
109 append_messages[kBidiEndEdgeKey] = kBidiLeftEdgeValue; | |
110 } else { | |
111 append_messages[kBidiDirectionKey] = "ltr"; | |
112 append_messages[kBidiReversedDirectionKey] = "rtl"; | |
113 append_messages[kBidiStartEdgeKey] = kBidiLeftEdgeValue; | |
114 append_messages[kBidiEndEdgeKey] = kBidiRightEdgeValue; | |
115 } | |
116 | |
117 // Add all reserved messages to the dictionary, but check for collisions. | |
118 SubstitutionMap::iterator it = append_messages.begin(); | |
119 for (; it != append_messages.end(); ++it) { | |
120 if (ContainsKey(dictionary_, it->first)) { | |
121 *error = ErrorUtils::FormatErrorMessage( | |
122 errors::kReservedMessageFound, it->first); | |
123 return false; | |
124 } else { | |
125 dictionary_[it->first] = it->second; | |
126 } | |
127 } | |
128 | |
129 return true; | |
130 } | |
131 | |
132 bool MessageBundle::GetMessageValue(const std::string& key, | |
133 const base::Value& name_value, | |
134 std::string* value, | |
135 std::string* error) const { | |
136 // Get the top level tree for given key (name part). | |
137 const base::DictionaryValue* name_tree; | |
138 if (!name_value.GetAsDictionary(&name_tree)) { | |
139 *error = base::StringPrintf("Not a valid tree for key %s.", key.c_str()); | |
140 return false; | |
141 } | |
142 // Extract message from it. | |
143 if (!name_tree->GetString(kMessageKey, value)) { | |
144 *error = base::StringPrintf( | |
145 "There is no \"%s\" element for key %s.", kMessageKey, key.c_str()); | |
146 return false; | |
147 } | |
148 | |
149 SubstitutionMap placeholders; | |
150 if (!GetPlaceholders(*name_tree, key, &placeholders, error)) | |
151 return false; | |
152 | |
153 if (!ReplacePlaceholders(placeholders, value, error)) | |
154 return false; | |
155 | |
156 return true; | |
157 } | |
158 | |
159 MessageBundle::MessageBundle() { | |
160 } | |
161 | |
162 bool MessageBundle::GetPlaceholders(const base::DictionaryValue& name_tree, | |
163 const std::string& name_key, | |
164 SubstitutionMap* placeholders, | |
165 std::string* error) const { | |
166 if (!name_tree.HasKey(kPlaceholdersKey)) | |
167 return true; | |
168 | |
169 const base::DictionaryValue* placeholders_tree; | |
170 if (!name_tree.GetDictionary(kPlaceholdersKey, &placeholders_tree)) { | |
171 *error = base::StringPrintf("Not a valid \"%s\" element for key %s.", | |
172 kPlaceholdersKey, name_key.c_str()); | |
173 return false; | |
174 } | |
175 | |
176 for (base::DictionaryValue::Iterator it(*placeholders_tree); !it.IsAtEnd(); | |
177 it.Advance()) { | |
178 const base::DictionaryValue* placeholder; | |
179 const std::string& content_key(it.key()); | |
180 if (!IsValidName(content_key)) | |
181 return BadKeyMessage(content_key, error); | |
182 if (!it.value().GetAsDictionary(&placeholder)) { | |
183 *error = base::StringPrintf("Invalid placeholder %s for key %s", | |
184 content_key.c_str(), | |
185 name_key.c_str()); | |
186 return false; | |
187 } | |
188 std::string content; | |
189 if (!placeholder->GetString(kContentKey, &content)) { | |
190 *error = base::StringPrintf("Invalid \"%s\" element for key %s.", | |
191 kContentKey, name_key.c_str()); | |
192 return false; | |
193 } | |
194 (*placeholders)[StringToLowerASCII(content_key)] = content; | |
195 } | |
196 | |
197 return true; | |
198 } | |
199 | |
200 bool MessageBundle::ReplacePlaceholders(const SubstitutionMap& placeholders, | |
201 std::string* message, | |
202 std::string* error) const { | |
203 return ReplaceVariables(placeholders, | |
204 kPlaceholderBegin, | |
205 kPlaceholderEnd, | |
206 message, | |
207 error); | |
208 } | |
209 | |
210 bool MessageBundle::ReplaceMessages(std::string* text, | |
211 std::string* error) const { | |
212 return ReplaceMessagesWithExternalDictionary(dictionary_, text, error); | |
213 } | |
214 | |
215 MessageBundle::~MessageBundle() { | |
216 } | |
217 | |
218 // static | |
219 bool MessageBundle::ReplaceMessagesWithExternalDictionary( | |
220 const SubstitutionMap& dictionary, std::string* text, std::string* error) { | |
221 return ReplaceVariables(dictionary, kMessageBegin, kMessageEnd, text, error); | |
222 } | |
223 | |
224 // static | |
225 bool MessageBundle::ReplaceVariables(const SubstitutionMap& variables, | |
226 const std::string& var_begin_delimiter, | |
227 const std::string& var_end_delimiter, | |
228 std::string* message, | |
229 std::string* error) { | |
230 std::string::size_type beg_index = 0; | |
231 const std::string::size_type var_begin_delimiter_size = | |
232 var_begin_delimiter.size(); | |
233 while (true) { | |
234 beg_index = message->find(var_begin_delimiter, beg_index); | |
235 if (beg_index == message->npos) | |
236 return true; | |
237 | |
238 // Advance it immediately to the begining of possible variable name. | |
239 beg_index += var_begin_delimiter_size; | |
240 if (beg_index >= message->size()) | |
241 return true; | |
242 std::string::size_type end_index = | |
243 message->find(var_end_delimiter, beg_index); | |
244 if (end_index == message->npos) | |
245 return true; | |
246 | |
247 // Looking for 1 in substring of ...$1$.... | |
248 const std::string& var_name = | |
249 message->substr(beg_index, end_index - beg_index); | |
250 if (!IsValidName(var_name)) | |
251 continue; | |
252 SubstitutionMap::const_iterator it = | |
253 variables.find(StringToLowerASCII(var_name)); | |
254 if (it == variables.end()) { | |
255 *error = base::StringPrintf("Variable %s%s%s used but not defined.", | |
256 var_begin_delimiter.c_str(), | |
257 var_name.c_str(), | |
258 var_end_delimiter.c_str()); | |
259 return false; | |
260 } | |
261 | |
262 // Replace variable with its value. | |
263 std::string value = it->second; | |
264 message->replace(beg_index - var_begin_delimiter_size, | |
265 end_index - beg_index + var_begin_delimiter_size + | |
266 var_end_delimiter.size(), | |
267 value); | |
268 | |
269 // And position pointer to after the replacement. | |
270 beg_index += value.size() - var_begin_delimiter_size; | |
271 } | |
272 | |
273 return true; | |
274 } | |
275 | |
276 // static | |
277 bool MessageBundle::IsValidName(const std::string& name) { | |
278 if (name.empty()) | |
279 return false; | |
280 | |
281 std::string::const_iterator it = name.begin(); | |
282 for (; it != name.end(); ++it) { | |
283 // Allow only ascii 0-9, a-z, A-Z, and _ in the name. | |
284 if (!IsAsciiAlpha(*it) && !IsAsciiDigit(*it) && *it != '_' && *it != '@') | |
285 return false; | |
286 } | |
287 | |
288 return true; | |
289 } | |
290 | |
291 // Dictionary interface. | |
292 | |
293 std::string MessageBundle::GetL10nMessage(const std::string& name) const { | |
294 return GetL10nMessage(name, dictionary_); | |
295 } | |
296 | |
297 // static | |
298 std::string MessageBundle::GetL10nMessage(const std::string& name, | |
299 const SubstitutionMap& dictionary) { | |
300 SubstitutionMap::const_iterator it = | |
301 dictionary.find(StringToLowerASCII(name)); | |
302 if (it != dictionary.end()) { | |
303 return it->second; | |
304 } | |
305 | |
306 return std::string(); | |
307 } | |
308 | |
309 /////////////////////////////////////////////////////////////////////////////// | |
310 // | |
311 // Renderer helper functions. | |
312 // | |
313 /////////////////////////////////////////////////////////////////////////////// | |
314 | |
315 // Unique class for Singleton. | |
316 struct ExtensionToMessagesMap { | |
317 ExtensionToMessagesMap(); | |
318 ~ExtensionToMessagesMap(); | |
319 | |
320 // Maps extension ID to message map. | |
321 ExtensionToL10nMessagesMap messages_map; | |
322 }; | |
323 | |
324 static base::LazyInstance<ExtensionToMessagesMap> g_extension_to_messages_map = | |
325 LAZY_INSTANCE_INITIALIZER; | |
326 | |
327 ExtensionToMessagesMap::ExtensionToMessagesMap() {} | |
328 | |
329 ExtensionToMessagesMap::~ExtensionToMessagesMap() {} | |
330 | |
331 ExtensionToL10nMessagesMap* GetExtensionToL10nMessagesMap() { | |
332 return &g_extension_to_messages_map.Get().messages_map; | |
333 } | |
334 | |
335 L10nMessagesMap* GetL10nMessagesMap(const std::string& extension_id) { | |
336 ExtensionToL10nMessagesMap::iterator it = | |
337 g_extension_to_messages_map.Get().messages_map.find(extension_id); | |
338 if (it != g_extension_to_messages_map.Get().messages_map.end()) | |
339 return &(it->second); | |
340 | |
341 return NULL; | |
342 } | |
343 | |
344 void EraseL10nMessagesMap(const std::string& extension_id) { | |
345 g_extension_to_messages_map.Get().messages_map.erase(extension_id); | |
346 } | |
347 | |
348 } // namespace extensions | |
OLD | NEW |