1#!/usr/bin/env python 2# Copyright (c) 2012 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 9import os 10import types 11import re 12from functools import partial 13 14from grit import util 15from grit.node import misc 16 17 18def Format(root, lang='en', output_dir='.'): 19 from grit.node import empty, include, message, structure 20 21 yield _FormatHeader(root, lang, output_dir) 22 23 for item in root.ActiveDescendants(): 24 if isinstance(item, empty.MessagesNode): 25 # Write one STRINGTABLE per <messages> container. 26 # This is hacky: it iterates over the children twice. 27 yield 'STRINGTABLE\nBEGIN\n' 28 for subitem in item.ActiveDescendants(): 29 if isinstance(subitem, message.MessageNode): 30 with subitem: 31 yield FormatMessage(subitem, lang) 32 yield 'END\n\n' 33 elif isinstance(item, include.IncludeNode): 34 with item: 35 yield FormatInclude(item, lang, output_dir) 36 elif isinstance(item, structure.StructureNode): 37 with item: 38 yield FormatStructure(item, lang, output_dir) 39 40 41''' 42This dictionary defines the language charset pair lookup table, which is used 43for replacing the GRIT expand variables for language info in Product Version 44resource. The key is the language ISO country code, and the value 45is the language and character-set pair, which is a hexadecimal string 46consisting of the concatenation of the language and character-set identifiers. 47The first 4 digit of the value is the hex value of LCID, the remaining 484 digits is the hex value of character-set id(code page)of the language. 49 50LCID resource: http://msdn.microsoft.com/en-us/library/ms776294.aspx 51Codepage resource: http://www.science.co.il/language/locale-codes.asp 52 53We have defined three GRIT expand_variables to be used in the version resource 54file to set the language info. Here is an example how they should be used in 55the VS_VERSION_INFO section of the resource file to allow GRIT to localize 56the language info correctly according to product locale. 57 58VS_VERSION_INFO VERSIONINFO 59... 60BEGIN 61 BLOCK "StringFileInfo" 62 BEGIN 63 BLOCK "[GRITVERLANGCHARSETHEX]" 64 BEGIN 65 ... 66 END 67 END 68 BLOCK "VarFileInfo" 69 BEGIN 70 VALUE "Translation", [GRITVERLANGID], [GRITVERCHARSETID] 71 END 72END 73 74''' 75 76_LANGUAGE_CHARSET_PAIR = { 77 # Language neutral LCID, unicode(1200) code page. 78 'neutral' : '000004b0', 79 # LANG_USER_DEFAULT LCID, unicode(1200) code page. 80 'userdefault' : '040004b0', 81 'ar' : '040104e8', 82 'fi' : '040b04e4', 83 'ko' : '041203b5', 84 'es' : '0c0a04e4', 85 'bg' : '040204e3', 86 # No codepage for filipino, use unicode(1200). 87 'fil' : '046404e4', 88 'fr' : '040c04e4', 89 'lv' : '042604e9', 90 'sv' : '041d04e4', 91 'ca' : '040304e4', 92 'de' : '040704e4', 93 'lt' : '042704e9', 94 # Do not use! This is only around for backwards 95 # compatibility and will be removed - use fil instead 96 'tl' : '0c0004b0', 97 'zh-CN' : '080403a8', 98 'zh-TW' : '040403b6', 99 'zh-HK' : '0c0403b6', 100 'el' : '040804e5', 101 'no' : '001404e4', 102 'nb' : '041404e4', 103 'nn' : '081404e4', 104 'th' : '041e036a', 105 'he' : '040d04e7', 106 'iw' : '040d04e7', 107 'pl' : '041504e2', 108 'tr' : '041f04e6', 109 'hr' : '041a04e4', 110 # No codepage for Hindi, use unicode(1200). 111 'hi' : '043904b0', 112 'pt-PT' : '081604e4', 113 'pt-BR' : '041604e4', 114 'uk' : '042204e3', 115 'cs' : '040504e2', 116 'hu' : '040e04e2', 117 'ro' : '041804e2', 118 # No codepage for Urdu, use unicode(1200). 119 'ur' : '042004b0', 120 'da' : '040604e4', 121 'is' : '040f04e4', 122 'ru' : '041904e3', 123 'vi' : '042a04ea', 124 'nl' : '041304e4', 125 'id' : '042104e4', 126 'sr' : '081a04e2', 127 'en-GB' : '0809040e', 128 'it' : '041004e4', 129 'sk' : '041b04e2', 130 'et' : '042504e9', 131 'ja' : '041103a4', 132 'sl' : '042404e2', 133 'en' : '040904b0', 134 # LCID for Mexico; Windows does not support L.A. LCID. 135 'es-419' : '080a04e4', 136 # No codepage for Bengali, use unicode(1200). 137 'bn' : '044504b0', 138 'fa' : '042904e8', 139 # No codepage for Gujarati, use unicode(1200). 140 'gu' : '044704b0', 141 # No codepage for Kannada, use unicode(1200). 142 'kn' : '044b04b0', 143 # Malay (Malaysia) [ms-MY] 144 'ms' : '043e04e4', 145 # No codepage for Malayalam, use unicode(1200). 146 'ml' : '044c04b0', 147 # No codepage for Marathi, use unicode(1200). 148 'mr' : '044e04b0', 149 # No codepage for Oriya , use unicode(1200). 150 'or' : '044804b0', 151 # No codepage for Tamil, use unicode(1200). 152 'ta' : '044904b0', 153 # No codepage for Telugu, use unicode(1200). 154 'te' : '044a04b0', 155 # No codepage for Amharic, use unicode(1200). >= Vista. 156 'am' : '045e04b0', 157 'sw' : '044104e4', 158 'af' : '043604e4', 159 'eu' : '042d04e4', 160 'fr-CA' : '0c0c04e4', 161 'gl' : '045604e4', 162 # No codepage for Zulu, use unicode(1200). 163 'zu' : '043504b0', 164 'fake-bidi' : '040d04e7', 165} 166 167# Language ID resource: http://msdn.microsoft.com/en-us/library/ms776294.aspx 168# 169# There is no appropriate sublang for Spanish (Latin America) [es-419], so we 170# use Mexico. SUBLANG_DEFAULT would incorrectly map to Spain. Unlike other 171# Latin American countries, Mexican Spanish is supported by VERSIONINFO: 172# http://msdn.microsoft.com/en-us/library/aa381058.aspx 173 174_LANGUAGE_DIRECTIVE_PAIR = { 175 'neutral' : 'LANG_NEUTRAL, SUBLANG_NEUTRAL', 176 'userdefault' : 'LANG_NEUTRAL, SUBLANG_DEFAULT', 177 'ar' : 'LANG_ARABIC, SUBLANG_DEFAULT', 178 'fi' : 'LANG_FINNISH, SUBLANG_DEFAULT', 179 'ko' : 'LANG_KOREAN, SUBLANG_KOREAN', 180 'es' : 'LANG_SPANISH, SUBLANG_SPANISH_MODERN', 181 'bg' : 'LANG_BULGARIAN, SUBLANG_DEFAULT', 182 # LANG_FILIPINO (100) not in VC 7 winnt.h. 183 'fil' : '100, SUBLANG_DEFAULT', 184 'fr' : 'LANG_FRENCH, SUBLANG_FRENCH', 185 'lv' : 'LANG_LATVIAN, SUBLANG_DEFAULT', 186 'sv' : 'LANG_SWEDISH, SUBLANG_SWEDISH', 187 'ca' : 'LANG_CATALAN, SUBLANG_DEFAULT', 188 'de' : 'LANG_GERMAN, SUBLANG_GERMAN', 189 'lt' : 'LANG_LITHUANIAN, SUBLANG_LITHUANIAN', 190 # Do not use! See above. 191 'tl' : 'LANG_NEUTRAL, SUBLANG_DEFAULT', 192 'zh-CN' : 'LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED', 193 'zh-TW' : 'LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL', 194 'zh-HK' : 'LANG_CHINESE, SUBLANG_CHINESE_HONGKONG', 195 'el' : 'LANG_GREEK, SUBLANG_DEFAULT', 196 'no' : 'LANG_NORWEGIAN, SUBLANG_DEFAULT', 197 'nb' : 'LANG_NORWEGIAN, SUBLANG_NORWEGIAN_BOKMAL', 198 'nn' : 'LANG_NORWEGIAN, SUBLANG_NORWEGIAN_NYNORSK', 199 'th' : 'LANG_THAI, SUBLANG_DEFAULT', 200 'he' : 'LANG_HEBREW, SUBLANG_DEFAULT', 201 'iw' : 'LANG_HEBREW, SUBLANG_DEFAULT', 202 'pl' : 'LANG_POLISH, SUBLANG_DEFAULT', 203 'tr' : 'LANG_TURKISH, SUBLANG_DEFAULT', 204 'hr' : 'LANG_CROATIAN, SUBLANG_DEFAULT', 205 'hi' : 'LANG_HINDI, SUBLANG_DEFAULT', 206 'pt-PT' : 'LANG_PORTUGUESE, SUBLANG_PORTUGUESE', 207 'pt-BR' : 'LANG_PORTUGUESE, SUBLANG_DEFAULT', 208 'uk' : 'LANG_UKRAINIAN, SUBLANG_DEFAULT', 209 'cs' : 'LANG_CZECH, SUBLANG_DEFAULT', 210 'hu' : 'LANG_HUNGARIAN, SUBLANG_DEFAULT', 211 'ro' : 'LANG_ROMANIAN, SUBLANG_DEFAULT', 212 'ur' : 'LANG_URDU, SUBLANG_DEFAULT', 213 'da' : 'LANG_DANISH, SUBLANG_DEFAULT', 214 'is' : 'LANG_ICELANDIC, SUBLANG_DEFAULT', 215 'ru' : 'LANG_RUSSIAN, SUBLANG_DEFAULT', 216 'vi' : 'LANG_VIETNAMESE, SUBLANG_DEFAULT', 217 'nl' : 'LANG_DUTCH, SUBLANG_DEFAULT', 218 'id' : 'LANG_INDONESIAN, SUBLANG_DEFAULT', 219 'sr' : 'LANG_SERBIAN, SUBLANG_SERBIAN_LATIN', 220 'en-GB' : 'LANG_ENGLISH, SUBLANG_ENGLISH_UK', 221 'it' : 'LANG_ITALIAN, SUBLANG_DEFAULT', 222 'sk' : 'LANG_SLOVAK, SUBLANG_DEFAULT', 223 'et' : 'LANG_ESTONIAN, SUBLANG_DEFAULT', 224 'ja' : 'LANG_JAPANESE, SUBLANG_DEFAULT', 225 'sl' : 'LANG_SLOVENIAN, SUBLANG_DEFAULT', 226 'en' : 'LANG_ENGLISH, SUBLANG_ENGLISH_US', 227 # No L.A. sublang exists. 228 'es-419' : 'LANG_SPANISH, SUBLANG_SPANISH_MEXICAN', 229 'bn' : 'LANG_BENGALI, SUBLANG_DEFAULT', 230 'fa' : 'LANG_PERSIAN, SUBLANG_DEFAULT', 231 'gu' : 'LANG_GUJARATI, SUBLANG_DEFAULT', 232 'kn' : 'LANG_KANNADA, SUBLANG_DEFAULT', 233 'ms' : 'LANG_MALAY, SUBLANG_DEFAULT', 234 'ml' : 'LANG_MALAYALAM, SUBLANG_DEFAULT', 235 'mr' : 'LANG_MARATHI, SUBLANG_DEFAULT', 236 'or' : 'LANG_ORIYA, SUBLANG_DEFAULT', 237 'ta' : 'LANG_TAMIL, SUBLANG_DEFAULT', 238 'te' : 'LANG_TELUGU, SUBLANG_DEFAULT', 239 'am' : 'LANG_AMHARIC, SUBLANG_DEFAULT', 240 'sw' : 'LANG_SWAHILI, SUBLANG_DEFAULT', 241 'af' : 'LANG_AFRIKAANS, SUBLANG_DEFAULT', 242 'eu' : 'LANG_BASQUE, SUBLANG_DEFAULT', 243 'fr-CA' : 'LANG_FRENCH, SUBLANG_FRENCH_CANADIAN', 244 'gl' : 'LANG_GALICIAN, SUBLANG_DEFAULT', 245 'zu' : 'LANG_ZULU, SUBLANG_DEFAULT', 246 'pa' : 'LANG_PUNJABI, SUBLANG_PUNJABI_INDIA', 247 'sa' : 'LANG_SANSKRIT, SUBLANG_SANSKRIT_INDIA', 248 'si' : 'LANG_SINHALESE, SUBLANG_SINHALESE_SRI_LANKA', 249 'ne' : 'LANG_NEPALI, SUBLANG_NEPALI_NEPAL', 250 'ti' : 'LANG_TIGRIGNA, SUBLANG_TIGRIGNA_ERITREA', 251 'fake-bidi' : 'LANG_HEBREW, SUBLANG_DEFAULT', 252} 253 254# A note on 'no-specific-language' in the following few functions: 255# Some build systems may wish to call GRIT to scan for dependencies in 256# a language-agnostic way, and can then specify this fake language as 257# the output context. It should never be used when output is actually 258# being generated. 259 260def GetLangCharsetPair(language): 261 if _LANGUAGE_CHARSET_PAIR.has_key(language): 262 return _LANGUAGE_CHARSET_PAIR[language] 263 elif language == 'no-specific-language': 264 return '' 265 else: 266 print 'Warning:GetLangCharsetPair() found undefined language %s' %(language) 267 return '' 268 269def GetLangDirectivePair(language): 270 if _LANGUAGE_DIRECTIVE_PAIR.has_key(language): 271 return _LANGUAGE_DIRECTIVE_PAIR[language] 272 else: 273 # We don't check for 'no-specific-language' here because this 274 # function should only get called when output is being formatted, 275 # and at that point we would not want to get 276 # 'no-specific-language' passed as the language. 277 print ('Warning:GetLangDirectivePair() found undefined language %s' % 278 language) 279 return 'unknown language: see tools/grit/format/rc.py' 280 281def GetLangIdHex(language): 282 if _LANGUAGE_CHARSET_PAIR.has_key(language): 283 langcharset = _LANGUAGE_CHARSET_PAIR[language] 284 lang_id = '0x' + langcharset[0:4] 285 return lang_id 286 elif language == 'no-specific-language': 287 return '' 288 else: 289 print 'Warning:GetLangIdHex() found undefined language %s' %(language) 290 return '' 291 292 293def GetCharsetIdDecimal(language): 294 if _LANGUAGE_CHARSET_PAIR.has_key(language): 295 langcharset = _LANGUAGE_CHARSET_PAIR[language] 296 charset_decimal = int(langcharset[4:], 16) 297 return str(charset_decimal) 298 elif language == 'no-specific-language': 299 return '' 300 else: 301 print 'Warning:GetCharsetIdDecimal() found undefined language %s' % language 302 return '' 303 304 305def GetUnifiedLangCode(language) : 306 r = re.compile('([a-z]{1,2})_([a-z]{1,2})') 307 if r.match(language) : 308 underscore = language.find('_') 309 return language[0:underscore] + '-' + language[underscore + 1:].upper() 310 else : 311 return language 312 313 314def RcSubstitutions(substituter, lang): 315 '''Add language-based substitutions for Rc files to the substitutor.''' 316 unified_lang_code = GetUnifiedLangCode(lang) 317 substituter.AddSubstitutions({ 318 'GRITVERLANGCHARSETHEX': GetLangCharsetPair(unified_lang_code), 319 'GRITVERLANGID': GetLangIdHex(unified_lang_code), 320 'GRITVERCHARSETID': GetCharsetIdDecimal(unified_lang_code)}) 321 322 323def _FormatHeader(root, lang, output_dir): 324 '''Returns the required preamble for RC files.''' 325 assert isinstance(lang, types.StringTypes) 326 assert isinstance(root, misc.GritNode) 327 # Find the location of the resource header file, so that we can include 328 # it. 329 resource_header = 'resource.h' # fall back to this 330 language_directive = '' 331 for output in root.GetOutputFiles(): 332 if output.attrs['type'] == 'rc_header': 333 resource_header = os.path.abspath(output.GetOutputFilename()) 334 resource_header = util.MakeRelativePath(output_dir, resource_header) 335 if output.attrs['lang'] != lang: 336 continue 337 if output.attrs['language_section'] == '': 338 # If no language_section is requested, no directive is added 339 # (Used when the generated rc will be included from another rc 340 # file that will have the appropriate language directive) 341 language_directive = '' 342 elif output.attrs['language_section'] == 'neutral': 343 # If a neutral language section is requested (default), add a 344 # neutral language directive 345 language_directive = 'LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL' 346 elif output.attrs['language_section'] == 'lang': 347 language_directive = 'LANGUAGE %s' % GetLangDirectivePair(lang) 348 resource_header = resource_header.replace('\\', '\\\\') 349 return '''// This file is automatically generated by GRIT. Do not edit. 350 351#include "%s" 352#include <winresrc.h> 353#ifdef IDC_STATIC 354#undef IDC_STATIC 355#endif 356#define IDC_STATIC (-1) 357 358%s 359 360 361''' % (resource_header, language_directive) 362# end _FormatHeader() function 363 364 365def FormatMessage(item, lang): 366 '''Returns a single message of a string table.''' 367 message = item.ws_at_start + item.Translate(lang) + item.ws_at_end 368 # Escape quotation marks (RC format uses doubling-up 369 message = message.replace('"', '""') 370 # Replace linebreaks with a \n escape 371 message = util.LINEBREAKS.sub(r'\\n', message) 372 if hasattr(item.GetRoot(), 'GetSubstituter'): 373 substituter = item.GetRoot().GetSubstituter() 374 message = substituter.Substitute(message) 375 376 name_attr = item.GetTextualIds()[0] 377 378 return ' %-15s "%s"\n' % (name_attr, message) 379 380 381def _FormatSection(item, lang, output_dir): 382 '''Writes out an .rc file section.''' 383 assert isinstance(lang, types.StringTypes) 384 from grit.node import structure 385 assert isinstance(item, structure.StructureNode) 386 387 if item.IsExcludedFromRc(): 388 return '' 389 else: 390 text = item.gatherer.Translate( 391 lang, skeleton_gatherer=item.GetSkeletonGatherer(), 392 pseudo_if_not_available=item.PseudoIsAllowed(), 393 fallback_to_english=item.ShouldFallbackToEnglish()) + '\n\n' 394 395 # Replace the language expand_variables in version rc info. 396 if item.ExpandVariables() and hasattr(item.GetRoot(), 'GetSubstituter'): 397 substituter = item.GetRoot().GetSubstituter() 398 text = substituter.Substitute(text) 399 400 return text 401 402 403def FormatInclude(item, lang, output_dir, type=None, process_html=False): 404 '''Formats an item that is included in an .rc file (e.g. an ICON). 405 406 Args: 407 item: an IncludeNode or StructureNode 408 lang, output_dir: standard formatter parameters 409 type: .rc file resource type, e.g. 'ICON' (ignored unless item is a 410 StructureNode) 411 process_html: False/True (ignored unless item is a StructureNode) 412 ''' 413 assert isinstance(lang, types.StringTypes) 414 from grit.node import structure 415 from grit.node import include 416 assert isinstance(item, (structure.StructureNode, include.IncludeNode)) 417 418 if isinstance(item, include.IncludeNode): 419 type = item.attrs['type'].upper() 420 process_html = item.attrs['flattenhtml'] == 'true' 421 filename_only = item.attrs['filenameonly'] == 'true' 422 relative_path = item.attrs['relativepath'] == 'true' 423 else: 424 assert (isinstance(item, structure.StructureNode) and item.attrs['type'] in 425 ['admin_template', 'chrome_html', 'chrome_scaled_image', 'igoogle', 426 'muppet', 'tr_html', 'txt']) 427 filename_only = False 428 relative_path = False 429 430 # By default, we use relative pathnames to included resources so that 431 # sharing the resulting .rc files is possible. 432 # 433 # The FileForLanguage() Function has the side effect of generating the file 434 # if needed (e.g. if it is an HTML file include). 435 filename = os.path.abspath(item.FileForLanguage(lang, output_dir)) 436 if process_html: 437 filename = item.Process(output_dir) 438 elif filename_only: 439 filename = os.path.basename(filename) 440 elif relative_path: 441 filename = util.MakeRelativePath(output_dir, filename) 442 443 filename = filename.replace('\\', '\\\\') # escape for the RC format 444 445 if isinstance(item, structure.StructureNode) and item.IsExcludedFromRc(): 446 return '' 447 else: 448 return '%-18s %-18s "%s"\n' % (item.attrs['name'], type, filename) 449 450 451def _DoNotFormat(item, lang, output_dir): 452 return '' 453 454 455# Formatter instance to use for each type attribute 456# when formatting Structure nodes. 457_STRUCTURE_FORMATTERS = { 458 'accelerators' : _FormatSection, 459 'dialog' : _FormatSection, 460 'menu' : _FormatSection, 461 'rcdata' : _FormatSection, 462 'version' : _FormatSection, 463 'admin_template' : partial(FormatInclude, type='ADM'), 464 'chrome_html' : partial(FormatInclude, type='BINDATA', 465 process_html=True), 466 'chrome_scaled_image' : partial(FormatInclude, type='BINDATA'), 467 'igoogle' : partial(FormatInclude, type='XML'), 468 'muppet' : partial(FormatInclude, type='XML'), 469 'tr_html' : partial(FormatInclude, type='HTML'), 470 'txt' : partial(FormatInclude, type='TXT'), 471 'policy_template_metafile': _DoNotFormat, 472} 473 474 475def FormatStructure(item, lang, output_dir): 476 formatter = _STRUCTURE_FORMATTERS[item.attrs['type']] 477 return formatter(item, lang, output_dir) 478