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.
5 #include "chrome/common/extensions/message_bundle.h"
10 #include "base/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/stringprintf.h"
17 #include "base/utf_string_conversions.h"
18 #include "base/values.h"
19 #include "chrome/common/extensions/extension_manifest_constants.h"
20 #include "chrome/common/extensions/extension_error_utils.h"
21 #include "chrome/common/extensions/extension_l10n_util.h"
22 #include "ui/base/l10n/l10n_util.h"
24 namespace errors
= extension_manifest_errors
;
26 namespace extensions
{
28 const char* MessageBundle::kContentKey
= "content";
29 const char* MessageBundle::kMessageKey
= "message";
30 const char* MessageBundle::kPlaceholdersKey
= "placeholders";
32 const char* MessageBundle::kPlaceholderBegin
= "$";
33 const char* MessageBundle::kPlaceholderEnd
= "$";
34 const char* MessageBundle::kMessageBegin
= "__MSG_";
35 const char* MessageBundle::kMessageEnd
= "__";
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";
46 // Reserved messages values.
47 const char* MessageBundle::kBidiLeftEdgeValue
= "left";
48 const char* MessageBundle::kBidiRightEdgeValue
= "right";
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.",
61 MessageBundle
* MessageBundle::Create(const CatalogVector
& locale_catalogs
,
63 scoped_ptr
<MessageBundle
> message_bundle(new MessageBundle
);
64 if (!message_bundle
->Init(locale_catalogs
, error
))
67 return message_bundle
.release();
70 bool MessageBundle::Init(const CatalogVector
& locale_catalogs
,
74 for (CatalogVector::const_reverse_iterator it
= locale_catalogs
.rbegin();
75 it
!= locale_catalogs
.rend(); ++it
) {
76 DictionaryValue
* catalog
= (*it
).get();
77 for (DictionaryValue::key_iterator key_it
= catalog
->begin_keys();
78 key_it
!= catalog
->end_keys(); ++key_it
) {
79 std::string
key(StringToLowerASCII(*key_it
));
80 if (!IsValidName(*key_it
))
81 return BadKeyMessage(key
, error
);
83 if (!GetMessageValue(*key_it
, *catalog
, &value
, error
))
85 // Keys are not case-sensitive.
86 dictionary_
[key
] = value
;
90 if (!AppendReservedMessagesForLocale(
91 extension_l10n_util::CurrentLocaleOrDefault(), error
))
97 bool MessageBundle::AppendReservedMessagesForLocale(
98 const std::string
& app_locale
, std::string
* error
) {
99 SubstitutionMap append_messages
;
100 append_messages
[kUILocaleKey
] = app_locale
;
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
;
111 append_messages
[kBidiDirectionKey
] = "ltr";
112 append_messages
[kBidiReversedDirectionKey
] = "rtl";
113 append_messages
[kBidiStartEdgeKey
] = kBidiLeftEdgeValue
;
114 append_messages
[kBidiEndEdgeKey
] = kBidiRightEdgeValue
;
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
= ExtensionErrorUtils::FormatErrorMessage(
122 errors::kReservedMessageFound
, it
->first
);
125 dictionary_
[it
->first
] = it
->second
;
132 bool MessageBundle::GetMessageValue(const std::string
& key
,
133 const DictionaryValue
& catalog
,
135 std::string
* error
) const {
136 // Get the top level tree for given key (name part).
137 const DictionaryValue
* name_tree
;
138 if (!catalog
.GetDictionaryWithoutPathExpansion(key
, &name_tree
)) {
139 *error
= base::StringPrintf("Not a valid tree for key %s.", key
.c_str());
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());
149 SubstitutionMap placeholders
;
150 if (!GetPlaceholders(*name_tree
, key
, &placeholders
, error
))
153 if (!ReplacePlaceholders(placeholders
, value
, error
))
159 MessageBundle::MessageBundle() {
162 bool MessageBundle::GetPlaceholders(const DictionaryValue
& name_tree
,
163 const std::string
& name_key
,
164 SubstitutionMap
* placeholders
,
165 std::string
* error
) const {
166 if (!name_tree
.HasKey(kPlaceholdersKey
))
169 const 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());
176 for (DictionaryValue::key_iterator key_it
= placeholders_tree
->begin_keys();
177 key_it
!= placeholders_tree
->end_keys(); ++key_it
) {
178 const DictionaryValue
* placeholder
;
179 const std::string
& content_key(*key_it
);
180 if (!IsValidName(content_key
))
181 return BadKeyMessage(content_key
, error
);
182 if (!placeholders_tree
->GetDictionaryWithoutPathExpansion(content_key
,
184 *error
= base::StringPrintf("Invalid placeholder %s for key %s",
190 if (!placeholder
->GetString(kContentKey
, &content
)) {
191 *error
= base::StringPrintf("Invalid \"%s\" element for key %s.",
192 kContentKey
, name_key
.c_str());
195 (*placeholders
)[StringToLowerASCII(content_key
)] = content
;
201 bool MessageBundle::ReplacePlaceholders(const SubstitutionMap
& placeholders
,
202 std::string
* message
,
203 std::string
* error
) const {
204 return ReplaceVariables(placeholders
,
211 bool MessageBundle::ReplaceMessages(std::string
* text
,
212 std::string
* error
) const {
213 return ReplaceMessagesWithExternalDictionary(dictionary_
, text
, error
);
216 MessageBundle::~MessageBundle() {
220 bool MessageBundle::ReplaceMessagesWithExternalDictionary(
221 const SubstitutionMap
& dictionary
, std::string
* text
, std::string
* error
) {
222 return ReplaceVariables(dictionary
, kMessageBegin
, kMessageEnd
, text
, error
);
226 bool MessageBundle::ReplaceVariables(const SubstitutionMap
& variables
,
227 const std::string
& var_begin_delimiter
,
228 const std::string
& var_end_delimiter
,
229 std::string
* message
,
230 std::string
* error
) {
231 std::string::size_type beg_index
= 0;
232 const std::string::size_type var_begin_delimiter_size
=
233 var_begin_delimiter
.size();
235 beg_index
= message
->find(var_begin_delimiter
, beg_index
);
236 if (beg_index
== message
->npos
)
239 // Advance it immediately to the begining of possible variable name.
240 beg_index
+= var_begin_delimiter_size
;
241 if (beg_index
>= message
->size())
243 std::string::size_type end_index
=
244 message
->find(var_end_delimiter
, beg_index
);
245 if (end_index
== message
->npos
)
248 // Looking for 1 in substring of ...$1$....
249 const std::string
& var_name
=
250 message
->substr(beg_index
, end_index
- beg_index
);
251 if (!IsValidName(var_name
))
253 SubstitutionMap::const_iterator it
=
254 variables
.find(StringToLowerASCII(var_name
));
255 if (it
== variables
.end()) {
256 *error
= base::StringPrintf("Variable %s%s%s used but not defined.",
257 var_begin_delimiter
.c_str(),
259 var_end_delimiter
.c_str());
263 // Replace variable with its value.
264 std::string value
= it
->second
;
265 message
->replace(beg_index
- var_begin_delimiter_size
,
266 end_index
- beg_index
+ var_begin_delimiter_size
+
267 var_end_delimiter
.size(),
270 // And position pointer to after the replacement.
271 beg_index
+= value
.size() - var_begin_delimiter_size
;
278 bool MessageBundle::IsValidName(const std::string
& name
) {
282 std::string::const_iterator it
= name
.begin();
283 for (; it
!= name
.end(); ++it
) {
284 // Allow only ascii 0-9, a-z, A-Z, and _ in the name.
285 if (!IsAsciiAlpha(*it
) && !IsAsciiDigit(*it
) && *it
!= '_' && *it
!= '@')
292 // Dictionary interface.
294 std::string
MessageBundle::GetL10nMessage(const std::string
& name
) const {
295 return GetL10nMessage(name
, dictionary_
);
299 std::string
MessageBundle::GetL10nMessage(const std::string
& name
,
300 const SubstitutionMap
& dictionary
) {
301 SubstitutionMap::const_iterator it
=
302 dictionary
.find(StringToLowerASCII(name
));
303 if (it
!= dictionary
.end()) {
310 ///////////////////////////////////////////////////////////////////////////////
312 // Renderer helper functions.
314 ///////////////////////////////////////////////////////////////////////////////
316 // Unique class for Singleton.
317 struct ExtensionToMessagesMap
{
318 ExtensionToMessagesMap();
319 ~ExtensionToMessagesMap();
321 // Maps extension ID to message map.
322 ExtensionToL10nMessagesMap messages_map
;
325 static base::LazyInstance
<ExtensionToMessagesMap
> g_extension_to_messages_map
=
326 LAZY_INSTANCE_INITIALIZER
;
328 ExtensionToMessagesMap::ExtensionToMessagesMap() {}
330 ExtensionToMessagesMap::~ExtensionToMessagesMap() {}
332 ExtensionToL10nMessagesMap
* GetExtensionToL10nMessagesMap() {
333 return &g_extension_to_messages_map
.Get().messages_map
;
336 L10nMessagesMap
* GetL10nMessagesMap(const std::string
& extension_id
) {
337 ExtensionToL10nMessagesMap::iterator it
=
338 g_extension_to_messages_map
.Get().messages_map
.find(extension_id
);
339 if (it
!= g_extension_to_messages_map
.Get().messages_map
.end())
340 return &(it
->second
);
345 } // namespace extensions