OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/python2.4 |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 '''Support for formatting an RC file for compilation. |
| 7 ''' |
| 8 |
| 9 import os |
| 10 import types |
| 11 import re |
| 12 |
| 13 from grit import util |
| 14 from grit.format import interface |
| 15 |
| 16 # Matches all different types of linebreaks. |
| 17 _LINEBREAKS = re.compile('\r\n|\n|\r') |
| 18 |
| 19 ''' |
| 20 This dictionary defines the langauge charset pair lookup table, which is used |
| 21 for replacing the GRIT expand variables for language info in Product Version |
| 22 resource. The key is the language ISO country code, and the value |
| 23 is the language and character-set pair, which is a hexadecimal string |
| 24 consisting of the concatenation of the language and character-set identifiers. |
| 25 The first 4 digit of the value is the hex value of LCID, the remaining |
| 26 4 digits is the hex value of character-set id(code page)of the language. |
| 27 |
| 28 We have defined three GRIT expand_variables to be used in the version resource |
| 29 file to set the language info. Here is an example how they should be used in |
| 30 the VS_VERSION_INFO section of the resource file to allow GRIT to localize |
| 31 the language info correctly according to product locale. |
| 32 |
| 33 VS_VERSION_INFO VERSIONINFO |
| 34 ... |
| 35 BEGIN |
| 36 BLOCK "StringFileInfo" |
| 37 BEGIN |
| 38 BLOCK "[GRITVERLANGCHARSETHEX]" |
| 39 BEGIN |
| 40 ... |
| 41 END |
| 42 END |
| 43 BLOCK "VarFileInfo" |
| 44 BEGIN |
| 45 VALUE "Translation", [GRITVERLANGID], [GRITVERCHARSETID] |
| 46 END |
| 47 END |
| 48 |
| 49 ''' |
| 50 |
| 51 _LANGUAGE_CHARSET_PAIR = { |
| 52 'ar' : '040104e8', |
| 53 'fi' : '040b04e4', |
| 54 'ko' : '041203b5', |
| 55 'es' : '040a04e4', |
| 56 'bg' : '040204e3', |
| 57 'fr' : '040c04e4', |
| 58 'lv' : '042604e9', |
| 59 'sv' : '041d04e4', |
| 60 'ca' : '040304e4', |
| 61 'de' : '040704e4', |
| 62 'lt' : '042704e9', |
| 63 # no lcid for tl(Tagalog), use default custom locale |
| 64 'tl' : '0c0004b0', |
| 65 'zh-CN' : '080403a8', |
| 66 'el' : '040804e5', |
| 67 'no' : '041404e4', |
| 68 'th' : '041e036a', |
| 69 'zh-TW' : '040403b6', |
| 70 'iw' : '040d04e7', |
| 71 'pl' : '041504e2', |
| 72 'tr' : '041f04e6', |
| 73 'hr' : '041a04e4', |
| 74 # no codepage for hindi, use unicode(1200) |
| 75 'hi' : '043904b0', |
| 76 'pt-BR' : '041604e4', |
| 77 'uk' : '042204e3', |
| 78 'cs' : '040504e2', |
| 79 'hu' : '040e04e2', |
| 80 'ro' : '041804e2', |
| 81 # no codepage for urdu, use unicode(1200) |
| 82 'ur' : '042004b0', |
| 83 'da' : '040604e4', |
| 84 'is' : '040f04e4', |
| 85 'ru' : '041904e3', |
| 86 'vi' : '042a04ea', |
| 87 'nl' : '041304e4', |
| 88 'id' : '042104e4', |
| 89 'sr' : '081a04e2', |
| 90 'en-GB' : '0809040e', |
| 91 'it' : '041004e4', |
| 92 'sk' : '041b04e2', |
| 93 'et' : '042504e9', |
| 94 'ja' : '041103a4', |
| 95 'sl' : '042404e2', |
| 96 'en' : '040904b0', |
| 97 'fake_bidi' : '040d04e7', |
| 98 } |
| 99 |
| 100 _LANGUAGE_DIRECTIVE_PAIR = { |
| 101 'ar' : 'LANG_ARABIC, SUBLANG_DEFAULT', |
| 102 'fi' : 'LANG_FINNISH, SUBLANG_DEFAULT', |
| 103 'ko' : 'LANG_KOREAN, SUBLANG_KOREAN', |
| 104 'es' : 'LANG_SPANISH, SUBLANG_SPANISH_MODERN', |
| 105 'bg' : 'LANG_BULGARIAN, SUBLANG_DEFAULT', |
| 106 'fr' : 'LANG_FRENCH, SUBLANG_FRENCH', |
| 107 'lv' : 'LANG_LATVIAN, SUBLANG_DEFAULT', |
| 108 'sv' : 'LANG_SWEDISH, SUBLANG_SWEDISH', |
| 109 'ca' : 'LANG_CATALAN, SUBLANG_DEFAULT', |
| 110 'de' : 'LANG_GERMAN, SUBLANG_GERMAN', |
| 111 'lt' : 'LANG_LITHUANIAN, SUBLANG_LITHUANIAN', |
| 112 'tl' : 'LANG_NEUTRAL, SUBLANG_DEFAULT', |
| 113 'zh-CN' : 'LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED', |
| 114 'el' : 'LANG_GREEK, SUBLANG_DEFAULT', |
| 115 'no' : 'LANG_NORWEGIAN, SUBLANG_DEFAULT', |
| 116 'th' : 'LANG_THAI, SUBLANG_DEFAULT', |
| 117 'zh-TW' : 'LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL', |
| 118 'iw' : 'LANG_HEBREW, SUBLANG_DEFAULT', |
| 119 'pl' : 'LANG_POLISH, SUBLANG_DEFAULT', |
| 120 'tr' : 'LANG_TURKISH, SUBLANG_DEFAULT', |
| 121 'hr' : 'LANG_CROATIAN, SUBLANG_DEFAULT', |
| 122 'hi' : 'LANG_HINDI, SUBLANG_DEFAULT', |
| 123 'pt-BR' : 'LANG_PORTUGUESE, SUBLANG_DEFAULT', |
| 124 'uk' : 'LANG_UKRAINIAN, SUBLANG_DEFAULT', |
| 125 'cs' : 'LANG_CZECH, SUBLANG_DEFAULT', |
| 126 'hu' : 'LANG_HUNGARIAN, SUBLANG_DEFAULT', |
| 127 'ro' : 'LANG_ROMANIAN, SUBLANG_DEFAULT', |
| 128 'ur' : 'LANG_URDU, SUBLANG_DEFAULT', |
| 129 'da' : 'LANG_DANISH, SUBLANG_DEFAULT', |
| 130 'is' : 'LANG_ICELANDIC, SUBLANG_DEFAULT', |
| 131 'ru' : 'LANG_RUSSIAN, SUBLANG_DEFAULT', |
| 132 'vi' : 'LANG_VIETNAMESE, SUBLANG_DEFAULT', |
| 133 'nl' : 'LANG_DUTCH, SUBLANG_DEFAULT', |
| 134 'id' : 'LANG_INDONESIAN, SUBLANG_DEFAULT', |
| 135 'sr' : 'LANG_SERBIAN, SUBLANG_SERBIAN_CYRILLIC', |
| 136 'en-GB' : 'LANG_ENGLISH, SUBLANG_ENGLISH_UK', |
| 137 'it' : 'LANG_ITALIAN, SUBLANG_DEFAULT', |
| 138 'sk' : 'LANG_SLOVAK, SUBLANG_DEFAULT', |
| 139 'et' : 'LANG_ESTONIAN, SUBLANG_DEFAULT', |
| 140 'ja' : 'LANG_JAPANESE, SUBLANG_DEFAULT', |
| 141 'sl' : 'LANG_SLOVENIAN, SUBLANG_DEFAULT', |
| 142 'en' : 'LANG_ENGLISH, SUBLANG_ENGLISH_US', |
| 143 'fake_bidi' : 'LANG_HEBREW, SUBLANG_DEFAULT', |
| 144 } |
| 145 |
| 146 def GetLangCharsetPair(language) : |
| 147 if _LANGUAGE_CHARSET_PAIR.has_key(language) : |
| 148 return _LANGUAGE_CHARSET_PAIR[language] |
| 149 else : |
| 150 print 'Warning:GetLangCharsetPair() found undefined language %s' %(language) |
| 151 return '' |
| 152 |
| 153 def GetLangDirectivePair(language) : |
| 154 if _LANGUAGE_DIRECTIVE_PAIR.has_key(language) : |
| 155 return _LANGUAGE_DIRECTIVE_PAIR[language] |
| 156 else : |
| 157 print 'Warning:GetLangDirectivePair() found undefined language %s' % (langua
ge) |
| 158 return 'unknown language: see tools/grit/format/rc.py' |
| 159 |
| 160 def GetLangIdHex(language) : |
| 161 if _LANGUAGE_CHARSET_PAIR.has_key(language) : |
| 162 langcharset = _LANGUAGE_CHARSET_PAIR[language] |
| 163 lang_id = '0x' + langcharset[0:4] |
| 164 return lang_id |
| 165 else : |
| 166 print 'Warning:GetLangIdHex() found undefined language %s' %(language) |
| 167 return '' |
| 168 |
| 169 |
| 170 def GetCharsetIdDecimal(language) : |
| 171 if _LANGUAGE_CHARSET_PAIR.has_key(language) : |
| 172 langcharset = _LANGUAGE_CHARSET_PAIR[language] |
| 173 charset_decimal = int(langcharset[4:], 16) |
| 174 return str(charset_decimal) |
| 175 else : |
| 176 print 'Warning:GetCharsetIdDecimal() found undefined language %s' %(language
) |
| 177 return '' |
| 178 |
| 179 |
| 180 def GetUnifiedLangCode(language) : |
| 181 r = re.compile('([a-z]{1,2})_([a-z]{1,2})') |
| 182 if r.match(language) : |
| 183 underscore = language.find('_') |
| 184 return language[0:underscore] + '-' + language[underscore + 1:].upper() |
| 185 else : |
| 186 return language |
| 187 |
| 188 |
| 189 def _MakeRelativePath(base_path, path_to_make_relative): |
| 190 '''Returns a relative path such from the base_path to |
| 191 the path_to_make_relative. |
| 192 |
| 193 In other words, os.join(base_path, |
| 194 MakeRelativePath(base_path, path_to_make_relative)) |
| 195 is the same location as path_to_make_relative. |
| 196 |
| 197 Args: |
| 198 base_path: the root path |
| 199 path_to_make_relative: an absolute path that is on the same drive |
| 200 as base_path |
| 201 ''' |
| 202 |
| 203 def _GetPathAfterPrefix(prefix_path, path_with_prefix): |
| 204 '''Gets the subpath within in prefix_path for the path_with_prefix |
| 205 with no beginning or trailing path separators. |
| 206 |
| 207 Args: |
| 208 prefix_path: the base path |
| 209 path_with_prefix: a path that starts with prefix_path |
| 210 ''' |
| 211 assert path_with_prefix.startswith(prefix_path) |
| 212 path_without_prefix = path_with_prefix[len(prefix_path):] |
| 213 normalized_path = os.path.normpath(path_without_prefix.strip(os.path.sep)) |
| 214 if normalized_path == '.': |
| 215 normalized_path = '' |
| 216 return normalized_path |
| 217 |
| 218 def _GetCommonBaseDirectory(*args): |
| 219 '''Returns the common prefix directory for the given paths |
| 220 |
| 221 Args: |
| 222 The list of paths (at least one of which should be a directory) |
| 223 ''' |
| 224 prefix = os.path.commonprefix(args) |
| 225 # prefix is a character-by-character prefix (i.e. it does not end |
| 226 # on a directory bound, so this code fixes that) |
| 227 |
| 228 # if the prefix ends with the separator, then it is prefect. |
| 229 if len(prefix) > 0 and prefix[-1] == os.path.sep: |
| 230 return prefix |
| 231 |
| 232 # We need to loop through all paths or else we can get |
| 233 # tripped up by "c:\a" and "c:\abc". The common prefix |
| 234 # is "c:\a" which is a directory and looks good with |
| 235 # respect to the first directory but it is clear that |
| 236 # isn't a common directory when the second path is |
| 237 # examined. |
| 238 for path in args: |
| 239 assert len(path) >= len(prefix) |
| 240 # If the prefix the same length as the path, |
| 241 # then the prefix must be a directory (since one |
| 242 # of the arguements should be a directory). |
| 243 if path == prefix: |
| 244 continue |
| 245 # if the character after the prefix in the path |
| 246 # is the separator, then the prefix appears to be a |
| 247 # valid a directory as well for the given path |
| 248 if path[len(prefix)] == os.path.sep: |
| 249 continue |
| 250 # Otherwise, the prefix is not a directory, so it needs |
| 251 # to be shortened to be one |
| 252 index_sep = prefix.rfind(os.path.sep) |
| 253 # The use "index_sep + 1" because it includes the final sep |
| 254 # and it handles the case when the index_sep is -1 as well |
| 255 prefix = prefix[:index_sep + 1] |
| 256 # At this point we backed up to a directory bound which is |
| 257 # common to all paths, so we can quit going through all of |
| 258 # the paths. |
| 259 break |
| 260 return prefix |
| 261 |
| 262 prefix = _GetCommonBaseDirectory(base_path, path_to_make_relative) |
| 263 # If the paths had no commonality at all, then return the absolute path |
| 264 # because it is the best that can be done. If the path had to be relative |
| 265 # then eventually this absolute path will be discovered (when a build breaks) |
| 266 # and an appropriate fix can be made, but having this allows for the best |
| 267 # backward compatibility with the absolute path behavior in the past. |
| 268 if len(prefix) <= 0: |
| 269 return path_to_make_relative |
| 270 # Build a path from the base dir to the common prefix |
| 271 remaining_base_path = _GetPathAfterPrefix(prefix, base_path) |
| 272 |
| 273 # The follow handles two case: "" and "foo\\bar" |
| 274 path_pieces = remaining_base_path.split(os.path.sep) |
| 275 base_depth_from_prefix = len([d for d in path_pieces if len(d)]) |
| 276 base_to_prefix = (".." + os.path.sep) * base_depth_from_prefix |
| 277 |
| 278 # Put add in the path from the prefix to the path_to_make_relative |
| 279 remaining_other_path = _GetPathAfterPrefix(prefix, path_to_make_relative) |
| 280 return base_to_prefix + remaining_other_path |
| 281 |
| 282 |
| 283 class TopLevel(interface.ItemFormatter): |
| 284 '''Writes out the required preamble for RC files.''' |
| 285 def Format(self, item, lang='en', begin_item=True, output_dir='.'): |
| 286 assert isinstance(lang, types.StringTypes) |
| 287 if not begin_item: |
| 288 return '' |
| 289 else: |
| 290 # Find the location of the resource header file, so that we can include |
| 291 # it. |
| 292 resource_header = 'resource.h' # fall back to this |
| 293 language_directive = '' |
| 294 for output in item.GetRoot().GetOutputFiles(): |
| 295 if output.attrs['type'] == 'rc_header': |
| 296 resource_header = os.path.abspath(output.GetOutputFilename()) |
| 297 resource_header = _MakeRelativePath(output_dir, resource_header) |
| 298 if output.attrs['lang'] != lang: |
| 299 continue |
| 300 if output.attrs['language_section'] == '': |
| 301 # If no language_section is requested, no directive is added |
| 302 # (Used when the generated rc will be included from another rc |
| 303 # file that will have the appropriate language directive) |
| 304 language_directive = '' |
| 305 elif output.attrs['language_section'] == 'neutral': |
| 306 # If a neutral language section is requested (default), add a |
| 307 # neutral language directive |
| 308 language_directive = 'LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL' |
| 309 elif output.attrs['language_section'] == 'lang': |
| 310 language_directive = 'LANGUAGE %s' % GetLangDirectivePair(lang) |
| 311 resource_header = resource_header.replace('\\', '\\\\') |
| 312 return '''// Copyright (c) Google Inc. %d |
| 313 // All rights reserved. |
| 314 // This file is automatically generated by GRIT. Do not edit. |
| 315 |
| 316 #include "%s" |
| 317 #include <winresrc.h> |
| 318 #ifdef IDC_STATIC |
| 319 #undef IDC_STATIC |
| 320 #endif |
| 321 #define IDC_STATIC (-1) |
| 322 |
| 323 %s |
| 324 |
| 325 |
| 326 ''' % (util.GetCurrentYear(), resource_header, language_directive) |
| 327 # end Format() function |
| 328 |
| 329 |
| 330 |
| 331 class StringTable(interface.ItemFormatter): |
| 332 '''Surrounds a collection of string messages with the required begin and |
| 333 end blocks to declare a string table.''' |
| 334 |
| 335 def Format(self, item, lang='en', begin_item=True, output_dir='.'): |
| 336 assert isinstance(lang, types.StringTypes) |
| 337 if begin_item: |
| 338 return 'STRINGTABLE\nBEGIN\n' |
| 339 else: |
| 340 return 'END\n\n' |
| 341 |
| 342 |
| 343 class Message(interface.ItemFormatter): |
| 344 '''Writes out a single message to a string table.''' |
| 345 |
| 346 def Format(self, item, lang='en', begin_item=True, output_dir='.'): |
| 347 from grit.node import message |
| 348 if not begin_item: |
| 349 return '' |
| 350 |
| 351 assert isinstance(lang, types.StringTypes) |
| 352 assert isinstance(item, message.MessageNode) |
| 353 |
| 354 message = item.ws_at_start + item.Translate(lang) + item.ws_at_end |
| 355 # Escape quotation marks (RC format uses doubling-up |
| 356 message = message.replace('"', '""') |
| 357 # Replace linebreaks with a \n escape |
| 358 message = _LINEBREAKS.sub(r'\\n', message) |
| 359 |
| 360 name_attr = item.GetTextualIds()[0] |
| 361 |
| 362 return ' %-15s "%s"\n' % (name_attr, message) |
| 363 |
| 364 |
| 365 class RcSection(interface.ItemFormatter): |
| 366 '''Writes out an .rc file section.''' |
| 367 |
| 368 def Format(self, item, lang='en', begin_item=True, output_dir='.'): |
| 369 if not begin_item: |
| 370 return '' |
| 371 |
| 372 assert isinstance(lang, types.StringTypes) |
| 373 from grit.node import structure |
| 374 assert isinstance(item, structure.StructureNode) |
| 375 |
| 376 if item.IsExcludedFromRc(): |
| 377 return '' |
| 378 else: |
| 379 text = item.gatherer.Translate( |
| 380 lang, skeleton_gatherer=item.GetSkeletonGatherer(), |
| 381 pseudo_if_not_available=item.PseudoIsAllowed(), |
| 382 fallback_to_english=item.ShouldFallbackToEnglish()) + '\n\n' |
| 383 |
| 384 # Replace the language expand_variables in version rc info. |
| 385 unified_lang_code = GetUnifiedLangCode(lang) |
| 386 if text.find('[GRITVERLANGCHARSETHEX]') != -1: |
| 387 text = text.replace('[GRITVERLANGCHARSETHEX]', |
| 388 GetLangCharsetPair(unified_lang_code)) |
| 389 if text.find('[GRITVERLANGID]') != -1: |
| 390 text = text.replace('[GRITVERLANGID]', GetLangIdHex(unified_lang_code)) |
| 391 if text.find('[GRITVERCHARSETID]') != -1: |
| 392 text = text.replace('[GRITVERCHARSETID]', |
| 393 GetCharsetIdDecimal(unified_lang_code)) |
| 394 |
| 395 return text |
| 396 |
| 397 |
| 398 class RcInclude(interface.ItemFormatter): |
| 399 '''Writes out an item that is included in an .rc file (e.g. an ICON)''' |
| 400 |
| 401 def __init__(self, type, filenameWithoutPath = 0, relative_path = 0, |
| 402 flatten_html = 0): |
| 403 '''Indicates to the instance what the type of the resource include is, |
| 404 e.g. 'ICON' or 'HTML'. Case must be correct, i.e. if the type is all-caps |
| 405 the parameter should be all-caps. |
| 406 |
| 407 Args: |
| 408 type: 'ICON' |
| 409 ''' |
| 410 self.type_ = type |
| 411 self.filenameWithoutPath = filenameWithoutPath |
| 412 self.relative_path_ = relative_path |
| 413 self.flatten_html = flatten_html |
| 414 |
| 415 def Format(self, item, lang='en', begin_item=True, output_dir='.'): |
| 416 if not begin_item: |
| 417 return '' |
| 418 |
| 419 assert isinstance(lang, types.StringTypes) |
| 420 from grit.node import structure |
| 421 from grit.node import include |
| 422 assert isinstance(item, (structure.StructureNode, include.IncludeNode)) |
| 423 assert (isinstance(item, include.IncludeNode) or |
| 424 item.attrs['type'] in ['tr_html', 'admin_template', 'txt', 'muppet']
) |
| 425 |
| 426 # By default, we use relative pathnames to included resources so that |
| 427 # sharing the resulting .rc files is possible. |
| 428 # |
| 429 # The FileForLanguage() Function has the side effect of generating the file |
| 430 # if needed (e.g. if it is an HTML file include). |
| 431 filename = os.path.abspath(item.FileForLanguage(lang, output_dir)) |
| 432 if self.flatten_html: |
| 433 filename = item.Flatten(output_dir) |
| 434 elif self.filenameWithoutPath: |
| 435 filename = os.path.basename(filename) |
| 436 elif self.relative_path_: |
| 437 filename = _MakeRelativePath(output_dir, filename) |
| 438 |
| 439 filename = filename.replace('\\', '\\\\') # escape for the RC format |
| 440 |
| 441 if isinstance(item, structure.StructureNode) and item.IsExcludedFromRc(): |
| 442 return '' |
| 443 else: |
| 444 return '%-18s %-18s "%s"\n' % (item.attrs['name'], self.type_, filename) |
| 445 |
OLD | NEW |