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