OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2009 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/browser/extensions/extension_message_bundle.h" |
| 6 |
| 7 #include <string> |
| 8 |
| 9 #include "base/hash_tables.h" |
| 10 #include "base/scoped_ptr.h" |
| 11 #include "base/string_util.h" |
| 12 #include "base/values.h" |
| 13 |
| 14 const wchar_t* ExtensionMessageBundle::kContentKey = L"content"; |
| 15 const wchar_t* ExtensionMessageBundle::kMessageKey = L"message"; |
| 16 const wchar_t* ExtensionMessageBundle::kPlaceholdersKey = L"placeholders"; |
| 17 |
| 18 const char* ExtensionMessageBundle::kPlaceholderBegin = "$"; |
| 19 const char* ExtensionMessageBundle::kPlaceholderEnd = "$"; |
| 20 const char* ExtensionMessageBundle::kMessageBegin = "__MSG_"; |
| 21 const char* ExtensionMessageBundle::kMessageEnd = "__"; |
| 22 |
| 23 const char* ExtensionMessageBundle::kExtensionName = "chrome_extension_name"; |
| 24 const char* ExtensionMessageBundle::kExtensionDescription = |
| 25 "chrome_extension_description"; |
| 26 |
| 27 // Formats message in case we encounter a bad formed key in the JSON object. |
| 28 // Returns false and sets |error| to actual error message. |
| 29 static bool BadKeyMessage(const std::string& name, std::string* error) { |
| 30 *error = StringPrintf("Name of a key \"%s\" is invalid. Only ASCII [a-z], " |
| 31 "[A-Z], [0-9] and \"_\" are allowed.", name.c_str()); |
| 32 return false; |
| 33 } |
| 34 |
| 35 // static |
| 36 ExtensionMessageBundle* ExtensionMessageBundle::Create( |
| 37 const DictionaryValue& default_locale_catalog, |
| 38 const DictionaryValue& current_locale_catalog, |
| 39 std::string* error) { |
| 40 scoped_ptr<ExtensionMessageBundle> message_bundle( |
| 41 new ExtensionMessageBundle); |
| 42 if (!message_bundle->Init(default_locale_catalog, |
| 43 current_locale_catalog, |
| 44 error)) |
| 45 return NULL; |
| 46 |
| 47 return message_bundle.release(); |
| 48 } |
| 49 |
| 50 bool ExtensionMessageBundle::Init(const DictionaryValue& default_locale_catalog, |
| 51 const DictionaryValue& current_locale_catalog, |
| 52 std::string* error) { |
| 53 dictionary_.clear(); |
| 54 |
| 55 // Create a single dictionary out of default and current_locale catalogs. |
| 56 // If message is missing from current_locale catalog, we take one from default |
| 57 // catalog. |
| 58 DictionaryValue::key_iterator key_it = current_locale_catalog.begin_keys(); |
| 59 for (; key_it != current_locale_catalog.end_keys(); ++key_it) { |
| 60 std::string key(StringToLowerASCII(WideToUTF8(*key_it))); |
| 61 if (!IsValidName(*key_it)) |
| 62 return BadKeyMessage(key, error); |
| 63 std::string value; |
| 64 if (!GetMessageValue(*key_it, current_locale_catalog, &value, error)) |
| 65 return false; |
| 66 // Keys are not case-sensitive. |
| 67 dictionary_[key] = value; |
| 68 } |
| 69 |
| 70 key_it = default_locale_catalog.begin_keys(); |
| 71 for (; key_it != default_locale_catalog.end_keys(); ++key_it) { |
| 72 std::string key(StringToLowerASCII(WideToUTF8(*key_it))); |
| 73 if (!IsValidName(*key_it)) |
| 74 return BadKeyMessage(key, error); |
| 75 // Add only messages that are not provided by app_catalog. |
| 76 if (dictionary_.find(key) != dictionary_.end()) |
| 77 continue; |
| 78 std::string value; |
| 79 if (!GetMessageValue(*key_it, default_locale_catalog, &value, error)) |
| 80 return false; |
| 81 // Keys are not case-sensitive. |
| 82 dictionary_[key] = value; |
| 83 } |
| 84 |
| 85 return true; |
| 86 } |
| 87 |
| 88 bool ExtensionMessageBundle::GetMessageValue(const std::wstring& wkey, |
| 89 const DictionaryValue& catalog, |
| 90 std::string* value, |
| 91 std::string* error) const { |
| 92 std::string key(WideToUTF8(wkey)); |
| 93 // Get the top level tree for given key (name part). |
| 94 DictionaryValue* name_tree; |
| 95 if (!catalog.GetDictionary(wkey, &name_tree)) { |
| 96 *error = StringPrintf("Not a valid tree for key %s.", key.c_str()); |
| 97 return false; |
| 98 } |
| 99 // Extract message from it. |
| 100 if (!name_tree->GetString(kMessageKey, value)) { |
| 101 *error = StringPrintf("There is no \"%s\" element for key %s.", |
| 102 WideToUTF8(kMessageKey).c_str(), |
| 103 key.c_str()); |
| 104 return false; |
| 105 } |
| 106 |
| 107 SubstitutionMap placeholders; |
| 108 if (!GetPlaceholders(*name_tree, key, &placeholders, error)) |
| 109 return false; |
| 110 |
| 111 if (!ReplacePlaceholders(placeholders, value, error)) |
| 112 return false; |
| 113 |
| 114 return true; |
| 115 } |
| 116 |
| 117 ExtensionMessageBundle::ExtensionMessageBundle() { |
| 118 } |
| 119 |
| 120 bool ExtensionMessageBundle::GetPlaceholders(const DictionaryValue& name_tree, |
| 121 const std::string& name_key, |
| 122 SubstitutionMap* placeholders, |
| 123 std::string* error) const { |
| 124 if (!name_tree.HasKey(kPlaceholdersKey)) |
| 125 return true; |
| 126 |
| 127 DictionaryValue* placeholders_tree; |
| 128 if (!name_tree.GetDictionary(kPlaceholdersKey, &placeholders_tree)) { |
| 129 *error = StringPrintf("Not a valid \"%s\" element for key %s.", |
| 130 WideToUTF8(kPlaceholdersKey).c_str(), |
| 131 name_key.c_str()); |
| 132 return false; |
| 133 } |
| 134 |
| 135 for (DictionaryValue::key_iterator key_it = placeholders_tree->begin_keys(); |
| 136 key_it != placeholders_tree->end_keys(); |
| 137 ++key_it) { |
| 138 DictionaryValue* placeholder; |
| 139 std::string content_key = WideToUTF8(*key_it); |
| 140 if (!IsValidName(*key_it)) |
| 141 return BadKeyMessage(content_key, error); |
| 142 if (!placeholders_tree->GetDictionary(*key_it, &placeholder)) { |
| 143 *error = StringPrintf("Invalid placeholder %s for key %s", |
| 144 content_key.c_str(), |
| 145 name_key.c_str()); |
| 146 return false; |
| 147 } |
| 148 std::string content; |
| 149 if (!placeholder->GetString(kContentKey, &content)) { |
| 150 *error = StringPrintf("Invalid \"%s\" element for key %s.", |
| 151 WideToUTF8(kContentKey).c_str(), |
| 152 name_key.c_str()); |
| 153 return false; |
| 154 } |
| 155 (*placeholders)[StringToLowerASCII(content_key)] = content; |
| 156 } |
| 157 |
| 158 return true; |
| 159 } |
| 160 |
| 161 bool ExtensionMessageBundle::ReplacePlaceholders( |
| 162 const SubstitutionMap& placeholders, |
| 163 std::string* message, |
| 164 std::string* error) const { |
| 165 return ReplaceVariables(placeholders, |
| 166 kPlaceholderBegin, |
| 167 kPlaceholderEnd, |
| 168 message, |
| 169 error); |
| 170 } |
| 171 |
| 172 bool ExtensionMessageBundle::ReplaceMessages(std::string* text, |
| 173 std::string* error) const { |
| 174 return ReplaceVariables(dictionary_, kMessageBegin, kMessageEnd, text, error); |
| 175 } |
| 176 |
| 177 // static |
| 178 bool ExtensionMessageBundle::ReplaceVariables( |
| 179 const SubstitutionMap& variables, |
| 180 const std::string& var_begin_delimiter, |
| 181 const std::string& var_end_delimiter, |
| 182 std::string* message, |
| 183 std::string* error) { |
| 184 std::string::size_type beg_index = 0; |
| 185 const std::string::size_type var_begin_delimiter_size = |
| 186 var_begin_delimiter.size(); |
| 187 while (true) { |
| 188 beg_index = message->find(var_begin_delimiter, beg_index); |
| 189 if (beg_index == message->npos) |
| 190 return true; |
| 191 |
| 192 // Advance it immediately to the begining of possible variable name. |
| 193 beg_index += var_begin_delimiter_size; |
| 194 if (beg_index >= message->size()) |
| 195 return true; |
| 196 std::string::size_type end_index = |
| 197 message->find(var_end_delimiter, beg_index); |
| 198 if (end_index == message->npos) |
| 199 return true; |
| 200 |
| 201 // Looking for 1 in substring of ...$1$.... |
| 202 const std::string& var_name = |
| 203 message->substr(beg_index, end_index - beg_index); |
| 204 if (!IsValidName(var_name)) |
| 205 continue; |
| 206 SubstitutionMap::const_iterator it = |
| 207 variables.find(StringToLowerASCII(var_name)); |
| 208 if (it == variables.end()) { |
| 209 *error = StringPrintf("Variable %s%s%s used but not defined.", |
| 210 var_begin_delimiter.c_str(), |
| 211 var_name.c_str(), |
| 212 var_end_delimiter.c_str()); |
| 213 return false; |
| 214 } |
| 215 |
| 216 // Replace variable with its value. |
| 217 std::string value = it->second; |
| 218 message->replace(beg_index - var_begin_delimiter_size, |
| 219 end_index - beg_index + var_begin_delimiter_size + |
| 220 var_end_delimiter.size(), |
| 221 value); |
| 222 |
| 223 // And position pointer to after the replacement. |
| 224 beg_index += value.size() - var_begin_delimiter_size; |
| 225 } |
| 226 |
| 227 return true; |
| 228 } |
| 229 |
| 230 // static |
| 231 template <typename str> |
| 232 bool ExtensionMessageBundle::IsValidName(const str& name) { |
| 233 if (name.empty()) |
| 234 return false; |
| 235 |
| 236 for (str::const_iterator it = name.begin(); it != name.end(); ++it) { |
| 237 // Allow only ascii 0-9, a-z, A-Z, and _ in the name. |
| 238 if (!IsAsciiAlpha(*it) && !IsAsciiDigit(*it) && *it != '_') |
| 239 return false; |
| 240 } |
| 241 |
| 242 return true; |
| 243 } |
| 244 |
| 245 // Dictionary interface. |
| 246 |
| 247 std::string ExtensionMessageBundle::GetL10nMessage( |
| 248 const std::string& name) const { |
| 249 SubstitutionMap::const_iterator it = |
| 250 dictionary_.find(StringToLowerASCII(name)); |
| 251 if (it != dictionary_.end()) { |
| 252 return it->second; |
| 253 } |
| 254 |
| 255 return ""; |
| 256 } |
OLD | NEW |